* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pith / send.c
blobd2bbdf43772d63067387e75c627864f6d0a8f7bf
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/send.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/store.h"
20 #include "../pith/mimedesc.h"
21 #include "../pith/context.h"
22 #include "../pith/status.h"
23 #include "../pith/folder.h"
24 #include "../pith/bldaddr.h"
25 #include "../pith/pipe.h"
26 #include "../pith/mailview.h"
27 #include "../pith/mailindx.h"
28 #include "../pith/list.h"
29 #include "../pith/filter.h"
30 #include "../pith/reply.h"
31 #include "../pith/addrstring.h"
32 #include "../pith/rfc2231.h"
33 #include "../pith/stream.h"
34 #include "../pith/util.h"
35 #include "../pith/adrbklib.h"
36 #include "../pith/options.h"
37 #include "../pith/busy.h"
38 #include "../pith/text.h"
39 #include "../pith/imap.h"
40 #include "../pith/ablookup.h"
41 #include "../pith/sort.h"
42 #include "../pith/smime.h"
44 #include "../c-client/smtp.h"
45 #include "../c-client/nntp.h"
48 /* this is used in pine_send and pine_simple_send */
49 /* name::type::canedit::writehdr::localcopy::rcptto */
50 PINEFIELD pf_template[] = {
51 {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
52 {"From", Address, 0, 1, 1, 0},
53 {"Reply-To", Address, 0, 1, 1, 0},
54 {TONAME, Address, 1, 1, 1, 1},
55 {CCNAME, Address, 1, 1, 1, 1},
56 {"bcc", Address, 1, 0, 1, 1},
57 {"Newsgroups", FreeText, 1, 1, 1, 0},
58 {"Fcc", Fcc, 1, 0, 0, 0},
59 {"Lcc", Address, 1, 0, 1, 1},
60 {"Attchmnt", Attachment, 1, 1, 1, 0},
61 {SUBJNAME, Subject, 1, 1, 1, 0},
62 {"References", FreeText, 0, 1, 1, 0},
63 {"Date", FreeText, 0, 1, 1, 0},
64 {"In-Reply-To", FreeText, 0, 1, 1, 0},
65 {"Message-ID", FreeText, 0, 1, 1, 0},
66 {PRIORITYNAME, FreeText, 0, 1, 1, 0},
67 {"User-Agent", FreeText, 0, 1, 1, 0},
68 {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
69 {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
70 {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
71 {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
72 {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
73 {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
74 {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
75 {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
76 {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
77 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
78 {"X-X-Sender", Address, 0, 1, 1, 0},
79 #endif
80 {NULL, FreeText}
84 PRIORITY_S priorities[] = {
85 {1, "Highest"},
86 {2, "High"},
87 {3, "Normal"},
88 {4, "Low"},
89 {5, "Lowest"},
90 {0, NULL}
94 #define ctrl(c) ((c) & 0x1f)
96 /* which message part to test for xliteration */
97 typedef enum {MsgBody, HdrText} MsgPart;
101 * Internal prototypes
103 long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
104 int l_flush_net(int);
105 int l_putc(int);
106 int pine_write_header_line(char *, char *, STORE_S *);
107 int pine_write_params(PARAMETER *, STORE_S *);
108 char *tidy_smtp_mess(char *, char *, char *, size_t);
109 int lmc_body_header_line(char *, int);
110 int lmc_body_header_finish(void);
111 int pwbh_finish(int, STORE_S *);
112 int sent_percent(void);
113 unsigned short *setup_avoid_table(void);
114 #ifndef _WINDOWS
115 int mta_handoff(METAENV *, BODY *, char *, size_t, char **, void (*)(char *, int),
116 void (*)(PIPE_S *, int, void *));
117 char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
118 void (*)(PIPE_S *, int, void *));
119 char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
120 void (*)(PIPE_S *, int, void *));
121 long pine_pipe_soutr_nl(void *, char *);
122 #endif
123 char *smtp_command(char *, size_t);
124 void *piped_smtp_open(char *, char *, unsigned long);
125 void *piped_aopen(NETMBX *, char *, char *);
126 long piped_soutr(void *, char *);
127 long piped_sout(void *, char *, unsigned long);
128 char *piped_getline(void *);
129 void piped_close(void *);
130 void piped_abort(void *);
131 char *piped_host(void *);
132 unsigned long piped_port(void *);
133 char *posting_characterset(void *, char *, MsgPart);
134 int body_is_translatable(void *, char *);
135 int text_is_translatable(void *, char *);
136 int dummy_putc(int);
137 unsigned long *init_charsetchecker(char *);
138 int representable_in_charset(unsigned long, char *);
139 char *most_preferred_charset(unsigned long);
142 * Storage object where the FCC (or postponed msg) is to be written.
143 * This is amazingly bogus. Much work was done to put messages
144 * together and encode them as they went to the tmp file for sendmail
145 * or into the SMTP slot (especially for for DOS, to prevent a temporary
146 * file (and needlessly copying the message).
148 * HOWEVER, since there's no piping into c-client routines
149 * (particularly mail_append() which copies the fcc), the fcc will have
150 * to be copied to disk. This global tells pine's copy of the rfc822
151 * output functions where to also write the message bytes for the fcc.
152 * With piping in the c-client we could just have two pipes to shove
153 * down rather than messing with damn copies. FIX THIS!
155 * The function open_fcc, locates the actual folder and creates it if
156 * requested before any mailing or posting is done.
158 struct local_message_copy lmc;
162 * Locally global pointer to stream used for sending/posting.
163 * It's also used to indicate when/if we write the Bcc: field in
164 * the header.
166 static SENDSTREAM *sending_stream = NULL;
169 static struct hooks {
170 void *rfc822_out; /* Message outputter */
171 } sending_hooks;
174 static FILE *verbose_send_output = NULL;
175 static long send_bytes_sent, send_bytes_to_send;
176 static METAENV *send_header = NULL;
179 * Hooks for prompts and confirmations
181 int (*pith_opt_daemon_confirm)(void);
184 static NETDRIVER piped_io = {
185 piped_smtp_open, /* open a connection */
186 piped_aopen, /* open an authenticated connection */
187 piped_getline, /* get a line */
188 NULL, /* get a buffer */
189 piped_soutr, /* output pushed data */
190 piped_sout, /* output string */
191 piped_close, /* close connection */
192 piped_host, /* return host name */
193 piped_host, /* remotehost */
194 piped_port, /* return port number */
195 piped_host /* return local host (NOTE: same as host!) */
200 * Since c-client preallocates, it's necessary here to define a limit
201 * such that we don't blow up in c-client (see rfc822_address_line()).
203 #define MAX_SINGLE_ADDR MAILTMPLEN
205 #define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
209 * postponed_stream - return stream associated with postponed messages
210 * in argument.
213 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
215 MAILSTREAM *stream = NULL;
216 CONTEXT_S *p_cntxt = NULL;
217 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
218 int exists;
220 if(!(streamp && mbox))
221 return(0);
223 *streamp = NULL;
226 * find default context to look for folder...
228 * The "mbox" is assumed to be local if we're given what looks
229 * like an absolute path. This is different from Goto/Save
230 * where we do a lot of work to interpret paths relative to the
231 * server. This reason is to support all the pre-4.00 pinerc'
232 * that specified a path and because there's yet to be a way
233 * in c-client to specify otherwise in the face of a remote
234 * context.
236 if(!is_absolute_path(mbox)
237 && !(p_cntxt = default_save_context(ps_global->context_list)))
238 p_cntxt = ps_global->context_list;
240 /* check to see if the folder exists, the user wants to continue
241 * and that we can actually read something in...
243 exists = folder_name_exists(p_cntxt, mbox, &fullname);
244 if(fullname)
245 mbox = fullname;
247 if(exists & FEX_ISFILE){
248 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
249 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
251 * The mbox is relative to the home directory.
252 * Make it absolute so we can compare it to
253 * stream->mailbox.
255 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
256 SIZEOF_20KBUF);
257 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
258 tmp[sizeof(tmp)-1] = '\0';
261 if((stream = ps_global->mail_stream)
262 && !(stream->mailbox
263 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
264 || (*tmp == '{'
265 && same_stream(tmp, stream)
266 && (p = strchr(tmp, '}'))
267 && (q = strchr(stream->mailbox,'}'))
268 && !strcmp(p + 1, q + 1)))))
269 stream = NULL;
271 if(!stream){
272 stream = context_open(p_cntxt, NULL, mbox,
273 SP_USEPOOL|SP_TEMPUSE, NULL);
274 if(stream && !stream->halfopen){
275 if(stream->nmsgs > 0)
276 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
278 if(checknmsgs && stream->nmsgs < 1){
279 pine_mail_close(stream);
280 exists = 0;
281 stream = NULL;
284 else{
285 q_status_message2(SM_ORDER | SM_DING, 3, 3,
286 _("Can't open %s mailbox: %s"), type, mbox);
287 if(stream)
288 pine_mail_close(stream);
290 exists = 0;
291 stream = NULL;
295 else{
296 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
297 q_status_message1(SM_ORDER | SM_DING, 3, 3,
298 _("%s message folder doesn't exist!"), type);
302 if(fullname)
303 fs_give((void **) &fullname);
305 *streamp = stream;
307 return(exists);
312 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
313 struct mail_bodystruct **body, char **fcc, char **lcc,
314 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
315 ACTION_S **role, int flags, STORE_S *so)
317 MAILSTREAM *stream;
318 ENVELOPE *e = NULL;
319 BODY *b;
320 PART *part;
321 PINEFIELD *pf;
322 gf_io_t pc;
323 char *extras, **fields, **values, *p;
324 char *hdrs[2], *h, *charset;
325 char **smtp_servers = NULL, **nntp_servers = NULL;
326 int i, pine_generated = 0, our_replyto = 0;
327 int added_to_role = 0;
328 unsigned gbpt_flags = GBPT_NONE;
329 MESSAGECACHE *mc;
331 if(!(streamp && *streamp))
332 return(redraft_cleanup(streamp, TRUE, flags));
334 stream = *streamp;
336 if(flags & REDRAFT_HTML)
337 gbpt_flags |= GBPT_HTML_OK;
339 /* grok any user-defined or non-c-client headers */
340 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
343 * The custom headers to look for in the suspended message should
344 * have been stored in the X-Our-Headers header. So first we get
345 * that list. If we can't find it (version that stored the
346 * message < 4.30) then we use the global custom list.
348 hdrs[0] = OUR_HDRS_LIST;
349 hdrs[1] = NULL;
350 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
351 int commas = 0;
352 char **list;
353 char *hdrval = NULL;
355 if((hdrval = strindex(h, ':')) != NULL){
356 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
357 hdrval++)
361 /* count elements in list */
362 for(p = hdrval; p && *p; p++)
363 if(*p == ',')
364 commas++;
366 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
368 *custom = parse_custom_hdrs(list, Replace);
369 add_defaults_from_list(*custom,
370 ps_global->VAR_CUSTOM_HDRS);
371 free_list_array(&list);
374 if(*custom && !(*custom)->name){
375 free_customs(*custom);
376 *custom = NULL;
379 fs_give((void **)&h);
382 if(!*custom)
383 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
385 #define INDEX_FCC 0
386 #define INDEX_POSTERR 1
387 #define INDEX_REPLYUID 2
388 #define INDEX_REPLYMBOX 3
389 #define INDEX_SMTP 4
390 #define INDEX_NNTP 5
391 #define INDEX_CURSORPOS 6
392 #define INDEX_OUR_REPLYTO 7
393 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
394 #define FIELD_COUNT 9
396 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
399 * Having these two fields separated isn't the slickest, but
400 * converting the pointer array for fetchheader_lines() to
401 * a list of structures or some such for simple_header_parse()
402 * is too goonie. We could do something like re-use c-client's
403 * PARAMETER struct which is a simple char * pairing, but that
404 * doesn't make sense to pass to fetchheader_lines()...
406 fields = (char **) fs_get((size_t) i * sizeof(char *));
407 values = (char **) fs_get((size_t) i * sizeof(char *));
408 memset(fields, 0, (size_t) i * sizeof(char *));
409 memset(values, 0, (size_t) i * sizeof(char *));
411 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
412 fields[++i] = "X-Post-Error"; /* posting errors too */
413 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
414 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
415 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
416 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
417 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
418 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
419 fields[++i] = "Lcc"; /* Lcc: too... */
420 if(++i != FIELD_COUNT)
421 alpine_panic("Fix FIELD_COUNT");
423 for(pf = *custom; pf && pf->name; pf = pf->next)
424 if(!pf->standard)
425 fields[i++] = pf->name; /* assign custom fields */
427 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
428 simple_header_parse(extras, fields, values);
429 fs_give((void **) &extras);
432 * translate RFC 1522 strings,
433 * starting with "Lcc" field
435 for(i = INDEX_LCC; fields[i]; i++)
436 if(values[i]){
437 size_t len;
438 char *bufp, *biggerbuf = NULL;
440 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
441 len++;
442 biggerbuf = (char *)fs_get(len * sizeof(char));
443 bufp = biggerbuf;
445 else{
446 bufp = tmp_20k_buf;
447 len = SIZEOF_20KBUF;
450 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
452 if(p == tmp_20k_buf){
453 fs_give((void **)&values[i]);
454 values[i] = cpystr(p);
457 if(biggerbuf)
458 fs_give((void **)&biggerbuf);
461 for(pf = *custom, i = FIELD_COUNT;
462 pf && pf->name;
463 pf = pf->next){
464 if(pf->standard){
466 * Because the value is already in the envelope.
468 pf->cstmtype = NoMatch;
469 continue;
472 if(values[i]){ /* use this instead of default */
473 if(pf->textbuf)
474 fs_give((void **)&pf->textbuf);
476 pf->textbuf = values[i]; /* freed in pine_send! */
478 else if(pf->textbuf) /* was erased before postpone */
479 fs_give((void **)&pf->textbuf);
481 i++;
484 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
485 pine_generated = 1; /* we put it there? */
488 * Since c-client fills in the reply_to field in the envelope
489 * even if there isn't a Reply-To header in the message we
490 * have to work around that. When we postpone we add
491 * a second header that has value "Empty" if there really
492 * was a Reply-To and it was empty. It has the
493 * value "Full" if we put the Reply-To contents there
494 * intentionally (and it isn't empty).
496 if(values[INDEX_OUR_REPLYTO]){
497 if(values[INDEX_OUR_REPLYTO][0] == 'E')
498 our_replyto = 'E'; /* we put an empty one there */
499 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
500 our_replyto = 'F'; /* we put it there */
502 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
505 if(fcc) /* fcc: special case... */
506 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
507 else if(values[INDEX_FCC])
508 fs_give((void **) &values[INDEX_FCC]);
510 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
511 q_status_message(SM_ORDER|SM_DING, 4, 4,
512 values[INDEX_POSTERR]);
513 fs_give((void **) &values[INDEX_POSTERR]);
516 if(values[INDEX_REPLYUID]){
517 if(reply)
518 *reply = build_reply_uid(values[INDEX_REPLYUID]);
520 fs_give((void **) &values[INDEX_REPLYUID]);
522 if(values[INDEX_REPLYMBOX] && reply && *reply)
523 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
525 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
526 (*reply)->origmbox = cpystr((*reply)->mailbox);
529 if(values[INDEX_REPLYMBOX])
530 fs_give((void **) &values[INDEX_REPLYMBOX]);
532 if(values[INDEX_SMTP]){
533 char *q;
534 size_t cnt = 0;
537 * Turn the space delimited list of smtp servers into
538 * a char ** list.
540 p = values[INDEX_SMTP];
542 if(!*p || isspace((unsigned char) *p))
543 cnt++;
544 } while(*p++);
546 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
547 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
549 cnt = 0;
550 q = p = values[INDEX_SMTP];
552 if(!*p || isspace((unsigned char) *p)){
553 if(*p){
554 *p = '\0';
555 smtp_servers[cnt++] = cpystr(q);
556 *p = ' ';
557 q = p+1;
559 else
560 smtp_servers[cnt++] = cpystr(q);
562 } while(*p++);
564 fs_give((void **) &values[INDEX_SMTP]);
567 if(values[INDEX_NNTP]){
568 char *q;
569 size_t cnt = 0;
572 * Turn the space delimited list of smtp nntp into
573 * a char ** list.
575 p = values[INDEX_NNTP];
577 if(!*p || isspace((unsigned char) *p))
578 cnt++;
579 } while(*p++);
581 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
582 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
584 cnt = 0;
585 q = p = values[INDEX_NNTP];
587 if(!*p || isspace((unsigned char) *p)){
588 if(*p){
589 *p = '\0';
590 nntp_servers[cnt++] = cpystr(q);
591 *p = ' ';
592 q = p+1;
594 else
595 nntp_servers[cnt++] = cpystr(q);
597 } while(*p++);
599 fs_give((void **) &values[INDEX_NNTP]);
602 if(values[INDEX_CURSORPOS]){
604 * The redraft cursor position is written as two fields
605 * separated by a space. First comes the name of the
606 * header field we're in, or just a ":" if we're in the
607 * body. Then comes the offset into that header or into
608 * the body.
610 if(redraft_pos){
611 char *q1, *q2;
613 *redraft_pos
614 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
615 (*redraft_pos)->offset = 0L;
617 q1 = skip_white_space(values[INDEX_CURSORPOS]);
618 if(*q1 && (q2 = strindex(q1, SPACE))){
619 *q2 = '\0';
620 (*redraft_pos)->hdrname = cpystr(q1);
621 q1 = skip_white_space(q2+1);
622 if(*q1)
623 (*redraft_pos)->offset = atol(q1);
625 else
626 (*redraft_pos)->hdrname = cpystr(":");
629 fs_give((void **) &values[INDEX_CURSORPOS]);
632 if(lcc)
633 *lcc = values[INDEX_LCC];
634 else
635 fs_give((void **) &values[INDEX_LCC]);
638 fs_give((void **)&fields);
639 fs_give((void **)&values);
641 *outgoing = copy_envelope(e);
644 * If the postponed message has a From which is different from
645 * the default, it is either because allow-changing-from is on
646 * or because there was a role with a from that allowed it to happen.
647 * If allow-changing-from is not on, put this back in a role
648 * so that it will be allowed again in pine_send.
650 if(role && *role == NULL &&
651 !ps_global->never_allow_changing_from &&
652 *outgoing){
654 * Now check to see if the from is different from default from.
656 ADDRESS *deffrom;
658 deffrom = generate_from();
659 if(!((*outgoing)->from &&
660 address_is_same(deffrom, (*outgoing)->from) &&
661 ((!(deffrom->personal && deffrom->personal[0]) &&
662 !((*outgoing)->from->personal &&
663 (*outgoing)->from->personal[0])) ||
664 (deffrom->personal && (*outgoing)->from->personal &&
665 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
667 *role = (ACTION_S *)fs_get(sizeof(**role));
668 memset((void *)*role, 0, sizeof(**role));
669 if(!(*outgoing)->from)
670 (*outgoing)->from = mail_newaddr();
672 (*role)->from = (*outgoing)->from;
673 (*outgoing)->from = NULL;
674 added_to_role++;
677 mail_free_address(&deffrom);
681 * Look at each empty address and see if the user has specified
682 * a default for that field or not. If they have, that means
683 * they have erased it before postponing, so they won't want
684 * the default to come back. If they haven't specified a default,
685 * then the default should be generated in pine_send. We prevent
686 * the default from being assigned by assigning an empty address
687 * to the variable here.
689 * BUG: We should do this for custom Address headers, too, but
690 * there isn't such a thing yet.
692 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
693 (*outgoing)->to = mail_newaddr();
694 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
695 (*outgoing)->cc = mail_newaddr();
696 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
697 (*outgoing)->bcc = mail_newaddr();
699 if(our_replyto == 'E'){
700 /* user erased reply-to before postponing */
701 if((*outgoing)->reply_to)
702 mail_free_address(&(*outgoing)->reply_to);
705 * If empty is not the normal default, make the outgoing
706 * reply_to be an empty address. If it is default, leave it
707 * as NULL and the default will be used.
709 if(hdr_is_in_list("reply-to", *custom)){
710 PINEFIELD pf;
712 pf.name = "reply-to";
713 set_default_hdrval(&pf, *custom);
714 if(pf.textbuf){
715 if(pf.textbuf[0]) /* empty is not default */
716 (*outgoing)->reply_to = mail_newaddr();
718 fs_give((void **)&pf.textbuf);
722 else if(our_replyto == 'F'){
723 int add_to_role = 0;
726 * The reply-to is real. If it is different from the default
727 * reply-to, put it in the role so that it will show up when
728 * the user edits.
730 if(hdr_is_in_list("reply-to", *custom)){
731 PINEFIELD pf;
732 char *str;
734 pf.name = "reply-to";
735 set_default_hdrval(&pf, *custom);
736 if(pf.textbuf && pf.textbuf[0]){
737 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
738 if(!strcmp(str, pf.textbuf)){
739 /* standard value, leave it alone */
742 else /* not standard, put in role */
743 add_to_role++;
745 fs_give((void **)&str);
748 else /* not standard, put in role */
749 add_to_role++;
751 if(pf.textbuf)
752 fs_give((void **)&pf.textbuf);
754 else /* not standard, put in role */
755 add_to_role++;
757 if(add_to_role && role && (*role == NULL || added_to_role)){
758 if(*role == NULL){
759 added_to_role++;
760 *role = (ACTION_S *)fs_get(sizeof(**role));
761 memset((void *)*role, 0, sizeof(**role));
764 (*role)->replyto = (*outgoing)->reply_to;
765 (*outgoing)->reply_to = NULL;
768 else{
769 /* this is a bogus c-client generated replyto */
770 if((*outgoing)->reply_to)
771 mail_free_address(&(*outgoing)->reply_to);
774 if((smtp_servers || nntp_servers)
775 && role && (*role == NULL || added_to_role)){
776 if(*role == NULL){
777 *role = (ACTION_S *)fs_get(sizeof(**role));
778 memset((void *)*role, 0, sizeof(**role));
781 if(smtp_servers)
782 (*role)->smtp = smtp_servers;
783 if(nntp_servers)
784 (*role)->nntp = nntp_servers;
787 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
788 (*outgoing)->subject = cpystr("");
790 if(!pine_generated){
792 * Now, this is interesting. We should have found
793 * the "fcc:" field if pine wrote the message being
794 * redrafted. Hence, we probably can't trust the
795 * "originator" type fields, so we'll blast them and let
796 * them get set later in pine_send. This should allow
797 * folks with custom or edited From's and such to still
798 * use redraft reasonably, without inadvertently sending
799 * messages that appear to be "From" others...
801 if((*outgoing)->from)
802 mail_free_address(&(*outgoing)->from);
805 * Ditto for Reply-To and Sender...
807 if((*outgoing)->reply_to)
808 mail_free_address(&(*outgoing)->reply_to);
810 if((*outgoing)->sender)
811 mail_free_address(&(*outgoing)->sender);
814 if(!pine_generated || !(flags & REDRAFT_DEL)){
817 * Generate a fresh message id for pretty much the same
818 * reason From and such got wacked...
819 * Also, if we're coming from a form letter, we need to
820 * generate a different id each time.
822 if((*outgoing)->message_id)
823 fs_give((void **)&(*outgoing)->message_id);
825 (*outgoing)->message_id = generate_message_id(role ? *role : NULL);
828 if(b && b->type != TYPETEXT){
829 if(b->type == TYPEMULTIPART){
830 if(strucmp(b->subtype, "mixed")){
831 q_status_message1(SM_INFO, 3, 4,
832 "Converting Multipart/%s to Multipart/Mixed",
833 b->subtype);
834 fs_give((void **)&b->subtype);
835 b->subtype = cpystr("mixed");
838 else{
839 q_status_message2(SM_ORDER | SM_DING, 3, 4,
840 "Unable to resume type %s/%s message",
841 body_types[b->type], b->subtype);
842 return(redraft_cleanup(streamp, TRUE, flags));
846 gf_set_so_writec(&pc, so);
848 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
849 *body = copy_body(NULL, b);
850 part = (*body)->nested.part;
851 part->body.contents.text.data = (void *)so;
852 set_mime_type_by_grope(&part->body);
853 if(part->body.type != TYPETEXT){
854 q_status_message2(SM_ORDER | SM_DING, 3, 4,
855 "Unable to resume; first part is non-text: %s/%s",
856 body_types[part->body.type],
857 part->body.subtype);
858 return(redraft_cleanup(streamp, TRUE, flags));
861 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
862 /* let outgoing routines decide on charset */
863 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
864 set_parameter(&part->body.parameter, "charset", NULL);
866 fs_give((void **) &charset);
869 ps_global->postpone_no_flow = 1;
871 get_body_part_text(stream, &b->nested.part->body,
872 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
873 ps_global->postpone_no_flow = 0;
875 if(!fetch_contents(stream, cont_msg, NULL, *body))
876 q_status_message(SM_ORDER | SM_DING, 3, 4,
877 _("Error including all message parts"));
879 else{
880 *body = mail_newbody();
881 (*body)->type = TYPETEXT;
882 if(b->subtype /* these types are transformed to text/plain */
883 && strucmp(b->subtype, "richtext")
884 && strucmp(b->subtype, "enriched")
885 && strucmp(b->subtype, "html"))
886 (*body)->subtype = cpystr(b->subtype);
888 if((charset = parameter_val(b->parameter,"charset")) != NULL){
889 /* let outgoing routines decide on charset */
890 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
891 fs_give((void **) &charset);
892 else{
893 (*body)->parameter = mail_newbody_parameter();
894 (*body)->parameter->attribute = cpystr("charset");
895 if(utf8_charset(charset)){
896 fs_give((void **) &charset);
897 (*body)->parameter->value = cpystr("UTF-8");
899 else
900 (*body)->parameter->value = charset;
904 (*body)->contents.text.data = (void *)so;
905 ps_global->postpone_no_flow = 1;
906 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
907 NULL, NULL, gbpt_flags);
908 ps_global->postpone_no_flow = 0;
911 gf_clear_so_writec(so);
913 /* We have what we want, blast this message... */
914 if((flags & REDRAFT_DEL)
915 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
916 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
917 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
919 else
920 return(redraft_cleanup(streamp, TRUE, flags));
922 return(redraft_cleanup(streamp, FALSE, flags));
926 /*----------------------------------------------------------------------
927 Clear deleted messages from given stream and expunge if necessary
929 Args: stream --
930 problem --
932 ----*/
934 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
936 MAILSTREAM *stream;
938 if(!(streamp && *streamp))
939 return(0);
941 if(!problem && streamp && (stream = *streamp)){
942 if(stream->nmsgs){
943 ps_global->expunge_in_progress = 1;
944 mail_expunge(stream); /* clean out deleted */
945 ps_global->expunge_in_progress = 0;
948 if(!stream->nmsgs){ /* close and delete folder */
949 int do_the_broach = 0;
950 char *mbox = NULL;
952 if(stream){
953 if(stream->original_mailbox && stream->original_mailbox[0])
954 mbox = cpystr(stream->original_mailbox);
955 else if(stream->mailbox && stream->mailbox[0])
956 mbox = cpystr(stream->mailbox);
959 /* if it is current, we have to change folders */
960 if(stream == ps_global->mail_stream)
961 do_the_broach++;
964 * This is the stream to the empty postponed-msgs folder.
965 * We are going to delete the folder in a second. It is
966 * probably preferable to unselect the mailbox and leave
967 * this stream open for re-use instead of actually closing it,
968 * so we do that if possible.
970 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
972 * This does the UNSELECT on the stream. A NULL
973 * return should mean that something went wrong and
974 * a mail_close already happened, so that should have
975 * cleaned things up in the callback.
977 if((stream=mail_open(stream, stream->mailbox,
978 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
979 /* now close it so it is put into the stream cache */
980 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
981 pine_mail_close(stream);
984 else
985 pine_mail_actually_close(stream);
987 *streamp = NULL;
989 if(do_the_broach){
990 ps_global->mail_stream = NULL; /* already closed above */
993 if(mbox && !pine_mail_delete(NULL, mbox))
994 q_status_message1(SM_ORDER|SM_DING, 3, 3,
995 /* TRANSLATORS: Arg is a mailbox name */
996 _("Can't delete %s"), mbox);
998 if(mbox)
999 fs_give((void **) &mbox);
1003 return(!problem);
1007 /*----------------------------------------------------------------------
1008 Parse the given header text for any given fields
1010 Args: text -- Text to parse for fcc and attachments refs
1011 fields -- array of field names to look for
1012 values -- array of pointer to save values to, returned NULL if
1013 fields isn't in text.
1015 This function simply looks for the given fields in the given header
1016 text string.
1017 NOTE: newlines are expected CRLF, and we'll ignore continuations
1018 ----*/
1019 void
1020 simple_header_parse(char *text, char **fields, char **values)
1022 int i, n;
1023 char *p, *t;
1025 for(i = 0; fields[i]; i++)
1026 values[i] = NULL; /* clear values array */
1028 /*---- Loop until the end of the header ----*/
1029 for(p = text; *p; ){
1030 for(i = 0; fields[i]; i++) /* find matching field? */
1031 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1032 for(p += n + 1; *p; p++){ /* find start of value */
1033 if(*p == '\015' && *(p+1) == '\012'
1034 && !isspace((unsigned char) *(p+2)))
1035 break;
1037 if(!isspace((unsigned char) *p))
1038 break; /* order here is key... */
1041 if(!values[i]){ /* if we haven't already */
1042 values[i] = fs_get(strlen(text) + 1);
1043 values[i][0] = '\0'; /* alloc space for it */
1046 if(*p && *p != '\015'){ /* non-blank value. */
1047 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1048 while(*p){ /* check for cont'n lines */
1049 if(*p == '\015' && *(p+1) == '\012'){
1050 if(isspace((unsigned char) *(p+2))){
1051 p += 2;
1052 continue;
1054 else
1055 break;
1058 *t++ = *p++;
1061 *t = '\0';
1064 break;
1067 /* Skip to end of line, what ever it was */
1068 for(; *p ; p++)
1069 if(*p == '\015' && *(p+1) == '\012'){
1070 p += 2;
1071 break;
1077 /*----------------------------------------------------------------------
1078 build a fresh REPLY_S from the given string (see pine_send for format)
1080 Args: s -- "X-Reply-UID" header value
1082 Returns: filled in REPLY_S or NULL on parse error
1083 ----*/
1084 REPLY_S *
1085 build_reply_uid(char *s)
1087 char *p, *prefix = NULL, *val, *seq, *mbox;
1088 int i, nseq, forwarded = 0;
1089 REPLY_S *reply = NULL;
1091 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1092 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1093 if(*s == '('){
1094 if(*(p = s + 1) == ')'){
1095 forwarded = 1;
1097 else{
1098 for(; isdigit(*p); p++)
1101 if(*p == ' '){
1102 *p++ = '\0';
1104 if((i = atoi(s+1)) && i < strlen(p)){
1105 prefix = p;
1106 *(p += i) = '\0';
1109 else
1110 return(NULL);
1113 if(*++p == '(' && *++p){
1114 for(seq = p; isdigit(*p); p++)
1117 if(*p == ' '){
1118 *p++ = '\0';
1119 for(val = p; isdigit(*p); p++)
1122 if(*p == ' '){
1123 *p++ = '\0';
1125 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1126 && (p = strchr(p, ')')) && *(mbox = ++p)){
1127 imapuid_t *uidl;
1129 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1130 for(i = 0; i < nseq; i++)
1131 if((p = strchr(seq,',')) != NULL){
1132 *p = '\0';
1133 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1134 seq = ++p;
1135 else
1136 break;
1138 else if((p = strchr(seq, ')')) != NULL){
1139 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1140 i++;
1142 break;
1145 if(i == nseq){
1146 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1147 memset(reply, 0, sizeof(REPLY_S));
1148 reply->uid = 1;
1149 reply->data.uid.validity = strtoul(val, NULL, 10);
1150 if(forwarded)
1151 reply->forwarded = 1;
1152 else
1153 reply->prefix = cpystr(prefix);
1155 reply->mailbox = cpystr(mbox);
1156 uidl[nseq] = 0;
1157 reply->data.uid.msgs = uidl;
1159 else
1160 fs_give((void **) &uidl);
1167 return(reply);
1172 * pine_new_env - allocate a new METAENV and fill it in.
1174 METAENV *
1175 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1177 int cnt, i, stdcnt;
1178 char *p;
1179 PINEFIELD *pfields, *pf, **sending_order;
1180 METAENV *header;
1182 header = (METAENV *) fs_get(sizeof(METAENV));
1184 /* how many fields are there? */
1185 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1188 stdcnt = cnt;
1190 for(pf = custom; pf; pf = pf->next)
1191 cnt++;
1193 /* temporary PINEFIELD array */
1194 i = (cnt + 1) * sizeof(PINEFIELD);
1195 pfields = (PINEFIELD *)fs_get((size_t) i);
1196 memset(pfields, 0, (size_t) i);
1198 i = (cnt + 1) * sizeof(PINEFIELD *);
1199 sending_order = (PINEFIELD **)fs_get((size_t) i);
1200 memset(sending_order, 0, (size_t) i);
1202 header->env = outgoing;
1203 header->local = pfields;
1204 header->custom = custom;
1205 header->sending_order = sending_order;
1207 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1208 # define NN 4
1209 #else
1210 # define NN 3
1211 #endif
1213 /* initialize pfield */
1214 pf = pfields;
1215 for(i=0; i < stdcnt; i++, pf++){
1217 pf->name = cpystr(pf_template[i].name);
1218 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global)){
1219 /* slide string over so it is Sender instead of X-X-Sender */
1220 for(p=pf->name+4; *p != '\0'; p++)
1221 *(p-4) = *p;
1222 *(p-4) = '\0';
1225 pf->type = pf_template[i].type;
1226 pf->canedit = pf_template[i].canedit;
1227 pf->rcptto = pf_template[i].rcptto;
1228 pf->writehdr = pf_template[i].writehdr;
1229 pf->localcopy = pf_template[i].localcopy;
1230 pf->extdata = NULL; /* unused */
1231 pf->next = pf + 1;
1233 switch(pf->type){
1234 case FreeText:
1235 switch(i){
1236 case N_AUTHRCVD:
1237 sending_order[0] = pf;
1238 break;
1240 case N_NEWS:
1241 pf->text = &outgoing->newsgroups;
1242 sending_order[1] = pf;
1243 break;
1245 case N_DATE:
1246 pf->text = (char **) &outgoing->date;
1247 sending_order[2] = pf;
1248 break;
1250 case N_INREPLY:
1251 pf->text = &outgoing->in_reply_to;
1252 sending_order[NN+9] = pf;
1253 break;
1255 case N_MSGID:
1256 pf->text = &outgoing->message_id;
1257 sending_order[NN+10] = pf;
1258 break;
1260 case N_REF: /* won't be used here */
1261 sending_order[NN+11] = pf;
1262 break;
1264 case N_PRIORITY:
1265 sending_order[NN+12] = pf;
1266 break;
1268 case N_USERAGENT:
1269 pf->text = &pf->textbuf;
1270 pf->textbuf = generate_user_agent();
1271 sending_order[NN+13] = pf;
1272 break;
1274 case N_POSTERR: /* won't be used here */
1275 sending_order[NN+14] = pf;
1276 break;
1278 case N_RPLUID: /* won't be used here */
1279 sending_order[NN+15] = pf;
1280 break;
1282 case N_RPLMBOX: /* won't be used here */
1283 sending_order[NN+16] = pf;
1284 break;
1286 case N_SMTP: /* won't be used here */
1287 sending_order[NN+17] = pf;
1288 break;
1290 case N_NNTP: /* won't be used here */
1291 sending_order[NN+18] = pf;
1292 break;
1294 case N_CURPOS: /* won't be used here */
1295 sending_order[NN+19] = pf;
1296 break;
1298 case N_OURREPLYTO: /* won't be used here */
1299 sending_order[NN+20] = pf;
1300 break;
1302 case N_OURHDRS: /* won't be used here */
1303 sending_order[NN+21] = pf;
1304 break;
1306 default:
1307 q_status_message1(SM_ORDER,3,3,
1308 "Internal error: 1)FreeText header %s", comatose(i));
1309 break;
1312 break;
1314 case Attachment:
1315 break;
1317 case Address:
1318 switch(i){
1319 case N_FROM:
1320 sending_order[3] = pf;
1321 pf->addr = &outgoing->from;
1322 break;
1324 case N_TO:
1325 sending_order[NN+2] = pf;
1326 pf->addr = &outgoing->to;
1327 if(tobufpp)
1328 (*tobufpp) = &pf->scratch;
1330 break;
1332 case N_CC:
1333 sending_order[NN+3] = pf;
1334 pf->addr = &outgoing->cc;
1335 break;
1337 case N_BCC:
1338 sending_order[NN+4] = pf;
1339 pf->addr = &outgoing->bcc;
1340 break;
1342 case N_REPLYTO:
1343 sending_order[NN+1] = pf;
1344 pf->addr = &outgoing->reply_to;
1345 break;
1347 case N_LCC: /* won't be used here */
1348 sending_order[NN+7] = pf;
1349 break;
1351 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1352 case N_SENDER:
1353 sending_order[4] = pf;
1354 pf->addr = &outgoing->sender;
1355 break;
1356 #endif
1358 case N_NOBODY: /* won't be used here */
1359 sending_order[NN+5] = pf;
1360 break;
1362 default:
1363 q_status_message1(SM_ORDER,3,3,
1364 "Internal error: Address header %s", comatose(i));
1365 break;
1367 break;
1369 case Fcc:
1370 sending_order[NN+8] = pf;
1371 pf->text = fccp;
1372 break;
1374 case Subject:
1375 sending_order[NN+6] = pf;
1376 pf->text = &outgoing->subject;
1377 break;
1379 default:
1380 q_status_message1(SM_ORDER,3,3,
1381 "Unknown header type %d in pine_new_send", (void *)pf->type);
1382 break;
1386 if(((--pf)->next = custom) != NULL){
1387 i--;
1390 * NOTE: "i" is assumed to now index first custom field in sending
1391 * order.
1393 for(pf = pf->next; pf && pf->name; pf = pf->next){
1394 if(pf->standard)
1395 continue;
1397 pf->canedit = 1;
1398 pf->rcptto = 0;
1399 pf->writehdr = 1;
1400 pf->localcopy = 1;
1402 switch(pf->type){
1403 case Address:
1404 if(pf->addr){ /* better be set */
1405 char *addr = NULL;
1406 BuildTo bldto;
1408 bldto.type = Str;
1409 bldto.arg.str = pf->textbuf;
1411 sending_order[i++] = pf;
1412 /* change default text into an ADDRESS */
1413 /* strip quotes around whole default */
1414 removing_trailing_white_space(pf->textbuf);
1415 (void)removing_double_quotes(pf->textbuf);
1416 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1417 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1418 fs_give((void **)&addr);
1419 if(pf->textbuf)
1420 fs_give((void **)&pf->textbuf);
1423 break;
1425 case FreeText:
1426 sending_order[i++] = pf;
1427 pf->text = &pf->textbuf;
1428 break;
1430 default:
1431 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1432 (void *)pf->type);
1433 break;
1439 return(header);
1443 void
1444 pine_free_env(METAENV **menv)
1446 int cnt;
1449 if((*menv)->local){
1450 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1453 for(; cnt >= 0; cnt--){
1454 if((*menv)->local[cnt].textbuf)
1455 fs_give((void **) &(*menv)->local[cnt].textbuf);
1457 fs_give((void **) &(*menv)->local[cnt].name);
1460 fs_give((void **) &(*menv)->local);
1463 if((*menv)->sending_order)
1464 fs_give((void **) &(*menv)->sending_order);
1466 fs_give((void **) menv);
1470 /*----------------------------------------------------------------------
1471 Check for addresses the user is not permitted to send to, or probably
1472 doesn't want to send to
1474 Returns: 0 if OK
1475 1 if there are only empty groups
1476 -1 if the message shouldn't be sent
1478 Queues a message indicating what happened
1479 ---*/
1481 check_addresses(METAENV *header)
1483 PINEFIELD *pf;
1484 ADDRESS *a;
1485 int send_daemon = 0, rv = CA_EMPTY;
1487 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1488 for(pf = header->local; pf && pf->name; pf = pf->next)
1489 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1490 for(a = *pf->addr; a != NULL; a = a->next){
1491 if(a->host && (a->host[0] == '.'
1492 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1493 && a->host[0] == '@'))){
1494 q_status_message2(SM_ORDER, 4, 7,
1495 /* TRANSLATORS: First arg is the address we can't
1496 send to, second arg is "not in addressbook". */
1497 _("Can't send to address %s: %s"),
1498 a->mailbox,
1499 (a->host[0] == '.')
1500 ? a->host
1501 : _("not in addressbook"));
1502 return(CA_BAD);
1504 else if(ps_global->restricted
1505 && !address_is_us(*pf->addr, ps_global)){
1506 q_status_message(SM_ORDER, 3, 3,
1507 "Restricted demo version of Alpine. You may only send mail to yourself");
1508 return(CA_BAD);
1510 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1511 send_daemon = 1;
1512 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1514 else if(a->mailbox && a->host){
1515 rv = CA_OK;
1519 return(rv);
1524 * If this isn't general enough we can modify it. The value passed in
1525 * is expected to be one of the desc settings from the priorities array,
1526 * like "High". The header value is X-Priority: 2 (High)
1527 * or something similar. If value doesn't match any of the values then
1528 * the actual value is used instead.
1530 PINEFIELD *
1531 set_priority_header(METAENV *header, char *value)
1533 PINEFIELD *pf;
1535 for(pf = header->local; pf && pf->name; pf = pf->next)
1536 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1537 break;
1539 if(pf){
1540 if(pf->textbuf)
1541 fs_give((void **) &pf->textbuf);
1543 if(value){
1544 PRIORITY_S *p;
1546 for(p = priorities; p && p->desc; p++)
1547 if(!strcmp(p->desc, value))
1548 break;
1550 if(p && p->desc){
1551 char buf[100];
1553 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1554 pf->textbuf = cpystr(buf);
1556 else
1557 pf->textbuf = cpystr(value);
1560 return pf;
1564 /*----------------------------------------------------------------------
1565 Set answered flags for messages specified by reply structure
1567 Args: reply --
1569 Returns: with appropriate flags set and index cache entries suitably tweeked
1570 ----*/
1571 void
1572 update_answered_flags(REPLY_S *reply)
1574 char *seq = NULL, *p;
1575 long i, ourstream = 0, we_cancel = 0;
1576 MAILSTREAM *stream = NULL;
1578 /* nothing to flip in a pseudo reply */
1579 if(reply && (reply->msgno || reply->uid)){
1580 int j;
1581 MAILSTREAM *m;
1584 * If an established stream will do, use it, else
1585 * build one unless we have an array of msgno's...
1587 * I was just mimicking what was already here. I don't really
1588 * understand why we use strcmp instead of same_stream_and_mailbox().
1589 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1590 * Hubert 2003-07-09
1592 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1593 m = ps_global->s_pool.streams[j];
1594 if(m && reply->mailbox && m->mailbox
1595 && !strcmp(reply->mailbox, m->mailbox))
1596 stream = m;
1599 if(!stream && reply->msgno)
1600 return;
1602 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1603 we_cancel = reply->forwarded
1604 ? busy_cue(_("Setting \"Forwarded\" Keyword"), NULL, 0)
1605 : busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1606 if(!stream){
1607 if((stream = pine_mail_open(NULL,
1608 reply->origmbox ? reply->origmbox
1609 : reply->mailbox,
1610 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1611 NULL)) != NULL){
1612 ourstream++;
1614 else{
1615 if(we_cancel)
1616 cancel_busy_cue(0);
1618 return;
1622 if(stream->uid_validity == reply->data.uid.validity){
1623 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1624 if(i){
1625 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1626 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1629 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1630 SIZEOF_20KBUF-(p-tmp_20k_buf));
1631 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1634 if(reply->forwarded){
1636 * $Forwarded is a regular keyword so we only try to
1637 * set it if the stream allows keywords.
1638 * We could mess up if the stream has keywords but just
1639 * isn't allowing anymore and $Forwarded already exists,
1640 * but what are the odds?
1642 if(stream && stream->kwd_create)
1643 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1644 FORWARDED_FLAG,
1645 ST_SET | ((reply->uid) ? ST_UID : 0L));
1647 else
1648 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1649 "\\ANSWERED",
1650 ST_SET | ((reply->uid) ? ST_UID : 0L));
1652 if(seq)
1653 fs_give((void **)&seq);
1656 if(ourstream)
1657 pine_mail_close(stream); /* clean up dangling stream */
1659 if(we_cancel)
1660 cancel_busy_cue(0);
1665 /*----------------------------------------------------------------------
1666 Call the mailer, SMTP, sendmail or whatever
1668 Args: header -- full header (envelope and local parts) of message to send
1669 body -- The full body of the message including text
1670 alt_smtp_servers --
1671 verbosefile -- non-null means caller wants verbose interaction and the resulting
1672 output file name to be returned
1674 Returns: -1 if failed, 1 if succeeded
1675 ----*/
1677 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1678 int flags, void (*bigresult_f)(char *, int),
1679 void (*pipecb_f)(PIPE_S *, int, void *))
1681 char error_buf[200], *error_mess = NULL, *postcmd;
1682 ADDRESS *a;
1683 ENVELOPE *fake_env = NULL;
1684 int addr_error_count, we_cancel = 0;
1685 long smtp_opts = 0L;
1686 char *verbose_file = NULL;
1687 BODY *bp = NULL;
1688 PINEFIELD *pf;
1689 BODY *origBody = body;
1691 dprint((4, "Sending mail...\n"));
1693 /* Check for any recipients */
1694 for(pf = header->local; pf && pf->name; pf = pf->next)
1695 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1696 break;
1698 if(!pf){
1699 q_status_message(SM_ORDER,3,3,
1700 _("Can't send message. No recipients specified!"));
1701 return(0);
1704 #ifdef SMIME
1705 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1706 int result;
1708 STORE_S *so = lmc.so;
1709 lmc.so = NULL;
1711 result = 1;
1713 if(ps_global->smime->do_sign){
1714 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1715 result = sign_outgoing_message(header, &body, 0, &bp);
1718 /* need to free new body from encrypt if sign fails? */
1719 if(result && ps_global->smime->do_encrypt)
1720 result = encrypt_outgoing_message(header, &body);
1722 lmc.so = so;
1724 if(!result)
1725 return 0;
1727 #endif
1729 /* set up counts and such to keep track sent percentage */
1730 send_bytes_sent = 0;
1731 gf_filter_init(); /* zero piped byte count, 'n */
1732 send_bytes_to_send = send_body_size(body); /* count body bytes */
1733 ps_global->c_client_error[0] = error_buf[0] = '\0';
1734 we_cancel = busy_cue(_("Sending mail"),
1735 send_bytes_to_send ? sent_percent : NULL, 0);
1737 #ifndef _WINDOWS
1739 /* try posting via local "<mta> <-t>" if specified */
1740 if(mta_handoff(header, body, error_buf, sizeof(error_buf), alt_smtp_servers, bigresult_f, pipecb_f)){
1741 if(error_buf[0])
1742 error_mess = error_buf;
1744 goto done;
1747 #endif
1750 * If the user's asked for it, and we find that the first text
1751 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1753 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1754 smtp_opts |= SOP_8BITMIME;
1756 #ifdef DEBUG
1757 #ifndef DEBUGJOURNAL
1758 if(debug > 5 || (flags & CM_VERBOSE))
1759 #endif
1760 smtp_opts |= SOP_DEBUG;
1761 #endif
1763 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1764 smtp_opts |= SOP_DSN;
1765 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1766 if(flags & CM_DSN_DELAY)
1767 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1768 if(flags & CM_DSN_SUCCESS)
1769 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1772 * If it isn't Never, then we're always going to let them
1773 * know about failures. This means we don't allow for the
1774 * possibility of setting delay or success without failure.
1776 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1778 if(flags & CM_DSN_FULL)
1779 smtp_opts |= SOP_DSN_RETURN_FULL;
1785 * Set global header pointer so post_rfc822_output can get at it when
1786 * it's called back from c-client's sending routine...
1788 send_header = header;
1791 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1792 * The purpose is to give smtp_mail the list for SMTP RCPT when
1793 * there are recipients in pine's METAENV that are outside c-client's
1794 * envelope.
1796 * NOTE: If there aren't any, don't bother. Dealt with it below.
1798 for(pf = header->local; pf && pf->name; pf = pf->next)
1799 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1800 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1801 || *pf->addr == header->env->bcc))
1802 break;
1804 if(pf && pf->name){
1805 ADDRESS **tail;
1807 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1808 memset(fake_env, 0, sizeof(ENVELOPE));
1809 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1810 tail = &(fake_env->to);
1811 for(pf = header->local; pf && pf->name; pf = pf->next)
1812 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1813 *tail = rfc822_cpy_adr(*pf->addr);
1814 while(*tail)
1815 tail = &((*tail)->next);
1820 * Install our rfc822 output routine
1822 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1823 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1826 * Allow for verbose posting
1828 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1829 (void *) pine_smtp_verbose_out);
1832 * We do this because we want mm_log to put the error message into
1833 * c_client_error instead of showing it itself.
1835 ps_global->noshow_error = 1;
1838 * OK, who posts what? We tried an mta_handoff above, but there
1839 * was either none specified or we decided not to use it. So,
1840 * if there's an smtp-server defined anywhere,
1842 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1843 /*---------- SMTP ----------*/
1844 dprint((4, "call_mailer: via TCP (%s)\n",
1845 alt_smtp_servers[0]));
1846 TIME_STAMP("smtp-open start (tcp)", 1);
1847 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1849 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1850 && ps_global->VAR_SMTP_SERVER[0][0]){
1851 /*---------- SMTP ----------*/
1852 dprint((4, "call_mailer: via TCP\n"));
1853 TIME_STAMP("smtp-open start (tcp)", 1);
1854 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1856 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1857 char *cmdlist[2];
1859 /*----- Send via LOCAL SMTP agent ------*/
1860 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1862 TIME_STAMP("smtp-open start (pipe)", 1);
1863 fs_give((void **) &postcmd);
1864 cmdlist[0] = "localhost";
1865 cmdlist[1] = NULL;
1866 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1867 SMTPTCPPORT, smtp_opts);
1868 /* BUG: should provide separate stderr output! */
1871 ps_global->noshow_error = 0;
1873 TIME_STAMP("smtp open", 1);
1874 if(sending_stream){
1875 unsigned short save_encoding = 0, added_encoding = 0;
1877 dprint((1, "Opened SMTP server \"%s\"\n",
1878 net_host(sending_stream->netstream)
1879 ? net_host(sending_stream->netstream) : "?"));
1881 if(flags & CM_VERBOSE){
1882 TIME_STAMP("verbose start", 1);
1883 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1884 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1885 if(!smtp_verbose(sending_stream)){
1886 snprintf(error_mess = error_buf, sizeof(error_buf),
1887 "Mail not sent. VERBOSE mode error%s%.50s.",
1888 (sending_stream && sending_stream->reply)
1889 ? ": ": "",
1890 (sending_stream && sending_stream->reply)
1891 ? sending_stream->reply : "");
1892 error_buf[sizeof(error_buf)-1] = '\0';
1895 else{
1896 our_unlink(verbose_file);
1897 strncpy(error_mess = error_buf,
1898 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1899 error_buf[sizeof(error_buf)-1] = '\0';
1902 else{
1903 strncpy(error_mess = error_buf,
1904 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1905 error_buf[sizeof(error_buf)-1] = '\0';
1908 TIME_STAMP("verbose end", 1);
1912 * Before we actually send data, see if we have to protect
1913 * the first text body part from getting encoded. We protect
1914 * it from getting encoded in "pine_rfc822_output_body" by
1915 * temporarily inventing a synonym for ENC8BIT...
1916 * This works like so:
1917 * Suppose bp->encoding is set to ENC8BIT.
1918 * We change that here to some unused value (added_encoding) and
1919 * set body_encodings[added_encoding] to "8BIT".
1920 * Then post_rfc822_output is called which calls
1921 * pine_rfc822_output_body. Inside that routine
1922 * pine_write_body_header writes out the encoding for the
1923 * part. Normally it would see encoding == ENC8BIT and it would
1924 * change that to QUOTED-PRINTABLE, but since encoding has been
1925 * set to added_encoding it uses body_encodings[added_encoding]
1926 * which is "8BIT" instead. Then the actual body is written by
1927 * pine_write_body_header which does not do the gf_8bit_qp
1928 * filtering because encoding != ENC8BIT (instead it's equal
1929 * to added_encoding).
1931 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1932 && sending_stream->protocol.esmtp.eightbit.want){
1933 int i;
1935 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1938 if(i > ENCMAX){ /* no empty encoding slots! */
1939 bp = NULL;
1941 else {
1942 added_encoding = i;
1943 body_encodings[added_encoding] = body_encodings[ENC8BIT];
1944 save_encoding = bp->encoding;
1945 bp->encoding = added_encoding;
1946 #ifdef SMIME
1947 if(ps_global->smime && ps_global->smime->do_sign
1948 && body->nested.part->next
1949 && body->nested.part->next->body.contents.text.data
1950 && body->nested.part->next->body.mime.text.data){
1951 STORE_S *so;
1953 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
1954 so_give(&so);
1955 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
1956 body->nested.part->next->body.mime.text.data = NULL;
1958 #endif /* SMIME */
1962 if(sending_stream->protocol.esmtp.ok
1963 && sending_stream->protocol.esmtp.dsn.want
1964 && !sending_stream->protocol.esmtp.dsn.ok)
1965 q_status_message(SM_ORDER,3,3,
1966 _("Delivery Status Notification not available from this server."));
1968 TIME_STAMP("smtp start", 1);
1969 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
1970 fake_env ? fake_env : header->env, body)){
1972 snprintf(error_buf, sizeof(error_buf),
1973 _("Mail not sent. Sending error%s%s"),
1974 (sending_stream && sending_stream->reply) ? ": ": ".",
1975 (sending_stream && sending_stream->reply)
1976 ? sending_stream->reply : "");
1977 error_buf[sizeof(error_buf)-1] = '\0';
1978 dprint((1, error_buf));
1979 addr_error_count = 0;
1980 if(fake_env){
1981 for(a = fake_env->to; a != NULL; a = a->next)
1982 if(a->error != NULL){
1983 if(addr_error_count++ < MAX_ADDR_ERROR){
1986 * Too complicated to figure out which header line
1987 * has the error in the fake_env case, so just
1988 * leave cursor at default.
1992 if(error_mess) /* previous error? */
1993 q_status_message(SM_ORDER, 4, 7, error_mess);
1995 error_mess = tidy_smtp_mess(a->error,
1996 _("Mail not sent: %.80s"),
1997 error_buf, sizeof(error_buf));
2000 dprint((1, "Send Error: \"%s\"\n",
2001 a->error));
2004 else{
2005 for(pf = header->local; pf && pf->name; pf = pf->next)
2006 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2007 for(a = *pf->addr; a != NULL; a = a->next)
2008 if(a->error != NULL){
2009 if(addr_error_count++ < MAX_ADDR_ERROR){
2011 if(error_mess) /* previous error? */
2012 q_status_message(SM_ORDER, 4, 7, error_mess);
2014 error_mess = tidy_smtp_mess(a->error,
2015 _("Mail not sent: %.80s"),
2016 error_buf, sizeof(error_buf));
2019 dprint((1, "Send Error: \"%s\"\n",
2020 a->error));
2024 if(!error_mess)
2025 error_mess = error_buf;
2028 /* repair modified "body_encodings" array? */
2029 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2030 && sending_stream->protocol.esmtp.eightbit.want){
2031 body_encodings[added_encoding] = NULL;
2032 bp->encoding = save_encoding;
2035 TIME_STAMP("smtp closing", 1);
2036 smtp_close(sending_stream);
2037 sending_stream = NULL;
2038 TIME_STAMP("smtp done", 1);
2040 else if(!error_mess){
2041 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2042 ps_global->c_client_error[0] ? ": " : "",
2043 ps_global->c_client_error);
2044 error_buf[sizeof(error_buf)-1] = '\0';
2047 if(verbose_file){
2048 if(verbose_send_output){
2049 TIME_STAMP("verbose start", 1);
2050 fclose(verbose_send_output);
2051 verbose_send_output = NULL;
2052 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2054 if(bigresult_f)
2055 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2057 TIME_STAMP("verbose end", 1);
2060 fs_give((void **)&verbose_file);
2064 * Restore original 822 emitter...
2066 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2068 if(fake_env)
2069 mail_free_envelope(&fake_env);
2071 done:
2073 #ifdef SMIME
2074 /* Free replacement encrypted body */
2075 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2077 if(body->type == TYPEMULTIPART){
2078 /* Just get rid of first part, it's actually origBody */
2079 void *x = body->nested.part;
2081 body->nested.part = body->nested.part->next;
2083 fs_give(&x);
2086 pine_free_body(&body);
2088 #endif
2090 if(we_cancel)
2091 cancel_busy_cue(0);
2093 TIME_STAMP("call_mailer done", 1);
2094 /*-------- Did message make it ? ----------*/
2095 if(error_mess){
2096 /*---- Error sending mail -----*/
2097 if(lmc.so && !lmc.all_written)
2098 so_give(&lmc.so);
2100 if(error_mess){
2101 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2102 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2105 return(-1);
2107 else{
2108 lmc.all_written = 1;
2109 return(1);
2115 * write_postponed - exported method to write the given message
2116 * to the postponed folder
2119 write_postponed(METAENV *header, struct mail_bodystruct *body)
2121 char **pp, *folder;
2122 int rv = 0, sz;
2123 CONTEXT_S *fcc_cntxt = NULL;
2124 PINEFIELD *pf;
2125 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2127 if(!ps_global->VAR_POSTPONED_FOLDER
2128 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2129 q_status_message(SM_ORDER | SM_DING, 3, 3,
2130 _("No postponed file defined"));
2131 return(-1);
2134 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2136 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2138 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2140 if(lmc.so){
2141 /* BUG: writem sufficient ? */
2142 for(pf = header->local; pf && pf->name; pf = pf->next)
2143 for(pp = writem; *pp; pp++)
2144 if(!strucmp(pf->name, *pp)){
2145 pf->localcopy = 1;
2146 pf->writehdr = 1;
2147 break;
2151 * Work around c-client reply-to bug. C-client will
2152 * return a reply_to in an envelope even if there is
2153 * no reply-to header field. We want to note here whether
2154 * the reply-to is real or not.
2156 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2157 for(pf = header->local; pf; pf = pf->next)
2158 if(!strcmp(pf->name, "Reply-To")){
2159 pf->writehdr = 1;
2160 pf->localcopy = 1;
2161 if(header->env->reply_to)
2162 pf->textbuf = cpystr("Full");
2163 else
2164 pf->textbuf = cpystr("Empty");
2168 * Write the list of custom headers to the
2169 * X-Our-Headers header so that we can recover the
2170 * list in redraft.
2172 sz = 0;
2173 for(pf = header->custom; pf && pf->name; pf = pf->next)
2174 sz += strlen(pf->name) + 1;
2176 if(sz){
2177 int i;
2178 char *pstart, *pend;
2180 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2183 pf->writehdr = 1;
2184 pf->localcopy = 1;
2185 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2186 pf->text = &pf->textbuf;
2187 pf->textbuf[sz] = '\0'; /* tie off overflow */
2188 /* note: "pf" overloaded */
2189 for(pf = header->custom; pf && pf->name; pf = pf->next){
2190 int r = sz - (pend - pstart); /* remaining buffer */
2192 if(r > 0 && r != sz){
2193 r--;
2194 *pend++ = ',';
2197 sstrncpy(&pend, pf->name, r);
2201 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2202 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2203 rv = -1;
2205 so_give(&lmc.so);
2207 else {
2208 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2209 "Can't allocate internal storage: %s ",
2210 error_description(errno));
2211 rv = -1;
2214 fs_give((void **) &folder);
2215 return(rv);
2220 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2222 if(fcc && *fcc){
2223 lmc.all_written = lmc.text_written = 0;
2224 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2225 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2227 else
2228 lmc.so = NULL;
2230 return(TRUE);
2235 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2237 int rv = TRUE;
2239 if(lmc.so){
2240 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2241 char label[50];
2243 strncpy(label, "Fcc", sizeof(label));
2244 label[sizeof(label)-1] = '\0';
2245 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2246 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2247 label[sizeof(label)-1] = '\0';
2250 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2252 else{
2253 rv = FALSE;
2256 so_give(&lmc.so);
2259 return(rv);
2263 /*----------------------------------------------------------------------
2264 Checks to make sure the fcc is available and can be opened
2266 Args: fcc -- the name of the fcc to create. It can't be NULL.
2267 fcc_cntxt -- Returns the context the fcc is in.
2268 force -- suppress user option prompt
2270 Returns allocated storage object on success, NULL on failure
2271 ----*/
2272 STORE_S *
2273 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2275 int exists, ok = 0;
2277 ps_global->mm_log_error = 0;
2280 * check for fcc's existence...
2282 TIME_STAMP("open_fcc start", 1);
2283 if(!is_absolute_path(fcc) && context_isambig(fcc)
2284 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2285 int flip_dot = 0;
2288 * Don't want to preclude a user from Fcc'ing a .name'd folder
2290 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2291 flip_dot = 1;
2292 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2296 * We only want to set the "context" if fcc is an ambiguous
2297 * name. Otherwise, our "relativeness" rules for contexts
2298 * (implemented in context.c) might cause the name to be
2299 * interpreted in the wrong context...
2301 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2302 *fcc_cntxt = ps_global->context_list;
2304 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2305 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2306 if(force
2307 || (pith_opt_save_create_prompt
2308 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2310 ps_global->noshow_error = 1;
2312 if(context_create(*fcc_cntxt, NULL, fcc))
2313 ok++;
2315 ps_global->noshow_error = 0;
2317 else
2318 ok--; /* declined! */
2320 else
2321 ok++; /* found! */
2323 if(flip_dot)
2324 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2326 free_folder_list(*fcc_cntxt);
2328 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2329 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2330 ok++;
2332 else{
2333 if(force
2334 || (pith_opt_save_create_prompt
2335 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2337 ps_global->mm_log_error = 0;
2338 ps_global->noshow_error = 1;
2340 ok = pine_mail_create(NULL, fcc) != 0L;
2342 ps_global->noshow_error = 0;
2344 else
2345 ok--; /* declined! */
2349 TIME_STAMP("open_fcc done.", 1);
2350 if(ok > 0){
2351 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2353 else{
2354 int l1, l2, l3, wid, w;
2355 char *errstr, tmp[MAILTMPLEN];
2356 char *s1, *s2;
2358 if(ok == 0){
2359 if(ps_global->mm_log_error){
2360 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2361 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2363 l1 = strlen(s1);
2364 l2 = strlen(s2);
2365 l3 = strlen(ps_global->c_client_error);
2366 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2367 ? ps_global->ttyo->screen_cols : 80;
2368 w = wid - l1 - l2 - 5;
2370 snprintf(errstr = tmp, sizeof(tmp),
2371 "%.99s\"%.*s%.99s\".%.99s",
2373 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2374 ps_global->c_client_error,
2375 (l3 > w) ? "..." : "",
2376 s2);
2377 tmp[sizeof(tmp)-1] = '\0';
2380 else
2381 errstr = _("Fcc creation error. Message NOT sent or copied.");
2383 else
2384 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2386 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2389 return(NULL);
2393 /*----------------------------------------------------------------------
2394 mail_append() the fcc accumulated in temp_storage to proper destination
2396 Args: fcc -- name of folder
2397 fcc_cntxt -- context for folder
2398 temp_storage -- String of file where Fcc has been accumulated
2400 This copies the string of file to the actual folder, which might be IMAP
2401 or a disk folder. The temp_storage is freed after it is written.
2402 An error message is produced if this fails.
2403 ----*/
2405 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2406 MAILSTREAM *stream, char *label, char *flags)
2408 STRING msg;
2409 CONTEXT_S *cntxt;
2410 int we_cancel = 0;
2412 if(!tmp_storage)
2413 return(0);
2415 TIME_STAMP("write_fcc start.", 1);
2416 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2417 if(label && *label){
2418 char msg_buf[80];
2420 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2421 msg_buf[sizeof(msg_buf)-1] = '\0';
2422 strncat(msg_buf, label, sizeof(msg_buf)-10);
2423 we_cancel = busy_cue(msg_buf, NULL, 0);
2425 else
2426 we_cancel = busy_cue(NULL, NULL, 1);
2428 so_seek(tmp_storage, 0L, 0);
2431 * Before changing this note that these lines depend on the
2432 * definition of FCC_SOURCE.
2434 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2435 strlen((char *)so_text(tmp_storage)));
2437 cntxt = fcc_cntxt;
2439 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2440 cancel_busy_cue(-1);
2441 we_cancel = 0;
2443 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2444 "Write to \"%s\" FAILED!!!", fcc);
2445 dprint((1, "ERROR appending %s in \"%s\"",
2446 fcc ? fcc : "?",
2447 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2448 return(0);
2451 if(we_cancel)
2452 cancel_busy_cue(label ? 0 : -1);
2454 dprint((4, "done.\n"));
2455 TIME_STAMP("write_fcc done.", 1);
2456 return(1);
2461 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2462 * contained.
2464 BODY *
2465 first_text_8bit(struct mail_bodystruct *body)
2467 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2468 body = &body->nested.part->body;
2470 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2471 ? body : NULL);
2476 * Build and return the "From:" address for outbound messages from
2477 * global data...
2479 ADDRESS *
2480 generate_from(void)
2482 ADDRESS *addr = mail_newaddr();
2483 if(ps_global->VAR_PERSONAL_NAME){
2484 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2485 removing_leading_and_trailing_white_space(addr->personal);
2486 if(addr->personal[0] == '\0')
2487 fs_give((void **)&addr->personal);
2490 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2491 addr->host = cpystr(ps_global->maildomain);
2492 removing_leading_and_trailing_white_space(addr->mailbox);
2493 removing_leading_and_trailing_white_space(addr->host);
2494 return(addr);
2499 * set_mime_type_by_grope - sniff the given storage object to determine its
2500 * type, subtype, encoding, and charset
2502 * "Type" and "encoding" must be set before calling this routine.
2503 * If "type" is set to something other than TYPEOTHER on entry,
2504 * then that is the "type" we wish to use. Same for "encoding"
2505 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2506 * figure them out here. If "type" is already set, we also
2507 * leave subtype alone. If not, we figure out subtype here.
2508 * There is a chance that we will upgrade encoding to a "higher"
2509 * level. For example, if it comes in as 7BIT we may change
2510 * that to 8BIT if we find a From_ we want to escape.
2511 * We may also set the charset attribute if the type is TEXT.
2513 * NOTE: this is rather inefficient if the store object is a CharStar
2514 * but the win is all types are handled the same
2516 void
2517 set_mime_type_by_grope(struct mail_bodystruct *body)
2519 #define RBUFSZ (8193)
2520 unsigned char *buf, *p, *bol;
2521 register size_t n;
2522 long max_line = 0L,
2523 eight_bit_chars = 0L,
2524 line_so_far = 0L,
2525 len = 0L;
2526 STORE_S *so = (STORE_S *)body->contents.text.data;
2527 unsigned short new_encoding = ENCOTHER;
2528 int we_cancel = 0;
2529 #ifdef ENCODE_FROMS
2530 short froms = 0, dots = 0,
2531 bmap = 0x1, dmap = 0x1;
2532 #endif
2534 we_cancel = busy_cue(NULL, NULL, 1);
2536 buf = (unsigned char *)fs_get(RBUFSZ);
2537 so_seek(so, 0L, 0);
2539 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2542 buf[n] = '\0';
2544 if(n){ /* check first few bytes to look for magic numbers */
2545 if(body->type == TYPEOTHER){
2546 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2547 body->type = TYPEIMAGE;
2548 body->subtype = cpystr("GIF");
2550 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2551 && buf[2] == 0xFF && buf[3] == 0xE0
2552 && !strncmp((char *)&buf[6], "JFIF", 4)){
2553 body->type = TYPEIMAGE;
2554 body->subtype = cpystr("JPEG");
2556 else if((buf[0] == 'M' && buf[1] == 'M')
2557 || (buf[0] == 'I' && buf[1] == 'I')){
2558 body->type = TYPEIMAGE;
2559 body->subtype = cpystr("TIFF");
2561 else if((buf[0] == '%' && buf[1] == '!')
2562 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2563 body->type = TYPEAPPLICATION;
2564 body->subtype = cpystr("PostScript");
2566 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2567 body->type = TYPEAPPLICATION;
2568 body->subtype = cpystr("PDF");
2570 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2571 body->type = TYPEAUDIO;
2572 body->subtype = cpystr("Basic");
2574 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2575 && buf[2] == 0x16 && buf[3] == 0x00){
2576 body->type = TYPEAPPLICATION;
2577 body->subtype = cpystr("APPLEFILE");
2579 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2580 && buf[2] == 0x03 && buf[3] == 0x04){
2581 body->type = TYPEAPPLICATION;
2582 body->subtype = cpystr("ZIP");
2586 * if type was set above, but no encoding specified, go
2587 * ahead and make it BASE64...
2589 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2590 body->encoding = ENCBINARY;
2593 else{
2594 /* PROBLEM !!! */
2595 if(body->type == TYPEOTHER){
2596 body->type = TYPEAPPLICATION;
2597 body->subtype = cpystr("octet-stream");
2598 if(body->encoding == ENCOTHER)
2599 body->encoding = ENCBINARY;
2603 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2604 #if defined(DOS) || defined(OS2) /* for binary file detection */
2605 int lastchar = '\0';
2606 #define BREAKOUT 300 /* a value that a character can't be */
2607 #endif
2609 p = bol = buf;
2610 len = n;
2611 while (n--){
2612 /* Some people don't like quoted-printable caused by leading Froms */
2613 #ifdef ENCODE_FROMS
2614 Find_Froms(froms, dots, bmap, dmap, *p);
2615 #endif
2616 if(*p == '\n'){
2617 max_line = MAX(max_line, line_so_far + p - bol);
2618 bol = NULL; /* clear beginning of line */
2619 line_so_far = 0L; /* clear line count */
2620 #if defined(DOS) || defined(OS2)
2621 /* LF with no CR!! */
2622 if(lastchar != '\r') /* must be non-text data! */
2623 lastchar = BREAKOUT;
2624 #endif
2626 else if(*p & 0x80){
2627 eight_bit_chars++;
2629 else if(!*p){
2630 /* NULL found. Unless we're told otherwise, must be binary */
2631 if(body->type == TYPEOTHER){
2632 body->type = TYPEAPPLICATION;
2633 body->subtype = cpystr("octet-stream");
2637 * The "TYPETEXT" here handles the case that the NULL
2638 * comes from imported text generated by some external
2639 * editor that permits or inserts NULLS. Otherwise,
2640 * assume it's a binary segment...
2642 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2645 * Since we've already set encoding, count this as a
2646 * hi bit char and continue. The reason is that if this
2647 * is text, there may be a high percentage of encoded
2648 * characters, so base64 may get set below...
2650 if(body->type == TYPETEXT)
2651 eight_bit_chars++;
2652 else
2653 break;
2656 #if defined(DOS) || defined(OS2) /* for binary file detection */
2657 if(lastchar != BREAKOUT)
2658 lastchar = *p;
2659 #endif
2661 /* read another buffer in */
2662 if(n == 0){
2663 if(bol)
2664 line_so_far += p - bol;
2666 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2669 len += n;
2670 p = buf;
2672 else
2673 p++;
2676 * If there's no beginning-of-line pointer, then we must
2677 * have seen an end-of-line. Set bol to the start of the
2678 * new line...
2680 if(!bol)
2681 bol = p;
2683 #if defined(DOS) || defined(OS2) /* for binary file detection */
2684 /* either a lone \r or lone \n indicate binary file */
2685 if(lastchar == '\r' || lastchar == BREAKOUT){
2686 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2687 if(body->type == TYPEOTHER){
2688 body->type = TYPEAPPLICATION;
2689 body->subtype = cpystr("octet-stream");
2692 new_encoding = ENCBINARY;
2693 break;
2696 #endif
2700 /* stash away for later */
2701 so_attr(so, "maxline", long2string(max_line));
2703 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2705 * Since the type or encoding aren't set yet, fall thru a
2706 * series of tests to make sure an adequate type and
2707 * encoding are set...
2710 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2711 if(body->type == TYPEOTHER){
2713 * Since the types not set, then we didn't find a NULL.
2714 * If there's no NULL, then this is likely text. However,
2715 * since we can't be *completely* sure, we set it to
2716 * the generic type.
2718 body->type = TYPEAPPLICATION;
2719 body->subtype = cpystr("octet-stream");
2722 if(new_encoding != ENCBINARY)
2724 * As with NULL handling, if we're told it's text,
2725 * qp-encode it, else it gets base 64...
2727 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2730 if(eight_bit_chars == 0L){
2731 if(body->type == TYPEOTHER)
2732 body->type = TYPETEXT;
2734 if(new_encoding == ENCOTHER)
2735 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2737 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2739 * The 30% threshold is based on qp encoded readability
2740 * on non-MIME UA's.
2742 if(body->type == TYPEOTHER)
2743 body->type = TYPETEXT;
2745 if(new_encoding != ENCBINARY)
2746 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2748 else{
2749 if(body->type == TYPEOTHER){
2750 body->type = TYPEAPPLICATION;
2751 body->subtype = cpystr("octet-stream");
2755 * Apply maximal encoding regardless of previous
2756 * setting. This segment's either not text, or is
2757 * unlikely to be readable with > 30% of the
2758 * text encoded anyway, so we might as well save space...
2760 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2764 #ifdef ENCODE_FROMS
2765 /* If there were From_'s at the beginning of a line or standalone dots */
2766 if((froms || dots) && new_encoding != ENCBINARY)
2767 new_encoding = ENC8BIT;
2768 #endif
2770 /* Set the subtype */
2771 if(body->subtype == NULL)
2772 body->subtype = cpystr(rfc822_default_subtype(body->type));
2774 if(body->encoding == ENCOTHER)
2775 body->encoding = new_encoding;
2777 fs_give((void **)&buf);
2779 if(we_cancel)
2780 cancel_busy_cue(-1);
2785 * Call this to set the charset of an attachment we have
2786 * created. If the attachment contains any non-ascii characters
2787 * then we'll set the charset to the passed in charset, otherwise
2788 * we'll make it us-ascii.
2790 void
2791 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2793 unsigned char c;
2794 int can_be_ascii = 1;
2795 STORE_S *so = (STORE_S *)body->contents.text.data;
2796 int we_cancel = 0;
2798 if(!body || body->type != TYPETEXT)
2799 return;
2801 we_cancel = busy_cue(NULL, NULL, 1);
2803 so_seek(so, 0L, 0);
2805 while(can_be_ascii && so_readc(&c, so))
2806 if(!c || c & 0x80)
2807 can_be_ascii--;
2809 if(can_be_ascii)
2810 set_parameter(&body->parameter, "charset", "US-ASCII");
2811 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2812 set_parameter(&body->parameter, "charset", charset);
2813 else{
2815 * Else we don't know. There are non ascii characters but we either
2816 * don't have a charset to set it to or that charset is just us_ascii,
2817 * which is impossible. So we label it unknown. An alternative would
2818 * have been to strip the high bits instead and label it ascii.
2820 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2823 if(we_cancel)
2824 cancel_busy_cue(-1);
2829 * since encoding happens on the way out the door, this is basically
2830 * just needed to handle TYPEMULTIPART
2832 void
2833 pine_encode_body (struct mail_bodystruct *body)
2835 PART *part;
2837 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2838 if (body) switch (body->type) {
2839 char *freethis;
2841 case TYPEMULTIPART: /* multi-part */
2842 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2843 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2845 snprintf (tmp,sizeof(tmp),"%ld%ld%ld%ld",gethostid (),random (),(long) time (0),
2846 (long) getpid ());
2847 tmp[sizeof(tmp)-1] = '\0';
2848 set_parameter(&body->parameter, "boundary", tmp);
2851 if(freethis)
2852 fs_give((void **) &freethis);
2854 part = body->nested.part; /* encode body parts */
2855 do pine_encode_body (&part->body);
2856 while ((part = part->next) != NULL); /* until done */
2857 break;
2859 case TYPETEXT :
2861 * If the part is text we edited, then it is UTF-8.
2862 * The user may be asking us to send it as something else
2863 * or we may want to downconvert to a more-specific characterset.
2864 * Mark it for conversion here so the right MIME header's written.
2865 * Do conversion pine_rfc822_output_body.
2866 * Attachments are left as is.
2868 if(body->contents.text.data
2869 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2870 char *charset, *posting_charset, *lp;
2872 if(!((charset = parameter_val(body->parameter, "charset"))
2873 && !strucmp(charset, UNKNOWN_CHARSET))
2874 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2876 set_parameter(&body->parameter, "charset", posting_charset);
2879 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2880 * and doesn't use anything but ASCII characters.
2881 * Why is it not ENC7BIT already? Because when we set the encoding
2882 * in set_mime_type_by_grope we were groping through UTF-8 text
2883 * not 2022 text. Not only that, but we didn't know at that point
2884 * that it wouldn't stay UTF-8 when we sent it, which would require
2885 * encoding.
2887 if(!strucmp(posting_charset, "iso-2022-jp")
2888 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2889 && strlen(lp) < 4)
2890 body->encoding = ENC7BIT;
2893 if(charset)
2894 fs_give((void **)&charset);
2897 break;
2899 /* case MESSAGE: */ /* here for documentation */
2900 /* Encapsulated messages are always treated as text objects at this point.
2901 This means that you must replace body->contents.msg with
2902 body->contents.text, which probably involves copying
2903 body->contents.msg.text to body->contents.text */
2904 default: /* all else has some encoding */
2906 * but we'll delay encoding it until the message is on the way
2907 * into the mail slot...
2909 break;
2915 * pine_header_line - simple wrapper around c-client call to contain
2916 * repeated code, and to write fcc if required.
2919 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2920 int writehdr, int localcopy)
2922 int ret = 1;
2923 int flags;
2924 int big = 10000;
2925 char *value, *folded = NULL, *cs;
2926 char *converted;
2928 if(!text)
2929 return 1;
2931 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2933 if(converted){
2934 if(cs && !strucmp(cs, "us-ascii"))
2935 value = converted;
2936 else
2937 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2938 (unsigned char *) converted, cs,
2939 encode_whole_header(field, header));
2941 if(value && value == converted){ /* no encoding was done, have to fold */
2942 int fold_by, len;
2943 char *actual_field;
2945 len = ((header && header->env && header->env->remail)
2946 ? strlen("ReSent-") : 0) +
2947 (field ? strlen(field) : 0) + 2;
2949 actual_field = (char *)fs_get((len+1) * sizeof(char));
2950 snprintf(actual_field, len+1, "%s%s: ",
2951 (header && header->env && header->env->remail) ? "ReSent-" : "",
2952 field ? field : "");
2953 actual_field[len] = '\0';
2956 * We were folding everything except message-id, but that wasn't
2957 * sufficient. Since 822 only allows folding where linear-white-space
2958 * is allowed we'd need a smarter folder than "fold" to do it. So,
2959 * instead of inventing that smarter folder (which would have to
2960 * know 822 syntax)
2962 * We could just alloc space and copy the actual_field followed by
2963 * the value into it, but since that's what fold does anyway we'll
2964 * waste some cpu time and use fold with a big fold parameter.
2966 * We upped the references folding from 75 to 256 because we were
2967 * encountering longer-than-75 message ids, and to break one line
2968 * in references is to break them all.
2970 * Also, some users are adding long text without spaces to the subject
2971 * line (e.g. URLs) so we up that number too. For the moment this is
2972 * going to 512 (a url that is about 6 lines long.)
2974 flags = FLD_CRLF;
2975 if(field && !strucmp("Subject", field)){
2976 fold_by = 75;
2977 flags |= FLD_NEXTSPC;
2979 else if(field && !strucmp("References", field))
2980 fold_by = 256;
2981 else
2982 fold_by = big;
2984 folded = fold(value, fold_by, big, actual_field, " ", flags);
2986 if(actual_field)
2987 fs_give((void **)&actual_field);
2989 else if(value){ /* encoding was done */
2990 RFC822BUFFER rbuf;
2991 size_t ll;
2994 * rfc1522_encode already inserted continuation lines and did
2995 * the necessary folding so we don't have to do it. Let
2996 * rfc822_header_line add the trailing crlf and the resent- if
2997 * necessary. The 20 could actually be a 12.
2999 ll = strlen(field) + strlen(value) + 20;
3000 folded = (char *) fs_get(ll * sizeof(char));
3001 *folded = '\0';
3002 rbuf.f = dummy_soutr;
3003 rbuf.s = NULL;
3004 rbuf.beg = folded;
3005 rbuf.cur = folded;
3006 rbuf.end = folded+ll-1;
3007 rfc822_output_header_line(&rbuf, field,
3008 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3009 *rbuf.cur = '\0';
3012 if(value && folded){
3013 if(writehdr && f)
3014 ret = (*f)(s, folded);
3016 if(ret && localcopy && lmc.so && !lmc.all_written)
3017 ret = so_puts(lmc.so, folded);
3020 if(folded)
3021 fs_give((void **)&folded);
3023 if(converted && converted != text)
3024 fs_give((void **) &converted);
3026 else
3027 ret = 0;
3029 return(ret);
3034 * Do appropriate encoding of text header lines.
3035 * For some field types (those that consist of 822 *text) we just encode
3036 * the whole thing. For structured fields we encode only within comments
3037 * if possible.
3039 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3040 * s -- Source string.
3041 * charset -- Charset to encode with.
3042 * encode_all -- If set, encode the whole string. If not, try to encode
3043 * only within comments if possible.
3045 * Returns S is returned if no encoding is done. D is returned if encoding
3046 * was needed.
3048 char *
3049 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3051 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3052 int in_comment = 0;
3054 if(!s)
3055 return((char *)s);
3057 if(dlen < SIZEOF_20KBUF)
3058 alpine_panic("bad call to encode_header_value");
3060 if(!encode_all){
3062 * We don't have to worry about keeping track of quoted-strings because
3063 * none of these fields which aren't addresses contain quoted-strings.
3064 * We do keep track of escaped parens inside of comments and comment
3065 * nesting.
3067 p = d+7000;
3068 for(q = (char *)s; *q; q++){
3069 switch(*q){
3070 case LPAREN:
3071 if(in_comment++ == 0)
3072 start_of_comment = q;
3074 break;
3076 case RPAREN:
3077 if(--in_comment == 0){
3078 /* encode the comment, excluding the outer parens */
3079 if(p-d < dlen-1)
3080 *p++ = LPAREN;
3082 *q = '\0';
3083 r = rfc1522_encode(d+14000, dlen-14000,
3084 (unsigned char *)start_of_comment+1,
3085 charset);
3086 if(r != start_of_comment+1)
3087 value = d+7000; /* some encoding was done */
3089 start_of_comment = NULL;
3090 if(r)
3091 sstrncpy(&p, r, dlen-1-(p-d));
3093 *q = RPAREN;
3094 if(p-d < dlen-1)
3095 *p++ = *q;
3097 else if(in_comment < 0){
3098 in_comment = 0;
3099 if(p-d < dlen-1)
3100 *p++ = *q;
3103 break;
3105 case BSLASH:
3106 if(!in_comment && *(q+1)){
3107 if(p-d < dlen-2){
3108 *p++ = *q++;
3109 *p++ = *q;
3113 break;
3115 default:
3116 if(!in_comment && p-d < dlen-1)
3117 *p++ = *q;
3119 break;
3123 if(value){
3124 /* Unterminated comment (wasn't really a comment) */
3125 if(start_of_comment)
3126 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3128 *p = '\0';
3133 * We have to check if there is anything that needs to be encoded that
3134 * wasn't in a comment. If there is, we'd better just start over and
3135 * encode the whole thing. So, if no encoding has been done within
3136 * comments, or if encoding is needed both within and outside of
3137 * comments, then we encode the whole thing. Otherwise, go with
3138 * the version that has only comments encoded.
3140 if(!value || rfc1522_encode(d, dlen,
3141 (unsigned char *)value, charset) != value)
3142 return(rfc1522_encode(d, dlen, s, charset));
3143 else{
3144 strncpy(d, value, dlen-1);
3145 d[dlen-1] = '\0';
3146 return(d);
3152 * pine_address_line - write a header field containing addresses,
3153 * one by one (so there's no buffer limit), and
3154 * wrapping where necessary.
3155 * Note: we use c-client functions to properly build the text string,
3156 * but have to screw around with pointers to fool c-client functions
3157 * into not blatting all the text into a single buffer. Yeah, I know.
3160 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3161 soutr_t f, void *s, int writehdr, int localcopy)
3163 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3164 size_t alloced = 0, sz;
3165 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3166 char *converted, *cs;
3167 ADDRESS *atmp;
3168 int i, count;
3169 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3170 RFC822BUFFER rbuf;
3171 static char comma[] = ", ";
3172 static char end_group[] = ";";
3173 #define no_comma (&comma[1])
3175 if(!alist) /* nothing in field! */
3176 return(1);
3178 if(!alist->host && alist->mailbox){ /* c-client group convention */
3179 in_group++;
3180 was_start_of_group++;
3181 /* encode mailbox of group */
3182 mtmp = alist->mailbox;
3183 if(mtmp){
3184 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3185 buftmp[sizeof(buftmp)-1] = '\0';
3186 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3187 if(converted){
3188 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3189 (unsigned char *) converted, cs));
3190 if(converted && converted != buftmp)
3191 fs_give((void **) &converted);
3193 else{
3194 failed++;
3195 goto bail_out;
3199 else
3200 mtmp = NULL;
3202 ptmp = alist->personal; /* remember personal name */
3203 /* make sure personal name is encoded */
3204 if(ptmp){
3205 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3206 buftmp[sizeof(buftmp)-1] = '\0';
3207 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3208 if(converted){
3209 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3210 (unsigned char *) converted, cs));
3211 if(converted && converted != buftmp)
3212 fs_give((void **) &converted);
3214 else{
3215 failed++;
3216 goto bail_out;
3220 atmp = alist->next;
3221 alist->next = NULL; /* digest only first address! */
3223 /* use automatic buffer unless it isn't big enough */
3224 if((alloced = est_size(alist)) > sizeof(tmp)){
3225 tmpptr = (char *)fs_get(alloced);
3226 sz = alloced;
3228 else{
3229 tmpptr = tmp;
3230 sz = sizeof(tmp);
3233 rbuf.f = dummy_soutr;
3234 rbuf.s = NULL;
3235 rbuf.beg = tmpptr;
3236 rbuf.cur = tmpptr;
3237 rbuf.end = tmpptr+sz-1;
3238 rfc822_output_address_line(&rbuf, field,
3239 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3240 *rbuf.cur = '\0';
3242 alist->next = atmp; /* restore pointer to next addr */
3244 if(alist->personal && alist->personal != ptmp)
3245 fs_give((void **) &alist->personal);
3247 alist->personal = ptmp; /* in case it changed, restore name */
3249 if(mtmp){
3250 if(alist->mailbox && alist->mailbox != mtmp)
3251 fs_give((void **) &alist->mailbox);
3253 alist->mailbox = mtmp;
3256 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3257 count -= 2;
3258 tmpptr[count] = '\0';
3262 * If there is no sending_stream and we are writing the Lcc header,
3263 * then we are piping it to sendmail -t which expects it to be a bcc,
3264 * not lcc.
3266 * When we write it to the fcc or postponed (the lmc.so),
3267 * we want it to be lcc, not bcc, so we put it back.
3269 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3270 fix_lcc = 1;
3272 if(writehdr && f && *tmpptr){
3273 if(fix_lcc)
3274 tmpptr[0] = 'b';
3276 failed = !(*f)(s, tmpptr);
3277 if(fix_lcc)
3278 tmpptr[0] = 'L';
3280 if(failed)
3281 goto bail_out;
3284 if(localcopy && lmc.so &&
3285 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3286 goto bail_out;
3288 for(alist = atmp; alist; alist = alist->next){
3289 delim = comma;
3290 /* account for c-client's representation of group names */
3291 if(in_group){
3292 if(!alist->host){ /* end of group */
3293 in_group = 0;
3294 was_start_of_group = 0;
3296 * Rfc822_write_address no longer writes out the end of group
3297 * unless the whole group address is passed to it, so we do
3298 * it ourselves.
3300 delim = end_group;
3302 else if(!localcopy || !lmc.so || lmc.all_written)
3303 continue;
3305 /* start of new group, print phrase below */
3306 else if(!alist->host && alist->mailbox){
3307 in_group++;
3308 was_start_of_group++;
3311 /* no comma before first address in group syntax */
3312 if(was_start_of_group && alist->host){
3313 delim = no_comma;
3314 was_start_of_group = 0;
3317 /* write delimiter */
3318 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3319 || (localcopy && lmc.so && !lmc.all_written
3320 && !so_puts(lmc.so, delim)))
3321 goto bail_out;
3323 ptmp = alist->personal; /* remember personal name */
3324 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3325 buftmp[sizeof(buftmp)-1] = '\0';
3326 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3327 if(converted){
3328 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3329 (unsigned char *) converted, cs));
3330 if(converted && converted != buftmp)
3331 fs_give((void **) &converted);
3333 else{
3334 failed++;
3335 goto bail_out;
3338 atmp = alist->next;
3339 alist->next = NULL; /* tie off linked list */
3340 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3341 alloced = i;
3342 sz = alloced;
3343 fs_resize((void **)&tmpptr, alloced);
3346 *tmpptr = '\0';
3347 /* make sure we don't write out group end with rfc822_write_address */
3348 if(alist->host || alist->mailbox){
3349 rbuf.f = dummy_soutr;
3350 rbuf.s = NULL;
3351 rbuf.beg = tmpptr;
3352 rbuf.cur = tmpptr;
3353 rbuf.end = tmpptr+sz-1;
3354 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3355 *rbuf.cur = '\0';
3358 alist->next = atmp; /* restore next pointer */
3360 if(alist->personal && alist->personal != ptmp)
3361 fs_give((void **) &alist->personal);
3363 alist->personal = ptmp; /* in case it changed, restore name */
3366 * BUG
3367 * With group syntax addresses we no longer have two identical
3368 * streams of output. Instead, for the fcc/postpone copy we include
3369 * all of the addresses inside the :; of the group, and for the
3370 * mail we're sending we don't include them. That means we aren't
3371 * correctly keeping track of the column to wrap in, below. That is,
3372 * we are keeping track of the fcc copy but we aren't keeping track
3373 * of the regular copy. It could result in too long or too short
3374 * lines. Should almost never come up since group addresses are almost
3375 * never followed by other addresses in the same header, and even
3376 * when they are, you have to go out of your way to get the headers
3377 * messed up.
3379 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3380 count = i + 4;
3381 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3382 || (localcopy && lmc.so && !lmc.all_written &&
3383 !so_puts(lmc.so, "\015\012 ")))
3384 goto bail_out;
3386 else
3387 count += i + 2;
3389 if(((!in_group || was_start_of_group)
3390 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3391 || (localcopy && lmc.so && !lmc.all_written
3392 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3393 goto bail_out;
3396 bail_out:
3397 if(tmpptr && tmpptr != tmp)
3398 fs_give((void **)&tmpptr);
3400 if(failed)
3401 return(0);
3403 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3404 && ((localcopy && lmc.so
3405 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3410 * mutated pine version of c-client's rfc822_header() function.
3411 * changed to call pine-wrapped header and address functions
3412 * so we don't have to limit the header size to a fixed buffer.
3413 * This function also calls pine's body_header write function
3414 * because encoding is delayed until output_body() is called.
3416 long
3417 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3419 PINEFIELD *pf;
3420 int j;
3422 if(header->env->remail){ /* if remailing */
3423 long i = strlen (header->env->remail);
3424 if(i > 4 && header->env->remail[i-4] == '\015')
3425 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3427 if((f && !(*f)(s, header->env->remail))
3428 || (lmc.so && !lmc.all_written
3429 && !so_puts(lmc.so, header->env->remail)))
3430 return(0L); /* start with remail header */
3433 j = 0;
3434 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3435 switch(pf->type){
3437 * Warning: This is confusing. The 2nd to last argument used to
3438 * be just pf->writehdr. We want Bcc lines to be written out
3439 * if we are handing off to a sendmail temp file but not if we
3440 * are talking smtp, so bcc's writehdr is set to 0 and
3441 * pine_address_line was sending if writehdr OR !sending_stream.
3442 * That works as long as we want to write everything when
3443 * !sending_stream (an mta handoff to sendmail). But then we
3444 * added the undisclosed recipients line which should only get
3445 * written if writehdr is set, and not when we pass to a
3446 * sendmail temp file. So pine_address_line has been changed
3447 * so it bases its decision solely on the writehdr passed to it,
3448 * and the logic that worries about Bcc and sending_stream
3449 * was moved up to the caller (here) to decide when to set it.
3451 * So we have:
3452 * undisclosed recipients:; This will just be written
3453 * if writehdr was set and not
3454 * otherwise, nothing magical.
3455 *** We may want to change this, because sendmail -t doesn't handle
3456 *** the empty group syntax well unless it has been configured to
3457 *** do so. It isn't configured by default, or in any of the
3458 *** sendmail v8 configs. So we may want to not write this line
3459 *** if we're doing an mta_handoff (!sending_stream).
3461 * !sending_stream (which means a handoff to a sendmail -t)
3462 * bcc or lcc both set the arg so they'll get written
3463 * (There is also Lcc hocus pocus in pine_address_line
3464 * which converts the Lcc: to Bcc: for sendmail
3465 * processing.)
3466 * sending_stream (which means an smtp handoff)
3467 * bcc and lcc will never have writehdr set, so
3468 * will never be written (They both do have rcptto set,
3469 * so they both do cause RCPT TO commands.)
3471 * The localcopy is independent of sending_stream and is just
3472 * written if it is set for all of these.
3474 case Address:
3475 if(!pine_address_line(pf->name,
3476 header,
3477 pf->addr ? *pf->addr : NULL,
3480 (!strucmp("bcc",pf->name ? pf->name : "")
3481 || !strucmp("Lcc",pf->name ? pf->name : ""))
3482 ? !sending_stream
3483 : pf->writehdr,
3484 pf->localcopy))
3485 return(0L);
3487 break;
3489 case Fcc:
3490 case FreeText:
3491 case Subject:
3492 if(!pine_header_line(pf->name, header,
3493 pf->text ? *pf->text : NULL,
3494 f, s, pf->writehdr, pf->localcopy))
3495 return(0L);
3497 break;
3499 default:
3500 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3501 pf->name);
3502 break;
3507 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3509 * Add comforting "X-" header line indicating what sort of
3510 * authenticity the receiver can expect...
3512 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3513 NETMBX netmbox;
3514 char sstring[MAILTMPLEN], *label; /* place to write */
3515 MAILSTREAM *m;
3516 int i, anonymous = 1;
3518 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3519 m = ps_global->s_pool.streams[i];
3520 if(m && sp_flagged(m, SP_LOCKED)
3521 && mail_valid_net_parse(m->mailbox, &netmbox)
3522 && !netmbox.anoflag)
3523 anonymous = 0;
3526 if(!anonymous){
3527 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3528 *user = (*netmbox.user)
3529 ? netmbox.user
3530 : cached_user_name(netmbox.mailbox);
3531 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3532 isdigit((unsigned char)last_char) ? "[" : "",
3533 netmbox.host,
3534 isdigit((unsigned char) last_char) ? "]" : "");
3535 sstring[sizeof(sstring)-1] = '\0';
3536 label = "X-X-Sender"; /* Jeez. */
3537 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3538 label += 4;
3540 else{
3541 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3542 sstring[sizeof(sstring)-1] = '\0';
3543 label = "X-Warning";
3546 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3547 return(0L);
3549 #endif
3551 if(body && !header->env->remail){ /* not if remail or no body */
3552 if((f && !(*f)(s, MIME_VER))
3553 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3554 || !pine_write_body_header(body, f, s))
3555 return(0L);
3557 else{ /* write terminating newline */
3558 if((f && !(*f)(s, "\015\012"))
3559 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3560 return(0L);
3563 return(1L);
3568 * pine_rfc822_output - pine's version of c-client call. Necessary here
3569 * since we're not using its structures as intended!
3571 long
3572 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3574 int we_cancel = 0;
3575 long retval;
3577 dprint((4, "-- pine_rfc822_output\n"));
3579 we_cancel = busy_cue(NULL, NULL, 1);
3580 pine_encode_body(body); /* encode body as necessary */
3581 /* build and output RFC822 header, output body */
3582 retval = pine_rfc822_header(header, body, f, s)
3583 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3585 if(we_cancel)
3586 cancel_busy_cue(-1);
3588 return(retval);
3593 * post_rfc822_output - cloak for pine's 822 output routine. Since
3594 * we can't pass opaque envelope thru c-client posting
3595 * logic, we need to wrap the real output inside
3596 * something that c-client knows how to call.
3598 long
3599 post_rfc822_output(char *tmp,
3600 ENVELOPE *env,
3601 struct mail_bodystruct *body,
3602 soutr_t f,
3603 void *s,
3604 long int ok8bit)
3606 return(pine_rfc822_output(send_header, body, f, s));
3611 * posting_characterset- determine what transliteration is reasonable
3612 * for posting the given non-ascii message data.
3614 * preferred_charset is the charset the original data was labeled in.
3615 * If we can keep that we do.
3617 * Returns: always returns the preferred character set.
3619 char *
3620 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3622 unsigned long *charsetmap = NULL;
3623 unsigned long validbitmap;
3624 static char *ascii = "us-ascii";
3625 static char *utf8 = "utf-8";
3626 int notcjk = 0;
3628 if(!ps_global->post_utf8){
3629 validbitmap = 0;
3631 if(mp == HdrText){
3632 char *text = NULL;
3633 UCS *ucs = NULL, *ucsp = NULL;
3635 text = (char *) data;
3637 /* convert text in header to UCS characters */
3638 if(text)
3639 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3641 if(!(ucs && *ucs))
3642 return(ascii);
3645 * After the while loop is done the validbitmap has
3646 * a 1 bit for all the character sets that can
3647 * represent all of the characters of this header.
3649 charsetmap = init_charsetchecker(preferred_charset);
3651 if(!charsetmap){
3652 if (ucs != NULL) fs_give((void **) &ucs);
3653 return(utf8);
3656 validbitmap = ~0;
3657 while((validbitmap & ~0x1) && (*ucsp)){
3658 if(*ucsp > 0xffff){
3659 fs_give((void **) &ucs);
3660 return(utf8);
3663 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3666 fs_give((void **) &ucs);
3668 notcjk = validbitmap & 0x1;
3669 validbitmap &= ~0x1;
3671 if(!validbitmap)
3672 return(utf8);
3674 else{
3675 struct mail_bodystruct *body = NULL;
3676 STORE_S *the_text = NULL;
3677 int outchars;
3678 unsigned char c;
3679 UCS ucs;
3680 CBUF_S cbuf;
3682 cbuf.cbuf[0] = '\0';
3683 cbuf.cbufp = cbuf.cbuf;
3684 cbuf.cbufend = cbuf.cbuf;
3686 body = (struct mail_bodystruct *) data;
3688 if(body && body->type == TYPEMULTIPART)
3689 body = &body->nested.part->body;
3691 if(body && body->type == TYPETEXT)
3692 the_text = (STORE_S *) body->contents.text.data;
3694 if(!the_text)
3695 return(ascii);
3697 so_seek(the_text, 0L, 0); /* rewind */
3699 charsetmap = init_charsetchecker(preferred_charset);
3701 if(!charsetmap)
3702 return(utf8);
3704 validbitmap = ~0;
3707 * Read a stream of UTF-8 characters from the_text
3708 * and convert them to UCS-4 characters for the translatable
3709 * test.
3711 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3712 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3713 /* got a ucs character */
3714 if(ucs > 0xffff)
3715 return(utf8);
3717 validbitmap &= charsetmap[(unsigned long) ucs];
3721 notcjk = validbitmap & 0x1;
3722 validbitmap &= ~0x1;
3724 if(!validbitmap)
3725 return(utf8);
3728 /* user chooses something other than UTF-8 */
3729 if(strucmp(ps_global->posting_charmap, utf8)){
3731 * If we're to post in other than UTF-8, and it can be
3732 * transliterated without losing fidelity, do it.
3733 * Else, use UTF-8.
3736 /* if ascii works, always use that */
3737 if(representable_in_charset(validbitmap, ascii))
3738 return(ascii);
3740 /* does the user's posting character set work? */
3741 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3742 return(ps_global->posting_charmap);
3744 /* this is the charset the message we are replying to was in */
3745 if(preferred_charset
3746 && strucmp(preferred_charset, ascii)
3747 && representable_in_charset(validbitmap, preferred_charset))
3748 return(preferred_charset);
3750 /* else, use UTF-8 */
3753 /* user chooses nothing, going with the default */
3754 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3755 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3756 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3757 char *most_preferred;
3760 * In this case the user didn't specify a posting character set
3761 * and we will choose the most-specific one from our list.
3764 /* ascii is best */
3765 if(representable_in_charset(validbitmap, ascii))
3766 return(ascii);
3768 /* Can we keep the original from the message we're replying to? */
3769 if(preferred_charset
3770 && strucmp(preferred_charset, ascii)
3771 && representable_in_charset(validbitmap, preferred_charset))
3772 return(preferred_charset);
3774 /* choose the best of the rest */
3775 most_preferred = most_preferred_charset(validbitmap);
3776 if(!most_preferred)
3777 return(utf8);
3780 * If the text we're labeling contains something like
3781 * smart quotes but no CJK characters, then instead of
3782 * labeling it as ISO-2022-JP we want to use UTF-8.
3784 if(notcjk){
3785 const CHARSET *cs;
3787 cs = utf8_charset(most_preferred);
3788 if(!cs
3789 || cs->script == SC_CHINESE_SIMPLIFIED
3790 || cs->script == SC_CHINESE_TRADITIONAL
3791 || cs->script == SC_JAPANESE
3792 || cs->script == SC_KOREAN)
3793 return(utf8);
3796 return(most_preferred);
3798 /* user explicitly chooses UTF-8 */
3799 else{
3800 /* if ascii works, always use that */
3801 if(representable_in_charset(validbitmap, ascii))
3802 return(ascii);
3804 /* else, use UTF-8 */
3809 return(utf8);
3813 static char **charsetlist = NULL;
3814 static int items_in_charsetlist = 0;
3815 static unsigned long *charsetmap = NULL;
3817 static char *downgrades[] = {
3818 "US-ASCII",
3819 "ISO-8859-15",
3820 "ISO-8859-1",
3821 "ISO-8859-2",
3822 "VISCII",
3823 "KOI8-R",
3824 "KOI8-U",
3825 "ISO-8859-7",
3826 "ISO-8859-6",
3827 "ISO-8859-8",
3828 "TIS-620",
3829 "ISO-2022-JP",
3830 "GB2312",
3831 "BIG5",
3832 "EUC-KR"
3836 unsigned long *
3837 init_charsetchecker(char *preferred_charset)
3839 int i, count = 0, reset = 0;
3840 char *ascii = "US-ASCII";
3841 char *utf8 = "UTF-8";
3844 * When user doesn't set a posting character set posting_charmap ends up
3845 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3846 * That's where the strange set of if-else's come from.
3849 /* user chooses something other than UTF-8 */
3850 if(strucmp(ps_global->posting_charmap, utf8)){
3851 count++; /* US-ASCII */
3852 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3853 reset++;
3855 /* if posting_charmap is valid, include it in list */
3856 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3857 && strucmp(ps_global->posting_charmap, ascii)
3858 && strucmp(ps_global->posting_charmap, utf8)
3859 && utf8_charset(ps_global->posting_charmap)){
3860 count++;
3861 if(!reset
3862 && (items_in_charsetlist < count
3863 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3864 reset++;
3867 if(preferred_charset && preferred_charset[0]
3868 && strucmp(preferred_charset, ascii)
3869 && strucmp(preferred_charset, utf8)
3870 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3871 count++;
3872 if(!reset
3873 && (items_in_charsetlist < count
3874 || strucmp(charsetlist[count-1], preferred_charset)))
3875 reset++;
3878 if(items_in_charsetlist != count)
3879 reset++;
3881 if(reset){
3882 if(charsetlist)
3883 free_list_array(&charsetlist);
3885 items_in_charsetlist = count;
3886 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3888 i = 0;
3889 charsetlist[i++] = cpystr(ascii);
3891 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3892 && strucmp(ps_global->posting_charmap, ascii)
3893 && strucmp(ps_global->posting_charmap, utf8)
3894 && utf8_charset(ps_global->posting_charmap))
3895 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3897 if(preferred_charset && preferred_charset[0]
3898 && strucmp(preferred_charset, ascii)
3899 && strucmp(preferred_charset, utf8)
3900 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3901 charsetlist[i++] = cpystr(preferred_charset);
3903 charsetlist[i] = NULL;
3906 /* user chooses nothing, going with the default */
3907 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3908 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3909 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3910 int add_preferred = 0;
3912 /* does preferred_charset have to be added to the list? */
3913 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3914 add_preferred = 1;
3915 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3916 if(!strucmp(downgrades[i], preferred_charset))
3917 add_preferred = 0;
3920 if(add_preferred){
3921 /* existing list is right size already */
3922 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3923 /* just check to see if last list item is the preferred_charset */
3924 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3925 /* no, fix it */
3926 reset++;
3927 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3928 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3931 else{
3932 reset++;
3933 if(charsetlist)
3934 free_list_array(&charsetlist);
3936 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3937 items_in_charsetlist = count;
3938 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3939 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3940 charsetlist[i] = cpystr(downgrades[i]);
3942 charsetlist[i++] = cpystr(preferred_charset);
3943 charsetlist[i] = NULL;
3946 else{
3947 /* if list is same size as downgrades, consider it good */
3948 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
3949 reset++;
3951 if(reset){
3952 if(charsetlist)
3953 free_list_array(&charsetlist);
3955 count = sizeof(downgrades)/sizeof(downgrades[0]);
3956 items_in_charsetlist = count;
3957 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3958 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3959 charsetlist[i] = cpystr(downgrades[i]);
3961 charsetlist[i] = NULL;
3965 /* user explicitly chooses UTF-8 */
3966 else{
3967 /* include possibility of ascii even if they explicitly ask for UTF-8 */
3968 count++; /* US-ASCII */
3969 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3970 reset++;
3972 if(items_in_charsetlist != count)
3973 reset++;
3975 if(reset){
3976 if(charsetlist)
3977 free_list_array(&charsetlist);
3979 /* the list is just ascii and nothing else */
3980 items_in_charsetlist = count;
3981 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3983 i = 0;
3984 charsetlist[i++] = cpystr(ascii);
3985 charsetlist[i] = NULL;
3990 if(reset){
3991 if(charsetmap)
3992 fs_give((void **) &charsetmap);
3994 if(charsetlist)
3995 charsetmap = utf8_csvalidmap(charsetlist);
3998 return(charsetmap);
4002 /* total reset */
4003 void
4004 free_charsetchecker(void)
4006 if(charsetlist)
4007 free_list_array(&charsetlist);
4009 items_in_charsetlist = 0;
4011 if(charsetmap)
4012 fs_give((void **) &charsetmap);
4017 representable_in_charset(unsigned long validbitmap, char *charset)
4019 int i, done = 0, ret = 0;
4020 unsigned long j;
4022 if(!(charset && charset[0]))
4023 return ret;
4025 if(!strucmp(charset, "UTF-8"))
4026 return 1;
4028 for(i = 0; !done && i < items_in_charsetlist; i++){
4029 if(!strucmp(charset, charsetlist[i])){
4030 j = 1;
4031 j <<= (i+1);
4032 done++;
4033 if(validbitmap & j)
4034 ret = 1;
4038 return ret;
4042 char *
4043 most_preferred_charset(unsigned long validbitmap)
4045 unsigned long bm;
4046 unsigned long rm;
4047 int index;
4049 if(!(validbitmap && items_in_charsetlist > 0))
4050 return("UTF-8");
4052 /* careful, find_rightmost_bit modifies the bitmap */
4053 bm = validbitmap;
4054 rm = find_rightmost_bit(&bm);
4055 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4057 return(charsetlist[index]);
4060 void
4061 remove_parameter(PARAMETER **param, char *paramname)
4063 PARAMETER *pm;
4065 if(param == NULL || *param == NULL
4066 || paramname == NULL || *paramname == '\0')
4067 return;
4069 for(pm = *param;
4070 pm != NULL && pm->attribute != NULL && strucmp(pm->attribute, paramname);
4071 pm = pm->next);
4073 if(pm){
4074 PARAMETER om, *qm;
4075 om.next = *param;
4076 for(qm = &om; qm->next != pm; qm = qm->next);
4077 qm->next = copy_parameters(pm->next);
4078 mail_free_body_parameter(&pm);
4079 *param = om.next;
4085 * Set parameter to new value.
4087 void
4088 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4090 PARAMETER *pm;
4092 if(!param || !(paramname && *paramname))
4093 return;
4095 if(*param == NULL){
4096 pm = (*param) = mail_newbody_parameter();
4097 pm->attribute = cpystr(paramname);
4099 else{
4100 int nomatch;
4102 for(pm = *param;
4103 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4104 pm = pm->next)
4105 ;/* searching for paramname parameter */
4107 if(nomatch){ /* add charset parameter */
4108 pm->next = mail_newbody_parameter();
4109 pm = pm->next;
4110 pm->attribute = cpystr(paramname);
4112 /* else pm is existing paramname parameter */
4115 if(pm){
4116 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4117 if(pm->value)
4118 fs_give((void **) &pm->value);
4120 if(new_value)
4121 pm->value = cpystr(new_value);
4127 /*----------------------------------------------------------------------
4128 Remove the leading digits from SMTP error messages
4129 -----*/
4130 char *
4131 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4133 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4134 (*error == '.' && isdigit((unsigned char)*(error+1))))
4135 error++;
4137 snprintf(outbuf, outbuflen, printstring, error);
4138 outbuf[outbuflen-1] = '\0';
4139 return(outbuf);
4144 * Local globals pine's body output routine needs
4146 static soutr_t l_f;
4147 static TCPSTREAM *l_stream;
4148 static unsigned c_in_buf = 0;
4151 * def to make our pipe write's more friendly
4153 #ifdef PIPE_MAX
4154 #if PIPE_MAX > 20000
4155 #undef PIPE_MAX
4156 #endif
4157 #endif
4159 #ifndef PIPE_MAX
4160 #define PIPE_MAX 1024
4161 #endif
4165 * l_flust_net - empties gf_io terminal function's buffer
4168 l_flush_net(int force)
4170 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4171 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4173 tmp_20k_buf[c_in_buf] = '\0';
4174 if(!force){
4176 * The start of each write is expected to be the start of a
4177 * "record" (i.e., a CRLF terminated line). Make sure that is true
4178 * else we might screw up SMTP dot quoting...
4180 for(p = tmp_20k_buf, lp = NULL;
4181 (p = strstr(p, "\015\012")) != NULL;
4182 lp = (p += 2))
4186 if(!lp && c_in_buf > 2) /* no CRLF! */
4187 for(p = &tmp_20k_buf[c_in_buf] - 2;
4188 p > &tmp_20k_buf[0] && *p == '.';
4189 p--) /* find last non-dot */
4192 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4193 /* snippet remains */
4194 c = *lp;
4195 *lp = '\0';
4199 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4200 || (lmc.so && !lmc.all_written
4201 && !(lmc.text_only && lmc.text_written)
4202 && !so_puts(lmc.so, tmp_20k_buf)))
4203 return(0);
4205 c_in_buf = 0;
4206 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4207 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4208 c_in_buf++, lp++;
4211 return(1);
4216 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4220 l_putc(int c)
4222 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4223 tmp_20k_buf[c_in_buf++] = (char) c;
4225 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4231 * pine_rfc822_output_body - pine's version of c-client call. Again,
4232 * necessary since c-client doesn't know about how
4233 * we're treating attachments
4235 long
4236 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4238 STORE_S *bodyso;
4239 PART *part;
4240 PARAMETER *param;
4241 char *t, *cookie = NIL, *encode_error;
4242 char tmp[MAILTMPLEN];
4243 int add_trailing_crlf;
4244 LOC_2022_JP ljp;
4245 gf_io_t gc;
4247 dprint((4, "-- pine_rfc822_output_body: %d\n",
4248 body ? body->type : 0));
4250 bodyso = (STORE_S *) body->contents.text.data;
4252 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4253 part = body->nested.part; /* first body part */
4254 /* find cookie */
4255 for (param = body->parameter; param && !cookie; param = param->next)
4256 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4257 if (!cookie) cookie = "-"; /* yucky default */
4260 * Output a bit of text before the first multipart delimiter
4261 * to warn unsuspecting users of non-mime-aware ua's that
4262 * they should expect weirdness. We do not add this when signing a
4263 * message, though...
4265 #ifdef SMIME
4266 if(!ps_global->smime || !ps_global->smime->do_sign)
4267 #endif /* SMIME */
4268 if(f && !(*f)(s, " This message is in MIME format. The first part should be readable text,\015\012 while the remaining parts are likely unreadable without MIME-aware tools.\015\012\015\012"))
4269 return(0);
4271 do { /* for each part */
4272 /* build cookie */
4273 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4274 tmp[sizeof(tmp)-1] = '\0';
4275 /* append cookie,mini-hdr,contents */
4276 if((f && !(*f)(s, tmp))
4277 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4278 || !pine_write_body_header(&part->body,f,s)
4279 || !pine_rfc822_output_body (&part->body,f,s))
4280 return(0);
4281 #if 0 /* temporariy disable this code */
4282 if(part == body->nested.part
4283 && ps_global->smime
4284 && ps_global->smime->do_sign
4285 && ((f && !(*f)(s, "\015\012"))
4286 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012"))))
4287 return 0;
4288 #endif /* SMIME */
4289 } while ((part = part->next) != NULL); /* until done */
4290 /* output trailing cookie */
4291 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4292 tmp[sizeof(tmp)-1] = '\0';
4293 #ifdef SMIME
4294 if(ps_global->smime && ps_global->smime->do_sign
4295 && strlen(tmp) < sizeof(tmp)-2)
4296 strncat(tmp, "\015\012", sizeof(tmp) - strlen(tmp) - 1);
4297 #endif
4298 if(lmc.so && !lmc.all_written){
4299 so_puts(lmc.so, t);
4300 so_puts(lmc.so, "\015\012");
4303 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4306 l_f = f; /* set up for writing chars... */
4307 l_stream = s; /* out other end of pipe... */
4308 gf_filter_init();
4309 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4310 body->size.bytes));
4312 if(bodyso)
4313 gf_set_so_readc(&gc, bodyso);
4314 else
4315 return(1);
4318 * Don't add trailing line if it is ExternalText, which already guarantees
4319 * a trailing newline.
4321 add_trailing_crlf = !(bodyso->src == ExternalText);
4323 so_seek(bodyso, 0L, 0);
4325 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4326 char *charset;
4328 if(body->type == TYPETEXT
4329 && so_attr(bodyso, "edited", NULL)
4330 && (charset = parameter_val(body->parameter, "charset"))){
4331 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4332 if(!strucmp(charset, "iso-2022-jp")){
4333 ljp.report_err = 0;
4334 gf_link_filter(gf_line_test,
4335 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4337 else{
4338 void *table = utf8_rmap(charset);
4340 if(table){
4341 gf_link_filter(gf_convert_utf8_charset,
4342 gf_convert_utf8_charset_opt(table,0));
4344 else{
4345 /* else, just send it? */
4346 set_parameter(&body->parameter, "charset", "UTF-8");
4351 fs_give((void **)&charset);
4355 * Convert text pieces to canonical form
4356 * BEFORE applying any encoding (rfc1341: appendix G)...
4357 * NOTE: almost all filters expect CRLF newlines
4359 if(body->type == TYPETEXT
4360 && body->encoding != ENCBASE64
4361 && !so_attr(bodyso, "rawbody", NULL)){
4362 gf_link_filter(gf_local_nvtnl, NULL);
4365 switch (body->encoding) { /* all else needs filtering */
4366 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4367 gf_link_filter(gf_8bit_qp, NULL);
4368 break;
4370 case ENCBINARY: /* encode binary into BASE64 */
4371 gf_link_filter(gf_binary_b64, NULL);
4372 break;
4374 default: /* otherwise text */
4375 break;
4379 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4380 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4381 _("Encoding Error \"%s\""), encode_error);
4382 display_message('x');
4385 gf_clear_so_readc(bodyso);
4387 if(encode_error || !l_flush_net(TRUE))
4388 return(0);
4390 send_bytes_sent += gf_bytes_piped();
4391 so_release((STORE_S *)body->contents.text.data);
4393 if(lmc.so && !lmc.all_written && lmc.text_only){
4394 if(lmc.text_written){ /* we have some splainin' to do */
4395 char tmp[MAILTMPLEN];
4396 char *name = NULL;
4398 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4399 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4400 return(0);
4403 * BUG: If this name is not ascii it's going to cause trouble.
4405 name = parameter_val(body->parameter, "name");
4406 snprintf(tmp, sizeof(tmp),
4407 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4408 body_type_names(body->type),
4409 body->subtype ? body->subtype : "Unknown",
4410 name ? " (Name=\"" : "",
4411 name ? name : "",
4412 name ? "\")" : "",
4413 comatose(body->size.bytes));
4414 tmp[sizeof(tmp)-1] = '\0';
4415 if(name)
4416 fs_give((void **)&name);
4418 if(!so_puts(lmc.so, tmp))
4419 return(0);
4421 else /* suppress everything after first text part */
4422 lmc.text_written = (body->type == TYPETEXT
4423 && (!body->subtype
4424 || !strucmp(body->subtype, "plain")));
4427 if(add_trailing_crlf)
4428 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4429 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4430 else
4431 return(1);
4434 char *
4435 ToLower(char *s, char *t)
4437 int i;
4439 for(i = 0; s != NULL && s[i] != '\0'; i++)
4440 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4441 t[i] = '\0';
4443 return t;
4447 * pine_write_body_header - another c-client clone. This time
4448 * so the final encoding labels get set
4449 * correctly since it hasn't happened yet,
4450 * and to be paranoid about line lengths.
4452 * Returns: TRUE/nonzero on success, zero on error
4455 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4457 char tmp[MAILTMPLEN];
4458 RFC822BUFFER rbuf;
4459 int i;
4460 unsigned char c;
4461 STRINGLIST *stl;
4462 STORE_S *so;
4463 extern const char *tspecials;
4465 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4466 if(!(so_puts(so, "Content-Type: ")
4467 && so_puts(so, ToLower(body_types[body->type], tmp))
4468 && so_puts(so, "/")
4469 && so_puts(so, ToLower(body->subtype
4470 ? body->subtype
4471 : rfc822_default_subtype (body->type),tmp))))
4472 return(pwbh_finish(0, so));
4474 if(body->parameter){
4475 if(!pine_write_params(body->parameter, so))
4476 return(pwbh_finish(0, so));
4478 else if ((body->type != TYPEMESSAGE
4479 || (body->subtype && strucmp(body->subtype, "RFC822")))
4480 && (!so_puts(so, "; CHARSET=US-ASCII")))
4481 return(pwbh_finish(0, so));
4483 if(!so_puts(so, "\015\012"))
4484 return(pwbh_finish(0, so));
4486 /* do not change the encoding of a TYPEMESSAGE part */
4487 if ((body->encoding /* note: encoding 7BIT never output! */
4488 && !(so_puts(so, "Content-Transfer-Encoding: ")
4489 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4490 ? (body->type == TYPEMESSAGE ? ENCBINARY : ENCBASE64)
4491 : (body->encoding == ENC8BIT)
4492 ?(body->type == TYPEMESSAGE ? ENC8BIT : ENCQUOTEDPRINTABLE)
4493 : (body->encoding <= ENCMAX)
4494 ? body->encoding
4495 : ENCOTHER])
4496 && so_puts(so, "\015\012")))
4498 * If requested, strip Content-ID headers that don't look like they
4499 * are needed. Microsoft's Outlook XP has a bug that causes it to
4500 * not show that there is an attachment when there is a Content-ID
4501 * header present on that attachment.
4503 * If user has quell-content-id turned on, don't output content-id
4504 * unless it is of type message/external-body.
4505 * Since this code doesn't look inside messages being forwarded
4506 * type message content-ids will remain as is and type multipart
4507 * alternative will remain as is. We don't create those on our
4508 * own. If we did, we'd have to worry about getting this right.
4510 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4511 || (body->type == TYPEMESSAGE
4512 && body->subtype
4513 && !strucmp(body->subtype, "external-body")))
4514 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4515 && so_puts(so, "\015\012")))
4516 || (body->description
4517 && strlen(body->description) < 5000 /* arbitrary! */
4518 && !pine_write_header_line("Content-Description: ", body->description, so))
4519 || (body->md5
4520 && !(so_puts(so, "Content-MD5: ")
4521 && so_puts(so, body->md5)
4522 && so_puts(so, "\015\012"))))
4523 return(pwbh_finish(0, so));
4525 if ((stl = body->language) != NULL) {
4526 if(!so_puts(so, "Content-Language: "))
4527 return(pwbh_finish(0, so));
4529 do {
4530 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4531 return(pwbh_finish(0, so));
4533 tmp[0] = '\0';
4534 rbuf.f = dummy_soutr;
4535 rbuf.s = NULL;
4536 rbuf.beg = tmp;
4537 rbuf.cur = tmp;
4538 rbuf.end = tmp+sizeof(tmp)-1;
4539 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4540 *rbuf.cur = '\0';
4542 if(!so_puts(so, tmp)
4543 || ((stl = stl->next) && !so_puts(so, ", ")))
4544 return(pwbh_finish(0, so));
4546 while (stl);
4548 if(!so_puts(so, "\015\012"))
4549 return(pwbh_finish(0, so));
4552 if (body->disposition.type) {
4553 if(!(so_puts(so, "Content-Disposition: ")
4554 && so_puts(so, body->disposition.type)))
4555 return(pwbh_finish(0, so));
4557 if(!pine_write_params(body->disposition.parameter, so))
4558 return(pwbh_finish(0, so));
4560 if(!so_puts(so, "\015\012"))
4561 return(pwbh_finish(0, so));
4564 /* copy out of so, a line at a time (or less than a K)
4565 * and send it down the pike
4567 so_seek(so, 0L, 0);
4568 i = 0;
4569 while(so_readc(&c, so))
4570 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4571 tmp[i] = '\0';
4572 if((f && !(*f)(s, tmp))
4573 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4574 return(pwbh_finish(0, so));
4576 i = 0;
4579 /* Finally, write blank line */
4580 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4581 return(pwbh_finish(0, so));
4583 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4586 return(0);
4591 * pine_write_header - convert, encode (if needed) and
4592 * write "header-name: field-body"
4595 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4597 char *cv, *cs, *vp;
4598 int rv;
4600 cs = posting_characterset(val, NULL, HdrText);
4601 cv = utf8_to_charset(val, cs, 0);
4602 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4603 (unsigned char *) cv, cs);
4605 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4607 if(cv && cv != val)
4608 fs_give((void **) &cv);
4611 return(rv);
4616 * pine_write_param - convert, encode and write MIME header-field parameters
4619 pine_write_params(PARAMETER *param, STORE_S *so)
4621 for(; param; param = param->next){
4622 int rv;
4623 char *cv, *cs;
4624 extern const char *tspecials;
4626 cs = posting_characterset(param->value, NULL, HdrText);
4627 cv = utf8_to_charset(param->value, cs, 0);
4628 rv = (so_puts(so, "; ")
4629 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4631 if(cv && cv != param->value)
4632 fs_give((void **) &cv);
4634 if(!rv)
4635 return(0);
4638 return(1);
4644 lmc_body_header_line(char *line, int beginning)
4646 if(lmc.so && !lmc.all_written){
4647 if(beginning && lmc.text_only && lmc.text_written
4648 && (!struncmp(line, "content-type:", 13)
4649 || !struncmp(line, "content-transfer-encoding:", 26)
4650 || !struncmp(line, "content-disposition:", 20))){
4652 * "comment out" the real values since our comment isn't
4653 * likely the same type, disposition nor encoding...
4655 if(!so_puts(lmc.so, "X-"))
4656 return(FALSE);
4659 return(so_puts(lmc.so, line));
4662 return(TRUE);
4667 lmc_body_header_finish(void)
4669 if(lmc.so && !lmc.all_written){
4670 if(lmc.text_only && lmc.text_written
4671 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4672 return(FALSE);
4674 return(so_puts(lmc.so, "\015\012"));
4677 return(TRUE);
4683 pwbh_finish(int rv, STORE_S *so)
4685 if(so)
4686 so_give(&so);
4688 return(rv);
4693 * pine_free_body - c-client call wrapper so the body data pointer we
4694 * we're using in a way c-client doesn't know about
4695 * gets free'd appropriately.
4697 void
4698 pine_free_body(struct mail_bodystruct **body)
4701 * Preempt c-client's contents.text.data clean up since we've
4702 * usurped it's meaning for our own purposes...
4704 pine_free_body_data (*body);
4706 /* Then let c-client handle the rest... */
4707 mail_free_body(body);
4712 * pine_free_body_data - free pine's interpretations of the body part's
4713 * data pointer.
4715 void
4716 pine_free_body_data(struct mail_bodystruct *body)
4718 if(body){
4719 if(body->type == TYPEMULTIPART){
4720 PART *part = body->nested.part;
4721 do /* for each part */
4722 pine_free_body_data(&part->body);
4723 while ((part = part->next) != NULL); /* until done */
4725 else if(body->contents.text.data)
4726 so_give((STORE_S **) &body->contents.text.data);
4731 long
4732 send_body_size(struct mail_bodystruct *body)
4734 long l = 0L;
4735 PART *part;
4737 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4738 part = body->nested.part; /* first body part */
4739 do /* for each part */
4740 l += send_body_size(&part->body);
4741 while ((part = part->next) != NULL); /* until done */
4742 return(l);
4745 return(l + body->size.bytes);
4750 sent_percent(void)
4752 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4753 / send_bytes_to_send);
4754 return(MIN(i, 100));
4759 * pine_smtp_verbose_out - write
4761 void
4762 pine_smtp_verbose_out(char *s)
4764 #ifdef _WINDOWS
4765 LPTSTR slpt;
4766 #endif
4767 if(verbose_send_output && s){
4768 char *p, last = '\0';
4770 for(p = s; *p; p++)
4771 if(*p == '\015')
4772 *p = ' ';
4773 else
4774 last = *p;
4776 #ifdef _WINDOWS
4778 * The stream is opened in Unicode mode, so we need to fix the
4779 * argument to fputs.
4781 slpt = utf8_to_lptstr((LPSTR) s);
4782 if(slpt){
4783 _fputts(slpt, verbose_send_output);
4784 fs_give((void **) &slpt);
4787 if(last != '\012')
4788 _fputtc(L'\n', verbose_send_output);
4789 #else
4790 fputs(s, verbose_send_output);
4791 if(last != '\012')
4792 fputc('\n', verbose_send_output);
4793 #endif
4800 * pine_header_forbidden - is this name a "forbidden" header?
4802 * name - the header name to check
4803 * We don't allow user to change these.
4806 pine_header_forbidden(char *name)
4808 char **p;
4809 static char *forbidden_headers[] = {
4810 "sender",
4811 "x-sender",
4812 "x-x-sender",
4813 "date",
4814 "received",
4815 "message-id",
4816 "in-reply-to",
4817 "path",
4818 "resent-message-id",
4819 "resent-date",
4820 "resent-from",
4821 "resent-sender",
4822 "resent-to",
4823 "resent-cc",
4824 "resent-reply-to",
4825 "mime-version",
4826 "content-type",
4827 "x-priority",
4828 "user-agent",
4829 "list-help", /* rfc 2369, section 3 */
4830 "list-unsubscribe",
4831 "list-subscribe",
4832 "list-post",
4833 "list-owner",
4834 "list-archive",
4835 NULL
4838 for(p = forbidden_headers; *p; p++)
4839 if(!strucmp(name, *p))
4840 break;
4842 return((*p) ? 1 : 0);
4847 * hdr_is_in_list - is there a custom value for this header?
4849 * hdr - the header name to check
4850 * custom - the list to check in
4851 * Returns 1 if there is a custom value, 0 otherwise.
4854 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4856 PINEFIELD *pf;
4858 for(pf = custom; pf && pf->name; pf = pf->next)
4859 if(strucmp(pf->name, hdr) == 0)
4860 return 1;
4862 return 0;
4867 * count_custom_hdrs_pf - returns number of custom headers in arg
4868 * custom -- the list to be counted
4869 * only_nonstandard -- only count headers which aren't standard pine headers
4872 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4874 int ret = 0;
4876 for(; custom && custom->name; custom = custom->next)
4877 if(!only_nonstandard || !custom->standard)
4878 ret++;
4880 return(ret);
4885 * count_custom_hdrs_list - returns number of custom headers in arg
4888 count_custom_hdrs_list(char **list)
4890 char **p;
4891 char *q = NULL;
4892 char *name;
4893 char *t;
4894 char save;
4895 int ret = 0;
4897 if(list){
4898 for(p = list; (q = *p) != NULL; p++){
4899 if(q[0]){
4900 /* remove leading whitespace */
4901 name = skip_white_space(q);
4903 /* look for colon or space or end */
4904 for(t = name;
4905 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4906 ;/* do nothing */
4908 save = *t;
4909 *t = '\0';
4910 if(!pine_header_forbidden(name))
4911 ret++;
4913 *t = save;
4918 return(ret);
4923 * set_default_hdrval - put the user's default value for this header
4924 * into pf->textbuf.
4925 * setthis - the pinefield to be set
4926 * custom - where to look for the default
4928 CustomType
4929 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4931 PINEFIELD *pf;
4932 CustomType ret = NoMatch;
4934 if(!setthis || !setthis->name){
4935 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4936 return(ret);
4939 setthis->textbuf = NULL;
4941 for(pf = custom; pf && pf->name; pf = pf->next){
4942 if(strucmp(pf->name, setthis->name) != 0)
4943 continue;
4945 ret = pf->cstmtype;
4947 /* turn on editing */
4948 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4949 setthis->canedit = 1;
4951 if(pf->val)
4952 setthis->textbuf = cpystr(pf->val);
4955 if(!setthis->textbuf)
4956 setthis->textbuf = cpystr("");
4958 return(ret);
4963 * pine_header_standard - is this name a "standard" header?
4965 * name - the header name to check
4967 FieldType
4968 pine_header_standard(char *name)
4970 int i;
4972 /* check to see if this is a standard header */
4973 for(i = 0; pf_template[i].name; i++)
4974 if(!strucmp(name, pf_template[i].name))
4975 return(pf_template[i].type);
4977 return(TypeUnknown);
4982 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
4983 * Allocates space for each name and addr ptr.
4984 * Allocates space for default in textbuf, even if empty.
4986 * head - the first PINEFIELD to fill in
4987 * list - the list to parse
4989 void
4990 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
4992 char **p, *q, *t, *name, *value, save;
4993 PINEFIELD *pf;
4995 pf = head;
4997 if(list){
4998 for(p = list; (q = *p) != NULL; p++){
5000 if(q[0]){
5002 /* anything after leading whitespace? */
5003 if(!*(name = skip_white_space(q)))
5004 continue;
5006 /* look for colon or space or end */
5007 for(t = name;
5008 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5009 ;/* do nothing */
5011 /* if there is a space in the field-name, skip it */
5012 if(isspace((unsigned char)*t)){
5013 q_status_message1(SM_ORDER, 3, 3,
5014 _("Space not allowed in header name (%s)"),
5015 name);
5016 continue;
5019 save = *t;
5020 *t = '\0';
5022 /* Don't allow any of the forbidden headers. */
5023 if(pine_header_forbidden(name)){
5024 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5025 _("Not allowed to change header \"%s\""),
5026 name);
5028 *t = save;
5029 continue;
5032 if(pf){
5033 if(pine_header_standard(name) != TypeUnknown)
5034 pf->standard = 1;
5036 pf->name = cpystr(name);
5037 pf->type = FreeText;
5038 pf->cstmtype = cstmtype;
5039 pf->next = pf+1;
5041 #ifdef OLDWAY
5043 * Some mailers apparently break if we change
5044 * user@domain into Fred <user@domain> for
5045 * return-receipt-to,
5046 * so we'll just call this a FreeText field, too.
5049 * For now, all custom headers are FreeText except for
5050 * this one that we happen to know about. We might
5051 * have to add some syntax to the config option so that
5052 * people can tell us their custom header takes addresses.
5054 if(!strucmp(pf->name, "Return-Receipt-to")){
5055 pf->type = Address;
5056 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5057 *pf->addr = (ADDRESS *)NULL;
5059 #endif /* OLDWAY */
5061 *t = save;
5063 /* remove space between name and colon */
5064 value = skip_white_space(t);
5066 /* give them an alloc'd default, even if empty */
5067 pf->textbuf = cpystr((*value == ':')
5068 ? skip_white_space(++value) : "");
5069 /* The instruction to remove the double quotes existed in
5070 * pine, but it was removed in alpine, but I do not know
5071 * why, so we will restore it until we understand why it
5072 * was removed. Also see:
5073 * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=981781
5075 if(pf->textbuf && pf->textbuf[0]){
5076 removing_double_quotes(pf->textbuf);
5077 pf->val = cpystr(pf->textbuf);
5080 pf++;
5082 else
5083 *t = save;
5088 /* fix last next pointer */
5089 if(head && pf != head)
5090 (pf-1)->next = NULL;
5095 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5096 * setup except that it doesn't have default values.
5097 * Add those defaults if they exist in "list".
5099 * head - the first PINEFIELD to add a default to
5100 * list - the list to get the defaults from
5102 void
5103 add_defaults_from_list(PINEFIELD *head, char **list)
5105 char **p, *q, *t, *name, *value, save;
5106 PINEFIELD *pf;
5108 for(pf = head; pf && list; pf = pf->next){
5109 if(!pf->name)
5110 continue;
5112 for(p = list; (q = *p) != NULL; p++){
5114 if(q[0]){
5116 /* anything after leading whitespace? */
5117 if(!*(name = skip_white_space(q)))
5118 continue;
5120 /* look for colon or space or end */
5121 for(t = name;
5122 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5123 ;/* do nothing */
5125 /* if there is a space in the field-name, skip it */
5126 if(isspace((unsigned char)*t))
5127 continue;
5129 save = *t;
5130 *t = '\0';
5132 if(strucmp(name, pf->name) != 0){
5133 *t = save;
5134 continue;
5137 *t = save;
5140 * Found the right header. See if it has a default
5141 * value set.
5144 /* remove space between name and colon */
5145 value = skip_white_space(t);
5147 if(*value == ':'){
5148 char *defval;
5150 defval = skip_white_space(++value);
5151 if(defval && *defval){
5152 if(pf->val)
5153 fs_give((void **)&pf->val);
5155 pf->val = cpystr(defval);
5159 break; /* on to next pf */
5167 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5168 * fill in the defaults
5169 * Args - list -- The list to parse.
5170 * cstmtype -- Fill the in cstmtype field with this value
5172 PINEFIELD *
5173 parse_custom_hdrs(char **list, CustomType cstmtype)
5175 PINEFIELD *pfields;
5176 int i;
5179 * add one for possible use by fcc
5180 * What is this "possible use"? I don't see it. I don't
5181 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5183 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5184 pfields = (PINEFIELD *)fs_get((size_t) i);
5185 memset(pfields, 0, (size_t) i);
5187 /* set up the custom header pfields */
5188 customized_hdr_setup(pfields, list, cstmtype);
5190 return(pfields);
5195 * Combine the two lists of headers into one list which is allocated here
5196 * and freed by the caller. Eliminate duplicates with values from the role
5197 * taking precedence over values from the default.
5199 PINEFIELD *
5200 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5202 PINEFIELD *pfields, *pf, *pf2;
5203 int max_hdrs, i;
5205 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5206 count_custom_hdrs_pf(dflthdrs,0);
5208 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5209 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5211 pf = pfields;
5212 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5213 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5214 pf->type = pf2->type;
5215 pf->cstmtype = pf2->cstmtype;
5216 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5217 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5218 pf->standard = pf2->standard;
5219 pf->next = pf+1;
5220 pf++;
5223 /* if these aren't already there, add them */
5224 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5225 /* check for already there */
5226 for(i = 0;
5227 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5228 i++)
5231 if(!pfields[i].name){ /* this is a new one */
5232 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5233 pf->type = pf2->type;
5234 pf->cstmtype = pf2->cstmtype;
5235 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5236 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5237 pf->standard = pf2->standard;
5238 pf->next = pf+1;
5239 pf++;
5243 /* fix last next pointer */
5244 if(pf != pfields)
5245 (pf-1)->next = NULL;
5247 return(pfields);
5252 * free_customs - free misc. resources associated with custom header fields
5254 * pf - pointer to first custom field
5256 void
5257 free_customs(PINEFIELD *head)
5259 PINEFIELD *pf;
5261 for(pf = head; pf && pf->name; pf = pf->next){
5263 fs_give((void **)&pf->name);
5265 if(pf->val)
5266 fs_give((void **)&pf->val);
5268 /* only true for FreeText */
5269 if(pf->textbuf)
5270 fs_give((void **)&pf->textbuf);
5272 /* only true for Address */
5273 if(pf->addr && *pf->addr)
5274 mail_free_address(pf->addr);
5277 fs_give((void **)&head);
5282 * encode_whole_header
5284 * Returns 1 if whole value should be encoded
5285 * 0 to encode only within comments
5288 encode_whole_header(char *field, METAENV *header)
5290 int retval = 0;
5291 PINEFIELD *pf;
5293 if(field && (!strucmp(field, "Subject") ||
5294 !strucmp(field, "Comment") ||
5295 !struncmp(field, "X-", 2)))
5296 retval++;
5297 else if(field && *field && header && header->custom){
5298 for(pf = header->custom; pf && pf->name; pf = pf->next){
5300 if(!pf->standard && !strucmp(pf->name, field)){
5301 retval++;
5302 break;
5307 return(retval);
5311 /*----------------------------------------------------------------------
5312 Post news via NNTP or inews
5314 Args: env -- envelope of message to post
5315 body -- body of message to post
5317 Returns: -1 if failed or cancelled, 1 if succeeded
5319 WARNING: This call function has the side effect of writing the message
5320 to the lmc.so object.
5321 ----*/
5323 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5324 void (*pipecb_f)(PIPE_S *, int, void *))
5326 char *error_mess, error_buf[200], **news_servers;
5327 char **servers_to_use;
5328 int we_cancel = 0, server_no = 0, done_posting = 0;
5329 void *orig_822_output;
5330 BODY *bp = NULL;
5332 error_buf[0] = '\0';
5333 we_cancel = busy_cue("Posting news", NULL, 0);
5335 dprint((4, "Posting: [%s]\n",
5336 (header && header->env && header->env->newsgroups)
5337 ? header->env->newsgroups : "?"));
5339 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5340 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5341 && ps_global->VAR_NNTP_SERVER[0][0])){
5342 /*---------- NNTP server defined ----------*/
5343 error_mess = NULL;
5344 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5345 && alt_nntp_servers[0][0])
5346 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5349 * Install our rfc822 output routine
5351 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5352 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5353 (void *)post_rfc822_output);
5355 server_no = 0;
5356 news_servers = (char **)fs_get(2 * sizeof(char *));
5357 news_servers[1] = NULL;
5358 while(!done_posting && servers_to_use[server_no] &&
5359 servers_to_use[server_no][0]){
5360 news_servers[0] = servers_to_use[server_no];
5361 ps_global->noshow_error = 1;
5362 #ifdef DEBUG
5363 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5364 #else
5365 sending_stream = nntp_open(news_servers, 0L);
5366 #endif
5367 ps_global->noshow_error = 0;
5369 if(sending_stream != NULL) {
5370 unsigned short save_encoding, added_encoding;
5373 * Fake that we've got clearance from the transport agent
5374 * for 8bit transport for the benefit of our output routines...
5376 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5377 && (bp = first_text_8bit(body))){
5378 int i;
5380 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5383 if(i > ENCMAX){ /* no empty encoding slots! */
5384 bp = NULL;
5386 else {
5387 added_encoding = i;
5388 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5389 save_encoding = bp->encoding;
5390 bp->encoding = added_encoding;
5395 * Set global header pointer so we can get at it later...
5397 send_header = header;
5398 ps_global->noshow_error = 1;
5399 if(nntp_mail(sending_stream, header->env, body) == 0)
5400 snprintf(error_mess = error_buf, sizeof(error_buf),
5401 _("Error posting message: %s"),
5402 sending_stream->reply);
5403 else{
5404 done_posting = 1;
5405 error_buf[0] = '\0';
5406 error_mess = NULL;
5409 error_buf[sizeof(error_buf)-1] = '\0';
5410 smtp_close(sending_stream);
5411 ps_global->noshow_error = 0;
5412 sending_stream = NULL;
5413 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5414 body_encodings[added_encoding] = NULL;
5415 bp->encoding = save_encoding;
5418 } else {
5419 /*---- Open of NNTP connection failed ------ */
5420 snprintf(error_mess = error_buf, sizeof(error_buf),
5421 _("Error connecting to news server: %s"),
5422 ps_global->c_client_error);
5423 error_buf[sizeof(error_buf)-1] = '\0';
5424 dprint((1, error_buf));
5426 server_no++;
5428 fs_give((void **)&news_servers);
5429 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5430 } else {
5431 /*----- Post via local mechanism -------*/
5432 #ifdef _WINDOWS
5433 snprintf(error_mess = error_buf, sizeof(error_buf),
5434 _("Can't post, NNTP-server must be defined!"));
5435 #else /* UNIX */
5436 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5437 pipecb_f);
5438 #endif
5441 if(we_cancel)
5442 cancel_busy_cue(0);
5444 if(error_mess){
5445 if(lmc.so && !lmc.all_written)
5446 so_give(&lmc.so); /* clean up any fcc data */
5448 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5449 return(-1);
5452 lmc.all_written = 1;
5453 return(1);
5457 /* ----------------------------------------------------------------------
5458 Figure out command to start local SMTP agent
5460 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5462 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5464 ----*/
5465 char *
5466 smtp_command(char *errbuf, size_t errbuflen)
5468 #ifdef _WINDOWS
5469 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5470 && ps_global->VAR_SMTP_SERVER[0][0]))
5471 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5473 errbuf[errbuflen-1] = '\0';
5474 #else /* UNIX */
5475 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5476 #define SENDTMPLEN 256
5477 char tmp[SENDTMPLEN];
5478 /* SENDTMPLEN == sizeof(tmp) */
5479 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
5480 (SENDTMPLEN-3)/2, SENDMAILFLAGS);
5481 return(cpystr(tmp));
5482 # else
5483 strncpy(errbuf, _("No default posting command."), errbuflen);
5484 errbuf[errbuflen-1] = '\0';
5485 # endif
5486 #endif
5488 return(NULL);
5493 #ifndef _WINDOWS
5494 /*----------------------------------------------------------------------
5495 Hand off given message to local posting agent
5497 Args: envelope -- The envelope for the BCC and debugging
5498 header -- The text of the message header
5499 errbuf -- buffer for reporting errors (assumed non-NULL)
5500 len -- Length of errbuf
5502 ----*/
5504 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5505 char *errbuf, size_t len,
5506 char **alt_smtp_servers,
5507 void (*bigresult_f) (char *, int),
5508 void (*pipecb_f)(PIPE_S *, int, void *))
5510 #ifdef DF_SENDMAIL_PATH
5511 char cmd_buf[256];
5512 #endif
5513 char *cmd = NULL;
5516 * A bit of complicated policy implemented here.
5517 * There are two posting variables sendmail-path and smtp-server.
5518 * Precedence is in that order.
5519 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5520 * Precedence is in that order.
5521 * Said differently, the order goes something like what's below.
5523 * NOTE: the fixed/command-line/user precedence handling is also
5524 * indicated by what's pointed to by ps_global->VAR_*, but since
5525 * that also includes the global defaults, it's not sufficient.
5528 if(ps_global->FIX_SENDMAIL_PATH
5529 && ps_global->FIX_SENDMAIL_PATH[0]){
5530 cmd = ps_global->FIX_SENDMAIL_PATH;
5532 else if(!(ps_global->FIX_SMTP_SERVER
5533 && ps_global->FIX_SMTP_SERVER[0])){
5534 if(ps_global->COM_SENDMAIL_PATH
5535 && ps_global->COM_SENDMAIL_PATH[0]){
5536 cmd = ps_global->COM_SENDMAIL_PATH;
5538 else if(!(ps_global->COM_SMTP_SERVER
5539 && ps_global->COM_SMTP_SERVER[0])
5540 && !(alt_smtp_servers
5541 && alt_smtp_servers[0]
5542 && alt_smtp_servers[0][0])){
5543 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5544 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5545 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5546 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5547 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5548 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5549 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5550 else
5551 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5553 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5554 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5555 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5556 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5557 if(ps_global->GLO_SENDMAIL_PATH
5558 && ps_global->GLO_SENDMAIL_PATH[0]){
5559 cmd = ps_global->GLO_SENDMAIL_PATH;
5561 #ifdef DF_SENDMAIL_PATH
5563 * This defines the default method of posting. So,
5564 * unless we're told otherwise use it...
5566 else if(!(ps_global->GLO_SMTP_SERVER
5567 && ps_global->GLO_SMTP_SERVER[0])){
5568 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5569 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5571 #endif
5576 *errbuf = '\0';
5577 if(cmd){
5578 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5580 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5581 return(1);
5583 else
5584 return(0);
5589 /*----------------------------------------------------------------------
5590 Hand off given message to local posting agent
5592 Args: envelope -- The envelope for the BCC and debugging
5593 header -- The text of the message header
5594 errbuf -- buffer for reporting errors (assumed non-NULL)
5595 errbuflen -- Length of errbuf
5597 Fork off mailer process and pipe the message into it
5598 Called to post news via Inews when NNTP is unavailable
5600 ----*/
5601 char *
5602 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5603 size_t errbuflen,
5604 void (*bigresult_f) (char *, int),
5605 void (*pipecb_f)(PIPE_S *, int, void *))
5607 char *err = NULL;
5608 #ifdef SENDNEWS
5609 char *s;
5610 char tmp[200];
5612 if((s = strstr(header->env->date," (")) != NULL) /* fix the date format for news */
5613 *s = '\0';
5615 if((err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f))){
5616 strncpy(tmp, err, sizeof(tmp)-1);
5617 tmp[sizeof(tmp)-1] = '\0';
5618 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5619 SENDNEWS, tmp);
5622 if(s)
5623 *s = ' '; /* restore the date */
5625 #else /* !SENDNEWS */ /* this is the default case */
5626 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5627 err[errbuflen-1] = '\0';
5628 #endif /* !SENDNEWS */
5629 return(err);
5634 /*----------------------------------------------------------------------
5635 Hand off message to local MTA; it parses recipients from 822 header
5637 Args: header -- struct containing header data
5638 body -- struct containing message body data
5639 cmd -- command to use for handoff (%s says where file should go)
5640 errs -- pointer to buf to hold errors
5642 ----*/
5643 char *
5644 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5645 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5646 void (*pipecb_f)(PIPE_S *, int, void *))
5648 char *result = NULL;
5649 PIPE_S *pipe;
5651 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5653 if((pipe = open_system_pipe(cmd, &result, NULL,
5654 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5655 0, pipecb_f, pipe_report_error)) != NULL){
5656 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5657 (TCPSTREAM *) pipe)){
5658 strncpy(errs, _("Error posting."), errslen-1);
5659 errs[errslen-1] = '\0';
5662 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5663 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5664 if(result && bigresult_f)
5665 (*bigresult_f)(result, CM_BR_ERROR);
5668 else
5669 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5671 if(result){
5672 our_unlink(result);
5673 fs_give((void **)&result);
5676 return(*errs ? errs : NULL);
5681 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5682 * pipes rather than a tcp stream
5684 long
5685 pine_pipe_soutr_nl (void *stream, char *s)
5687 long rv = T;
5688 char *p;
5689 size_t n;
5691 while(*s && rv){
5692 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0){
5693 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5694 if(rv < 0){
5695 if(errno != EINTR){
5696 rv = 0;
5697 break;
5700 else{
5701 s += rv;
5702 n -= rv;
5706 if(p && rv){
5707 s = p + 2; /* write UNIX EOL */
5708 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5709 if(rv < 0 && errno != EINTR){
5710 rv = 0;
5711 break;
5714 else
5715 break;
5718 return(rv);
5720 #endif
5723 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5727 * helpful def's
5729 #define S(X) ((PIPE_S *)(X))
5730 #define GETBUFLEN (4 * MAILTMPLEN)
5733 void *
5734 piped_smtp_open (char *host, char *service, long unsigned int port)
5736 char *postcmd;
5737 void *rv = NULL;
5739 if(strucmp(host, "localhost")){
5740 char tmp[MAILTMPLEN];
5741 /* MAILTMPLEN = sizeof(tmp) */
5742 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5743 MAILTMPLEN-50, host);
5744 tmp[sizeof(tmp)-1] = '\0';
5745 mm_log(tmp, ERROR);
5747 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5748 rv = open_system_pipe(postcmd, NULL, NULL,
5749 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5750 0, NULL, pipe_report_error);
5751 fs_give((void **) &postcmd);
5753 else
5754 mm_log(ps_global->c_client_error, ERROR);
5756 return(rv);
5760 void *
5761 piped_aopen (NETMBX *mb, char *service, char *user)
5763 return(NULL);
5768 * piped_soutr - Replacement for tcp_soutr that writes one of our
5769 * pipes rather than a tcp stream
5771 long
5772 piped_soutr (void *stream, char *s)
5774 return(piped_sout(stream, s, strlen(s)));
5779 * piped_sout - Replacement for tcp_soutr that writes one of our
5780 * pipes rather than a tcp stream
5782 long
5783 piped_sout (void *stream, char *s, long unsigned int size)
5785 int i, o;
5787 if(S(stream)->out.d < 0)
5788 return(0L);
5790 if((i = (int) size) != 0){
5791 while((o = write(S(stream)->out.d, s, i)) != i)
5792 if(o < 0){
5793 if(errno != EINTR){
5794 piped_abort(stream);
5795 return(0L);
5798 else{
5799 s += o; /* try again, fix up counts */
5800 i -= o;
5804 return(1L);
5809 * piped_getline - Replacement for tcp_getline that reads one
5810 * of our pipes rather than a tcp pipe
5812 * C-client expects that the \r\n will be stripped off.
5814 char *
5815 piped_getline (void *stream)
5817 static int cnt;
5818 static char *ptr;
5819 int n, m;
5820 char *ret, *s, *sp, c = '\0', d;
5822 if(S(stream)->in.d < 0)
5823 return(NULL);
5825 if(!S(stream)->tmp){ /* initialize! */
5826 /* alloc space to collect input (freed in close_system_pipe) */
5827 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5828 memset(S(stream)->tmp, 0, GETBUFLEN);
5829 cnt = -1;
5832 while(cnt < 0){
5833 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5834 if(errno != EINTR){
5835 piped_abort(stream);
5836 return(NULL);
5839 if(cnt == 0){
5840 piped_abort(stream);
5841 return(NULL);
5844 ptr = S(stream)->tmp;
5847 s = ptr;
5848 n = 0;
5849 while(cnt--){
5850 d = *ptr++;
5851 if((c == '\015') && (d == '\012')){
5852 ret = (char *)fs_get (n--);
5853 memcpy(ret, s, n);
5854 ret[n] = '\0';
5855 return(ret);
5858 n++;
5859 c = d;
5861 /* copy partial string from buffer */
5862 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5863 /* get more data */
5864 while(cnt < 0){
5865 memset(S(stream)->tmp, 0, GETBUFLEN);
5866 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5867 if(errno != EINTR){
5868 fs_give((void **) &ret);
5869 piped_abort(stream);
5870 return(NULL);
5873 if(cnt == 0){
5874 if(n > 0)
5875 ret[n-1] = '\0'; /* to try to get error message logged */
5876 else{
5877 piped_abort(stream);
5878 return(NULL);
5882 ptr = S(stream)->tmp;
5885 if(c == '\015' && *ptr == '\012'){
5886 ptr++;
5887 cnt--;
5888 ret[n - 1] = '\0'; /* tie off string with null */
5890 else if ((s = piped_getline(stream)) != NULL) {
5891 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5892 memcpy(ret, sp, n); /* copy first part */
5893 memcpy(ret + n, s, m); /* and second part */
5894 fs_give((void **) &sp); /* flush first part */
5895 fs_give((void **) &s); /* flush second part */
5896 ret[n + m] = '\0'; /* tie off string with null */
5899 return(ret);
5904 * piped_close - Replacement for tcp_close that closes pipes to our
5905 * child rather than a tcp connection
5907 void
5908 piped_close(void *stream)
5911 * Uninstall our hooks into smtp_send since it's being used by
5912 * the nntp driver as well...
5914 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5919 * piped_abort - close down the pipe we were using to post
5921 void
5922 piped_abort(void *stream)
5924 if(S(stream)->in.d >= 0){
5925 close(S(stream)->in.d);
5926 S(stream)->in.d = -1;
5929 if(S(stream)->out.d){
5930 close(S(stream)->out.d);
5931 S(stream)->out.d = -1;
5936 char *
5937 piped_host(void *stream)
5939 return(ps_global->hostname ? ps_global->hostname : "localhost");
5943 unsigned long
5944 piped_port(void *stream)
5946 return(0L);