* clear out some warnings by gcc 9.3.1.
[alpine.git] / pith / send.c
blob8d4d419a65f669416350eed25032a9c7be5b69dd
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/send.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/store.h"
24 #include "../pith/mimedesc.h"
25 #include "../pith/context.h"
26 #include "../pith/status.h"
27 #include "../pith/folder.h"
28 #include "../pith/bldaddr.h"
29 #include "../pith/pipe.h"
30 #include "../pith/mailview.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/list.h"
33 #include "../pith/filter.h"
34 #include "../pith/reply.h"
35 #include "../pith/addrstring.h"
36 #include "../pith/rfc2231.h"
37 #include "../pith/stream.h"
38 #include "../pith/util.h"
39 #include "../pith/adrbklib.h"
40 #include "../pith/options.h"
41 #include "../pith/busy.h"
42 #include "../pith/text.h"
43 #include "../pith/imap.h"
44 #include "../pith/ablookup.h"
45 #include "../pith/sort.h"
46 #include "../pith/smime.h"
48 #include "../c-client/smtp.h"
49 #include "../c-client/nntp.h"
52 /* this is used in pine_send and pine_simple_send */
53 /* name::type::canedit::writehdr::localcopy::rcptto */
54 PINEFIELD pf_template[] = {
55 {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
56 {"From", Address, 0, 1, 1, 0},
57 {"Reply-To", Address, 0, 1, 1, 0},
58 {TONAME, Address, 1, 1, 1, 1},
59 {CCNAME, Address, 1, 1, 1, 1},
60 {"bcc", Address, 1, 0, 1, 1},
61 {"Newsgroups", FreeText, 1, 1, 1, 0},
62 {"Fcc", Fcc, 1, 0, 0, 0},
63 {"Lcc", Address, 1, 0, 1, 1},
64 {"Attchmnt", Attachment, 1, 1, 1, 0},
65 {SUBJNAME, Subject, 1, 1, 1, 0},
66 {"References", FreeText, 0, 1, 1, 0},
67 {"Date", FreeText, 0, 1, 1, 0},
68 {"In-Reply-To", FreeText, 0, 1, 1, 0},
69 {"Message-ID", FreeText, 0, 1, 1, 0},
70 {PRIORITYNAME, FreeText, 0, 1, 1, 0},
71 {"User-Agent", FreeText, 0, 1, 1, 0},
72 {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
73 {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
74 {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
75 {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
76 {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
77 {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
78 {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
79 {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
80 {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
81 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
82 {"X-X-Sender", Address, 0, 1, 1, 0},
83 #endif
84 {NULL, FreeText}
88 PRIORITY_S priorities[] = {
89 {1, "Highest"},
90 {2, "High"},
91 {3, "Normal"},
92 {4, "Low"},
93 {5, "Lowest"},
94 {0, NULL}
98 #define ctrl(c) ((c) & 0x1f)
100 /* which message part to test for xliteration */
101 typedef enum {MsgBody, HdrText} MsgPart;
105 * Internal prototypes
107 long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
108 int l_flush_net(int);
109 int l_putc(int);
110 int pine_write_header_line(char *, char *, STORE_S *);
111 int pine_write_params(PARAMETER *, STORE_S *);
112 char *tidy_smtp_mess(char *, char *, char *, size_t);
113 int lmc_body_header_line(char *, int);
114 int lmc_body_header_finish(void);
115 int pwbh_finish(int, STORE_S *);
116 int sent_percent(void);
117 unsigned short *setup_avoid_table(void);
118 #ifndef _WINDOWS
119 int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
120 void (*)(PIPE_S *, int, void *));
121 char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
122 void (*)(PIPE_S *, int, void *));
123 char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
124 void (*)(PIPE_S *, int, void *));
125 long pine_pipe_soutr_nl(void *, char *);
126 #endif
127 char *smtp_command(char *, size_t);
128 void *piped_smtp_open(char *, char *, unsigned long);
129 void *piped_aopen(NETMBX *, char *, char *);
130 long piped_soutr(void *, char *);
131 long piped_sout(void *, char *, unsigned long);
132 char *piped_getline(void *);
133 void piped_close(void *);
134 void piped_abort(void *);
135 char *piped_host(void *);
136 unsigned long piped_port(void *);
137 char *posting_characterset(void *, char *, MsgPart);
138 int body_is_translatable(void *, char *);
139 int text_is_translatable(void *, char *);
140 int dummy_putc(int);
141 unsigned long *init_charsetchecker(char *);
142 int representable_in_charset(unsigned long, char *);
143 char *most_preferred_charset(unsigned long);
146 * Storage object where the FCC (or postponed msg) is to be written.
147 * This is amazingly bogus. Much work was done to put messages
148 * together and encode them as they went to the tmp file for sendmail
149 * or into the SMTP slot (especially for for DOS, to prevent a temporary
150 * file (and needlessly copying the message).
152 * HOWEVER, since there's no piping into c-client routines
153 * (particularly mail_append() which copies the fcc), the fcc will have
154 * to be copied to disk. This global tells pine's copy of the rfc822
155 * output functions where to also write the message bytes for the fcc.
156 * With piping in the c-client we could just have two pipes to shove
157 * down rather than messing with damn copies. FIX THIS!
159 * The function open_fcc, locates the actual folder and creates it if
160 * requested before any mailing or posting is done.
162 struct local_message_copy lmc;
166 * Locally global pointer to stream used for sending/posting.
167 * It's also used to indicate when/if we write the Bcc: field in
168 * the header.
170 static SENDSTREAM *sending_stream = NULL;
173 static struct hooks {
174 void *rfc822_out; /* Message outputter */
175 } sending_hooks;
178 static FILE *verbose_send_output = NULL;
179 static long send_bytes_sent, send_bytes_to_send;
180 static METAENV *send_header = NULL;
183 * Hooks for prompts and confirmations
185 int (*pith_opt_daemon_confirm)(void);
188 static NETDRIVER piped_io = {
189 piped_smtp_open, /* open a connection */
190 piped_aopen, /* open an authenticated connection */
191 piped_getline, /* get a line */
192 NULL, /* get a buffer */
193 piped_soutr, /* output pushed data */
194 piped_sout, /* output string */
195 piped_close, /* close connection */
196 piped_host, /* return host name */
197 piped_host, /* remotehost */
198 piped_port, /* return port number */
199 piped_host /* return local host (NOTE: same as host!) */
204 * Since c-client preallocates, it's necessary here to define a limit
205 * such that we don't blow up in c-client (see rfc822_address_line()).
207 #define MAX_SINGLE_ADDR MAILTMPLEN
209 #define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
213 * postponed_stream - return stream associated with postponed messages
214 * in argument.
217 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
219 MAILSTREAM *stream = NULL;
220 CONTEXT_S *p_cntxt = NULL;
221 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
222 int exists;
224 if(!(streamp && mbox))
225 return(0);
227 *streamp = NULL;
230 * find default context to look for folder...
232 * The "mbox" is assumed to be local if we're given what looks
233 * like an absolute path. This is different from Goto/Save
234 * where we do a lot of work to interpret paths relative to the
235 * server. This reason is to support all the pre-4.00 pinerc'
236 * that specified a path and because there's yet to be a way
237 * in c-client to specify otherwise in the face of a remote
238 * context.
240 if(!is_absolute_path(mbox)
241 && !(p_cntxt = default_save_context(ps_global->context_list)))
242 p_cntxt = ps_global->context_list;
244 /* check to see if the folder exists, the user wants to continue
245 * and that we can actually read something in...
247 exists = folder_name_exists(p_cntxt, mbox, &fullname);
248 if(fullname)
249 mbox = fullname;
251 if(exists & FEX_ISFILE){
252 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
253 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
255 * The mbox is relative to the home directory.
256 * Make it absolute so we can compare it to
257 * stream->mailbox.
259 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
260 SIZEOF_20KBUF);
261 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
262 tmp[sizeof(tmp)-1] = '\0';
265 if((stream = ps_global->mail_stream)
266 && !(stream->mailbox
267 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
268 || (*tmp == '{'
269 && same_stream(tmp, stream)
270 && (p = strchr(tmp, '}'))
271 && (q = strchr(stream->mailbox,'}'))
272 && !strcmp(p + 1, q + 1)))))
273 stream = NULL;
275 if(!stream){
276 stream = context_open(p_cntxt, NULL, mbox,
277 SP_USEPOOL|SP_TEMPUSE, NULL);
278 if(stream && !stream->halfopen){
279 if(stream->nmsgs > 0)
280 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
282 if(checknmsgs && stream->nmsgs < 1){
283 pine_mail_close(stream);
284 exists = 0;
285 stream = NULL;
288 else{
289 q_status_message2(SM_ORDER | SM_DING, 3, 3,
290 _("Can't open %s mailbox: %s"), type, mbox);
291 if(stream)
292 pine_mail_close(stream);
294 exists = 0;
295 stream = NULL;
299 else{
300 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
301 q_status_message1(SM_ORDER | SM_DING, 3, 3,
302 _("%s message folder doesn't exist!"), type);
306 if(fullname)
307 fs_give((void **) &fullname);
309 *streamp = stream;
311 return(exists);
316 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
317 struct mail_bodystruct **body, char **fcc, char **lcc,
318 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
319 ACTION_S **role, int flags, STORE_S *so)
321 MAILSTREAM *stream;
322 ENVELOPE *e = NULL;
323 BODY *b;
324 PART *part;
325 PINEFIELD *pf;
326 gf_io_t pc;
327 char *extras, **fields, **values, *p;
328 char *hdrs[2], *h, *charset;
329 char **smtp_servers = NULL, **nntp_servers = NULL;
330 int i, pine_generated = 0, our_replyto = 0;
331 int added_to_role = 0;
332 unsigned gbpt_flags = GBPT_NONE;
333 MESSAGECACHE *mc;
335 if(!(streamp && *streamp))
336 return(redraft_cleanup(streamp, TRUE, flags));
338 stream = *streamp;
340 if(flags & REDRAFT_HTML)
341 gbpt_flags |= GBPT_HTML_OK;
343 /* grok any user-defined or non-c-client headers */
344 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
347 * The custom headers to look for in the suspended message should
348 * have been stored in the X-Our-Headers header. So first we get
349 * that list. If we can't find it (version that stored the
350 * message < 4.30) then we use the global custom list.
352 hdrs[0] = OUR_HDRS_LIST;
353 hdrs[1] = NULL;
354 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
355 int commas = 0;
356 char **list;
357 char *hdrval = NULL;
359 if((hdrval = strindex(h, ':')) != NULL){
360 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
361 hdrval++)
365 /* count elements in list */
366 for(p = hdrval; p && *p; p++)
367 if(*p == ',')
368 commas++;
370 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
372 *custom = parse_custom_hdrs(list, Replace);
373 add_defaults_from_list(*custom,
374 ps_global->VAR_CUSTOM_HDRS);
375 free_list_array(&list);
378 if(*custom && !(*custom)->name){
379 free_customs(*custom);
380 *custom = NULL;
383 fs_give((void **)&h);
386 if(!*custom)
387 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
389 #define INDEX_FCC 0
390 #define INDEX_POSTERR 1
391 #define INDEX_REPLYUID 2
392 #define INDEX_REPLYMBOX 3
393 #define INDEX_SMTP 4
394 #define INDEX_NNTP 5
395 #define INDEX_CURSORPOS 6
396 #define INDEX_OUR_REPLYTO 7
397 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
398 #define FIELD_COUNT 9
400 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
403 * Having these two fields separated isn't the slickest, but
404 * converting the pointer array for fetchheader_lines() to
405 * a list of structures or some such for simple_header_parse()
406 * is too goonie. We could do something like re-use c-client's
407 * PARAMETER struct which is a simple char * pairing, but that
408 * doesn't make sense to pass to fetchheader_lines()...
410 fields = (char **) fs_get((size_t) i * sizeof(char *));
411 values = (char **) fs_get((size_t) i * sizeof(char *));
412 memset(fields, 0, (size_t) i * sizeof(char *));
413 memset(values, 0, (size_t) i * sizeof(char *));
415 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
416 fields[++i] = "X-Post-Error"; /* posting errors too */
417 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
418 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
419 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
420 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
421 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
422 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
423 fields[++i] = "Lcc"; /* Lcc: too... */
424 if(++i != FIELD_COUNT)
425 alpine_panic("Fix FIELD_COUNT");
427 for(pf = *custom; pf && pf->name; pf = pf->next)
428 if(!pf->standard)
429 fields[i++] = pf->name; /* assign custom fields */
431 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
432 simple_header_parse(extras, fields, values);
433 fs_give((void **) &extras);
436 * translate RFC 1522 strings,
437 * starting with "Lcc" field
439 for(i = INDEX_LCC; fields[i]; i++)
440 if(values[i]){
441 size_t len;
442 char *bufp, *biggerbuf = NULL;
444 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
445 len++;
446 biggerbuf = (char *)fs_get(len * sizeof(char));
447 bufp = biggerbuf;
449 else{
450 bufp = tmp_20k_buf;
451 len = SIZEOF_20KBUF;
454 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
456 if(p == tmp_20k_buf){
457 fs_give((void **)&values[i]);
458 values[i] = cpystr(p);
461 if(biggerbuf)
462 fs_give((void **)&biggerbuf);
465 for(pf = *custom, i = FIELD_COUNT;
466 pf && pf->name;
467 pf = pf->next){
468 if(pf->standard){
470 * Because the value is already in the envelope.
472 pf->cstmtype = NoMatch;
473 continue;
476 if(values[i]){ /* use this instead of default */
477 if(pf->textbuf)
478 fs_give((void **)&pf->textbuf);
480 pf->textbuf = values[i]; /* freed in pine_send! */
482 else if(pf->textbuf) /* was erased before postpone */
483 fs_give((void **)&pf->textbuf);
485 i++;
488 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
489 pine_generated = 1; /* we put it there? */
492 * Since c-client fills in the reply_to field in the envelope
493 * even if there isn't a Reply-To header in the message we
494 * have to work around that. When we postpone we add
495 * a second header that has value "Empty" if there really
496 * was a Reply-To and it was empty. It has the
497 * value "Full" if we put the Reply-To contents there
498 * intentionally (and it isn't empty).
500 if(values[INDEX_OUR_REPLYTO]){
501 if(values[INDEX_OUR_REPLYTO][0] == 'E')
502 our_replyto = 'E'; /* we put an empty one there */
503 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
504 our_replyto = 'F'; /* we put it there */
506 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
509 if(fcc) /* fcc: special case... */
510 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
511 else if(values[INDEX_FCC])
512 fs_give((void **) &values[INDEX_FCC]);
514 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
515 q_status_message(SM_ORDER|SM_DING, 4, 4,
516 values[INDEX_POSTERR]);
517 fs_give((void **) &values[INDEX_POSTERR]);
520 if(values[INDEX_REPLYUID]){
521 if(reply)
522 *reply = build_reply_uid(values[INDEX_REPLYUID]);
524 fs_give((void **) &values[INDEX_REPLYUID]);
526 if(values[INDEX_REPLYMBOX] && reply && *reply)
527 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
529 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
530 (*reply)->origmbox = cpystr((*reply)->mailbox);
533 if(values[INDEX_REPLYMBOX])
534 fs_give((void **) &values[INDEX_REPLYMBOX]);
536 if(values[INDEX_SMTP]){
537 char *q;
538 size_t cnt = 0;
541 * Turn the space delimited list of smtp servers into
542 * a char ** list.
544 p = values[INDEX_SMTP];
546 if(!*p || isspace((unsigned char) *p))
547 cnt++;
548 } while(*p++);
550 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
551 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
553 cnt = 0;
554 q = p = values[INDEX_SMTP];
556 if(!*p || isspace((unsigned char) *p)){
557 if(*p){
558 *p = '\0';
559 smtp_servers[cnt++] = cpystr(q);
560 *p = ' ';
561 q = p+1;
563 else
564 smtp_servers[cnt++] = cpystr(q);
566 } while(*p++);
568 fs_give((void **) &values[INDEX_SMTP]);
571 if(values[INDEX_NNTP]){
572 char *q;
573 size_t cnt = 0;
576 * Turn the space delimited list of smtp nntp into
577 * a char ** list.
579 p = values[INDEX_NNTP];
581 if(!*p || isspace((unsigned char) *p))
582 cnt++;
583 } while(*p++);
585 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
586 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
588 cnt = 0;
589 q = p = values[INDEX_NNTP];
591 if(!*p || isspace((unsigned char) *p)){
592 if(*p){
593 *p = '\0';
594 nntp_servers[cnt++] = cpystr(q);
595 *p = ' ';
596 q = p+1;
598 else
599 nntp_servers[cnt++] = cpystr(q);
601 } while(*p++);
603 fs_give((void **) &values[INDEX_NNTP]);
606 if(values[INDEX_CURSORPOS]){
608 * The redraft cursor position is written as two fields
609 * separated by a space. First comes the name of the
610 * header field we're in, or just a ":" if we're in the
611 * body. Then comes the offset into that header or into
612 * the body.
614 if(redraft_pos){
615 char *q1, *q2;
617 *redraft_pos
618 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
619 (*redraft_pos)->offset = 0L;
621 q1 = skip_white_space(values[INDEX_CURSORPOS]);
622 if(*q1 && (q2 = strindex(q1, SPACE))){
623 *q2 = '\0';
624 (*redraft_pos)->hdrname = cpystr(q1);
625 q1 = skip_white_space(q2+1);
626 if(*q1)
627 (*redraft_pos)->offset = atol(q1);
629 else
630 (*redraft_pos)->hdrname = cpystr(":");
633 fs_give((void **) &values[INDEX_CURSORPOS]);
636 if(lcc)
637 *lcc = values[INDEX_LCC];
638 else
639 fs_give((void **) &values[INDEX_LCC]);
642 fs_give((void **)&fields);
643 fs_give((void **)&values);
645 *outgoing = copy_envelope(e);
648 * If the postponed message has a From which is different from
649 * the default, it is either because allow-changing-from is on
650 * or because there was a role with a from that allowed it to happen.
651 * If allow-changing-from is not on, put this back in a role
652 * so that it will be allowed again in pine_send.
654 if(role && *role == NULL &&
655 !ps_global->never_allow_changing_from &&
656 *outgoing){
658 * Now check to see if the from is different from default from.
660 ADDRESS *deffrom;
662 deffrom = generate_from();
663 if(!((*outgoing)->from &&
664 address_is_same(deffrom, (*outgoing)->from) &&
665 ((!(deffrom->personal && deffrom->personal[0]) &&
666 !((*outgoing)->from->personal &&
667 (*outgoing)->from->personal[0])) ||
668 (deffrom->personal && (*outgoing)->from->personal &&
669 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
671 *role = (ACTION_S *)fs_get(sizeof(**role));
672 memset((void *)*role, 0, sizeof(**role));
673 if(!(*outgoing)->from)
674 (*outgoing)->from = mail_newaddr();
676 (*role)->from = (*outgoing)->from;
677 (*outgoing)->from = NULL;
678 added_to_role++;
681 mail_free_address(&deffrom);
685 * Look at each empty address and see if the user has specified
686 * a default for that field or not. If they have, that means
687 * they have erased it before postponing, so they won't want
688 * the default to come back. If they haven't specified a default,
689 * then the default should be generated in pine_send. We prevent
690 * the default from being assigned by assigning an empty address
691 * to the variable here.
693 * BUG: We should do this for custom Address headers, too, but
694 * there isn't such a thing yet.
696 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
697 (*outgoing)->to = mail_newaddr();
698 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
699 (*outgoing)->cc = mail_newaddr();
700 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
701 (*outgoing)->bcc = mail_newaddr();
703 if(our_replyto == 'E'){
704 /* user erased reply-to before postponing */
705 if((*outgoing)->reply_to)
706 mail_free_address(&(*outgoing)->reply_to);
709 * If empty is not the normal default, make the outgoing
710 * reply_to be an empty address. If it is default, leave it
711 * as NULL and the default will be used.
713 if(hdr_is_in_list("reply-to", *custom)){
714 PINEFIELD pf;
716 pf.name = "reply-to";
717 set_default_hdrval(&pf, *custom);
718 if(pf.textbuf){
719 if(pf.textbuf[0]) /* empty is not default */
720 (*outgoing)->reply_to = mail_newaddr();
722 fs_give((void **)&pf.textbuf);
726 else if(our_replyto == 'F'){
727 int add_to_role = 0;
730 * The reply-to is real. If it is different from the default
731 * reply-to, put it in the role so that it will show up when
732 * the user edits.
734 if(hdr_is_in_list("reply-to", *custom)){
735 PINEFIELD pf;
736 char *str;
738 pf.name = "reply-to";
739 set_default_hdrval(&pf, *custom);
740 if(pf.textbuf && pf.textbuf[0]){
741 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
742 if(!strcmp(str, pf.textbuf)){
743 /* standard value, leave it alone */
746 else /* not standard, put in role */
747 add_to_role++;
749 fs_give((void **)&str);
752 else /* not standard, put in role */
753 add_to_role++;
755 if(pf.textbuf)
756 fs_give((void **)&pf.textbuf);
758 else /* not standard, put in role */
759 add_to_role++;
761 if(add_to_role && role && (*role == NULL || added_to_role)){
762 if(*role == NULL){
763 added_to_role++;
764 *role = (ACTION_S *)fs_get(sizeof(**role));
765 memset((void *)*role, 0, sizeof(**role));
768 (*role)->replyto = (*outgoing)->reply_to;
769 (*outgoing)->reply_to = NULL;
772 else{
773 /* this is a bogus c-client generated replyto */
774 if((*outgoing)->reply_to)
775 mail_free_address(&(*outgoing)->reply_to);
778 if((smtp_servers || nntp_servers)
779 && role && (*role == NULL || added_to_role)){
780 if(*role == NULL){
781 *role = (ACTION_S *)fs_get(sizeof(**role));
782 memset((void *)*role, 0, sizeof(**role));
785 if(smtp_servers)
786 (*role)->smtp = smtp_servers;
787 if(nntp_servers)
788 (*role)->nntp = nntp_servers;
791 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
792 (*outgoing)->subject = cpystr("");
794 if(!pine_generated){
796 * Now, this is interesting. We should have found
797 * the "fcc:" field if pine wrote the message being
798 * redrafted. Hence, we probably can't trust the
799 * "originator" type fields, so we'll blast them and let
800 * them get set later in pine_send. This should allow
801 * folks with custom or edited From's and such to still
802 * use redraft reasonably, without inadvertently sending
803 * messages that appear to be "From" others...
805 if((*outgoing)->from)
806 mail_free_address(&(*outgoing)->from);
809 * Ditto for Reply-To and Sender...
811 if((*outgoing)->reply_to)
812 mail_free_address(&(*outgoing)->reply_to);
814 if((*outgoing)->sender)
815 mail_free_address(&(*outgoing)->sender);
818 if(!pine_generated || !(flags & REDRAFT_DEL)){
821 * Generate a fresh message id for pretty much the same
822 * reason From and such got wacked...
823 * Also, if we're coming from a form letter, we need to
824 * generate a different id each time.
826 if((*outgoing)->message_id)
827 fs_give((void **)&(*outgoing)->message_id);
829 (*outgoing)->message_id = generate_message_id(role ? *role : NULL);
832 if(b && b->type != TYPETEXT){
833 if(b->type == TYPEMULTIPART){
834 if(strucmp(b->subtype, "mixed")){
835 q_status_message1(SM_INFO, 3, 4,
836 "Converting Multipart/%s to Multipart/Mixed",
837 b->subtype);
838 fs_give((void **)&b->subtype);
839 b->subtype = cpystr("mixed");
842 else{
843 q_status_message2(SM_ORDER | SM_DING, 3, 4,
844 "Unable to resume type %s/%s message",
845 body_types[b->type], b->subtype);
846 return(redraft_cleanup(streamp, TRUE, flags));
850 gf_set_so_writec(&pc, so);
852 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
853 *body = copy_body(NULL, b);
854 part = (*body)->nested.part;
855 part->body.contents.text.data = (void *)so;
856 set_mime_type_by_grope(&part->body);
857 if(part->body.type != TYPETEXT){
858 q_status_message2(SM_ORDER | SM_DING, 3, 4,
859 "Unable to resume; first part is non-text: %s/%s",
860 body_types[part->body.type],
861 part->body.subtype);
862 return(redraft_cleanup(streamp, TRUE, flags));
865 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
866 /* let outgoing routines decide on charset */
867 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
868 set_parameter(&part->body.parameter, "charset", NULL);
870 fs_give((void **) &charset);
873 ps_global->postpone_no_flow = 1;
875 get_body_part_text(stream, &b->nested.part->body,
876 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
877 ps_global->postpone_no_flow = 0;
879 if(!fetch_contents(stream, cont_msg, NULL, *body))
880 q_status_message(SM_ORDER | SM_DING, 3, 4,
881 _("Error including all message parts"));
883 else{
884 *body = mail_newbody();
885 (*body)->type = TYPETEXT;
886 if(b->subtype /* these types are transformed to text/plain */
887 && strucmp(b->subtype, "richtext")
888 && strucmp(b->subtype, "enriched")
889 && strucmp(b->subtype, "html"))
890 (*body)->subtype = cpystr(b->subtype);
892 if((charset = parameter_val(b->parameter,"charset")) != NULL){
893 /* let outgoing routines decide on charset */
894 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
895 fs_give((void **) &charset);
896 else{
897 (*body)->parameter = mail_newbody_parameter();
898 (*body)->parameter->attribute = cpystr("charset");
899 if(utf8_charset(charset)){
900 fs_give((void **) &charset);
901 (*body)->parameter->value = cpystr("UTF-8");
903 else
904 (*body)->parameter->value = charset;
908 (*body)->contents.text.data = (void *)so;
909 ps_global->postpone_no_flow = 1;
910 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
911 NULL, NULL, gbpt_flags);
912 ps_global->postpone_no_flow = 0;
915 gf_clear_so_writec(so);
917 /* We have what we want, blast this message... */
918 if((flags & REDRAFT_DEL)
919 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
920 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
921 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
923 else
924 return(redraft_cleanup(streamp, TRUE, flags));
926 return(redraft_cleanup(streamp, FALSE, flags));
930 /*----------------------------------------------------------------------
931 Clear deleted messages from given stream and expunge if necessary
933 Args: stream --
934 problem --
936 ----*/
938 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
940 MAILSTREAM *stream;
942 if(!(streamp && *streamp))
943 return(0);
945 if(!problem && streamp && (stream = *streamp)){
946 if(stream->nmsgs){
947 ps_global->expunge_in_progress = 1;
948 mail_expunge(stream); /* clean out deleted */
949 ps_global->expunge_in_progress = 0;
952 if(!stream->nmsgs){ /* close and delete folder */
953 int do_the_broach = 0;
954 char *mbox = NULL;
956 if(stream){
957 if(stream->original_mailbox && stream->original_mailbox[0])
958 mbox = cpystr(stream->original_mailbox);
959 else if(stream->mailbox && stream->mailbox[0])
960 mbox = cpystr(stream->mailbox);
963 /* if it is current, we have to change folders */
964 if(stream == ps_global->mail_stream)
965 do_the_broach++;
968 * This is the stream to the empty postponed-msgs folder.
969 * We are going to delete the folder in a second. It is
970 * probably preferable to unselect the mailbox and leave
971 * this stream open for re-use instead of actually closing it,
972 * so we do that if possible.
974 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
976 * This does the UNSELECT on the stream. A NULL
977 * return should mean that something went wrong and
978 * a mail_close already happened, so that should have
979 * cleaned things up in the callback.
981 if((stream=mail_open(stream, stream->mailbox,
982 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
983 /* now close it so it is put into the stream cache */
984 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
985 pine_mail_close(stream);
988 else
989 pine_mail_actually_close(stream);
991 *streamp = NULL;
993 if(do_the_broach){
994 ps_global->mail_stream = NULL; /* already closed above */
997 if(mbox && !pine_mail_delete(NULL, mbox))
998 q_status_message1(SM_ORDER|SM_DING, 3, 3,
999 /* TRANSLATORS: Arg is a mailbox name */
1000 _("Can't delete %s"), mbox);
1002 if(mbox)
1003 fs_give((void **) &mbox);
1007 return(!problem);
1011 /*----------------------------------------------------------------------
1012 Parse the given header text for any given fields
1014 Args: text -- Text to parse for fcc and attachments refs
1015 fields -- array of field names to look for
1016 values -- array of pointer to save values to, returned NULL if
1017 fields isn't in text.
1019 This function simply looks for the given fields in the given header
1020 text string.
1021 NOTE: newlines are expected CRLF, and we'll ignore continuations
1022 ----*/
1023 void
1024 simple_header_parse(char *text, char **fields, char **values)
1026 int i, n;
1027 char *p, *t;
1029 for(i = 0; fields[i]; i++)
1030 values[i] = NULL; /* clear values array */
1032 /*---- Loop until the end of the header ----*/
1033 for(p = text; *p; ){
1034 for(i = 0; fields[i]; i++) /* find matching field? */
1035 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1036 for(p += n + 1; *p; p++){ /* find start of value */
1037 if(*p == '\015' && *(p+1) == '\012'
1038 && !isspace((unsigned char) *(p+2)))
1039 break;
1041 if(!isspace((unsigned char) *p))
1042 break; /* order here is key... */
1045 if(!values[i]){ /* if we haven't already */
1046 values[i] = fs_get(strlen(text) + 1);
1047 values[i][0] = '\0'; /* alloc space for it */
1050 if(*p && *p != '\015'){ /* non-blank value. */
1051 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1052 while(*p){ /* check for cont'n lines */
1053 if(*p == '\015' && *(p+1) == '\012'){
1054 if(isspace((unsigned char) *(p+2))){
1055 p += 2;
1056 continue;
1058 else
1059 break;
1062 *t++ = *p++;
1065 *t = '\0';
1068 break;
1071 /* Skip to end of line, what ever it was */
1072 for(; *p ; p++)
1073 if(*p == '\015' && *(p+1) == '\012'){
1074 p += 2;
1075 break;
1081 /*----------------------------------------------------------------------
1082 build a fresh REPLY_S from the given string (see pine_send for format)
1084 Args: s -- "X-Reply-UID" header value
1086 Returns: filled in REPLY_S or NULL on parse error
1087 ----*/
1088 REPLY_S *
1089 build_reply_uid(char *s)
1091 char *p, *prefix = NULL, *val, *seq, *mbox;
1092 int i, nseq, forwarded = 0;
1093 REPLY_S *reply = NULL;
1095 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1096 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1097 if(*s == '('){
1098 if(*(p = s + 1) == ')'){
1099 forwarded = 1;
1101 else{
1102 for(; isdigit(*p); p++)
1105 if(*p == ' '){
1106 *p++ = '\0';
1108 if((i = atoi(s+1)) && i < strlen(p)){
1109 prefix = p;
1110 *(p += i) = '\0';
1113 else
1114 return(NULL);
1117 if(*++p == '(' && *++p){
1118 for(seq = p; isdigit(*p); p++)
1121 if(*p == ' '){
1122 *p++ = '\0';
1123 for(val = p; isdigit(*p); p++)
1126 if(*p == ' '){
1127 *p++ = '\0';
1129 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1130 && (p = strchr(p, ')')) && *(mbox = ++p)){
1131 imapuid_t *uidl;
1133 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1134 for(i = 0; i < nseq; i++)
1135 if((p = strchr(seq,',')) != NULL){
1136 *p = '\0';
1137 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1138 seq = ++p;
1139 else
1140 break;
1142 else if((p = strchr(seq, ')')) != NULL){
1143 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1144 i++;
1146 break;
1149 if(i == nseq){
1150 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1151 memset(reply, 0, sizeof(REPLY_S));
1152 reply->uid = 1;
1153 reply->data.uid.validity = strtoul(val, NULL, 10);
1154 if(forwarded)
1155 reply->forwarded = 1;
1156 else
1157 reply->prefix = cpystr(prefix);
1159 reply->mailbox = cpystr(mbox);
1160 uidl[nseq] = 0;
1161 reply->data.uid.msgs = uidl;
1163 else
1164 fs_give((void **) &uidl);
1171 return(reply);
1176 * pine_new_env - allocate a new METAENV and fill it in.
1178 METAENV *
1179 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1181 int cnt, i, stdcnt;
1182 char *p;
1183 PINEFIELD *pfields, *pf, **sending_order;
1184 METAENV *header;
1186 header = (METAENV *) fs_get(sizeof(METAENV));
1188 /* how many fields are there? */
1189 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1192 stdcnt = cnt;
1194 for(pf = custom; pf; pf = pf->next)
1195 cnt++;
1197 /* temporary PINEFIELD array */
1198 i = (cnt + 1) * sizeof(PINEFIELD);
1199 pfields = (PINEFIELD *)fs_get((size_t) i);
1200 memset(pfields, 0, (size_t) i);
1202 i = (cnt + 1) * sizeof(PINEFIELD *);
1203 sending_order = (PINEFIELD **)fs_get((size_t) i);
1204 memset(sending_order, 0, (size_t) i);
1206 header->env = outgoing;
1207 header->local = pfields;
1208 header->custom = custom;
1209 header->sending_order = sending_order;
1211 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1212 # define NN 4
1213 #else
1214 # define NN 3
1215 #endif
1217 /* initialize pfield */
1218 pf = pfields;
1219 for(i=0; i < stdcnt; i++, pf++){
1221 pf->name = cpystr(pf_template[i].name);
1222 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global)){
1223 /* slide string over so it is Sender instead of X-X-Sender */
1224 for(p=pf->name+4; *p != '\0'; p++)
1225 *(p-4) = *p;
1226 *(p-4) = '\0';
1229 pf->type = pf_template[i].type;
1230 pf->canedit = pf_template[i].canedit;
1231 pf->rcptto = pf_template[i].rcptto;
1232 pf->writehdr = pf_template[i].writehdr;
1233 pf->localcopy = pf_template[i].localcopy;
1234 pf->extdata = NULL; /* unused */
1235 pf->next = pf + 1;
1237 switch(pf->type){
1238 case FreeText:
1239 switch(i){
1240 case N_AUTHRCVD:
1241 sending_order[0] = pf;
1242 break;
1244 case N_NEWS:
1245 pf->text = &outgoing->newsgroups;
1246 sending_order[1] = pf;
1247 break;
1249 case N_DATE:
1250 pf->text = (char **) &outgoing->date;
1251 sending_order[2] = pf;
1252 break;
1254 case N_INREPLY:
1255 pf->text = &outgoing->in_reply_to;
1256 sending_order[NN+9] = pf;
1257 break;
1259 case N_MSGID:
1260 pf->text = &outgoing->message_id;
1261 sending_order[NN+10] = pf;
1262 break;
1264 case N_REF: /* won't be used here */
1265 sending_order[NN+11] = pf;
1266 break;
1268 case N_PRIORITY:
1269 sending_order[NN+12] = pf;
1270 break;
1272 case N_USERAGENT:
1273 pf->text = &pf->textbuf;
1274 pf->textbuf = generate_user_agent();
1275 sending_order[NN+13] = pf;
1276 break;
1278 case N_POSTERR: /* won't be used here */
1279 sending_order[NN+14] = pf;
1280 break;
1282 case N_RPLUID: /* won't be used here */
1283 sending_order[NN+15] = pf;
1284 break;
1286 case N_RPLMBOX: /* won't be used here */
1287 sending_order[NN+16] = pf;
1288 break;
1290 case N_SMTP: /* won't be used here */
1291 sending_order[NN+17] = pf;
1292 break;
1294 case N_NNTP: /* won't be used here */
1295 sending_order[NN+18] = pf;
1296 break;
1298 case N_CURPOS: /* won't be used here */
1299 sending_order[NN+19] = pf;
1300 break;
1302 case N_OURREPLYTO: /* won't be used here */
1303 sending_order[NN+20] = pf;
1304 break;
1306 case N_OURHDRS: /* won't be used here */
1307 sending_order[NN+21] = pf;
1308 break;
1310 default:
1311 q_status_message1(SM_ORDER,3,3,
1312 "Internal error: 1)FreeText header %s", comatose(i));
1313 break;
1316 break;
1318 case Attachment:
1319 break;
1321 case Address:
1322 switch(i){
1323 case N_FROM:
1324 sending_order[3] = pf;
1325 pf->addr = &outgoing->from;
1326 break;
1328 case N_TO:
1329 sending_order[NN+2] = pf;
1330 pf->addr = &outgoing->to;
1331 if(tobufpp)
1332 (*tobufpp) = &pf->scratch;
1334 break;
1336 case N_CC:
1337 sending_order[NN+3] = pf;
1338 pf->addr = &outgoing->cc;
1339 break;
1341 case N_BCC:
1342 sending_order[NN+4] = pf;
1343 pf->addr = &outgoing->bcc;
1344 break;
1346 case N_REPLYTO:
1347 sending_order[NN+1] = pf;
1348 pf->addr = &outgoing->reply_to;
1349 break;
1351 case N_LCC: /* won't be used here */
1352 sending_order[NN+7] = pf;
1353 break;
1355 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1356 case N_SENDER:
1357 sending_order[4] = pf;
1358 pf->addr = &outgoing->sender;
1359 break;
1360 #endif
1362 case N_NOBODY: /* won't be used here */
1363 sending_order[NN+5] = pf;
1364 break;
1366 default:
1367 q_status_message1(SM_ORDER,3,3,
1368 "Internal error: Address header %s", comatose(i));
1369 break;
1371 break;
1373 case Fcc:
1374 sending_order[NN+8] = pf;
1375 pf->text = fccp;
1376 break;
1378 case Subject:
1379 sending_order[NN+6] = pf;
1380 pf->text = &outgoing->subject;
1381 break;
1383 default:
1384 q_status_message1(SM_ORDER,3,3,
1385 "Unknown header type %d in pine_new_send", (void *)pf->type);
1386 break;
1390 if(((--pf)->next = custom) != NULL){
1391 i--;
1394 * NOTE: "i" is assumed to now index first custom field in sending
1395 * order.
1397 for(pf = pf->next; pf && pf->name; pf = pf->next){
1398 if(pf->standard)
1399 continue;
1401 pf->canedit = 1;
1402 pf->rcptto = 0;
1403 pf->writehdr = 1;
1404 pf->localcopy = 1;
1406 switch(pf->type){
1407 case Address:
1408 if(pf->addr){ /* better be set */
1409 char *addr = NULL;
1410 BuildTo bldto;
1412 bldto.type = Str;
1413 bldto.arg.str = pf->textbuf;
1415 sending_order[i++] = pf;
1416 /* change default text into an ADDRESS */
1417 /* strip quotes around whole default */
1418 removing_trailing_white_space(pf->textbuf);
1419 (void)removing_double_quotes(pf->textbuf);
1420 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1421 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1422 fs_give((void **)&addr);
1423 if(pf->textbuf)
1424 fs_give((void **)&pf->textbuf);
1427 break;
1429 case FreeText:
1430 sending_order[i++] = pf;
1431 pf->text = &pf->textbuf;
1432 break;
1434 default:
1435 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1436 (void *)pf->type);
1437 break;
1443 return(header);
1447 void
1448 pine_free_env(METAENV **menv)
1450 int cnt;
1453 if((*menv)->local){
1454 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1457 for(; cnt >= 0; cnt--){
1458 if((*menv)->local[cnt].textbuf)
1459 fs_give((void **) &(*menv)->local[cnt].textbuf);
1461 fs_give((void **) &(*menv)->local[cnt].name);
1464 fs_give((void **) &(*menv)->local);
1467 if((*menv)->sending_order)
1468 fs_give((void **) &(*menv)->sending_order);
1470 fs_give((void **) menv);
1474 /*----------------------------------------------------------------------
1475 Check for addresses the user is not permitted to send to, or probably
1476 doesn't want to send to
1478 Returns: 0 if OK
1479 1 if there are only empty groups
1480 -1 if the message shouldn't be sent
1482 Queues a message indicating what happened
1483 ---*/
1485 check_addresses(METAENV *header)
1487 PINEFIELD *pf;
1488 ADDRESS *a;
1489 int send_daemon = 0, rv = CA_EMPTY;
1491 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1492 for(pf = header->local; pf && pf->name; pf = pf->next)
1493 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1494 for(a = *pf->addr; a != NULL; a = a->next){
1495 if(a->host && (a->host[0] == '.'
1496 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1497 && a->host[0] == '@'))){
1498 q_status_message2(SM_ORDER, 4, 7,
1499 /* TRANSLATORS: First arg is the address we can't
1500 send to, second arg is "not in addressbook". */
1501 _("Can't send to address %s: %s"),
1502 a->mailbox,
1503 (a->host[0] == '.')
1504 ? a->host
1505 : _("not in addressbook"));
1506 return(CA_BAD);
1508 else if(ps_global->restricted
1509 && !address_is_us(*pf->addr, ps_global)){
1510 q_status_message(SM_ORDER, 3, 3,
1511 "Restricted demo version of Alpine. You may only send mail to yourself");
1512 return(CA_BAD);
1514 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1515 send_daemon = 1;
1516 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1518 else if(a->mailbox && a->host){
1519 rv = CA_OK;
1523 return(rv);
1528 * If this isn't general enough we can modify it. The value passed in
1529 * is expected to be one of the desc settings from the priorities array,
1530 * like "High". The header value is X-Priority: 2 (High)
1531 * or something similar. If value doesn't match any of the values then
1532 * the actual value is used instead.
1534 PINEFIELD *
1535 set_priority_header(METAENV *header, char *value)
1537 PINEFIELD *pf;
1539 for(pf = header->local; pf && pf->name; pf = pf->next)
1540 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1541 break;
1543 if(pf){
1544 if(pf->textbuf)
1545 fs_give((void **) &pf->textbuf);
1547 if(value){
1548 PRIORITY_S *p;
1550 for(p = priorities; p && p->desc; p++)
1551 if(!strcmp(p->desc, value))
1552 break;
1554 if(p && p->desc){
1555 char buf[100];
1557 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1558 pf->textbuf = cpystr(buf);
1560 else
1561 pf->textbuf = cpystr(value);
1564 return pf;
1568 /*----------------------------------------------------------------------
1569 Set answered flags for messages specified by reply structure
1571 Args: reply --
1573 Returns: with appropriate flags set and index cache entries suitably tweeked
1574 ----*/
1575 void
1576 update_answered_flags(REPLY_S *reply)
1578 char *seq = NULL, *p;
1579 long i, ourstream = 0, we_cancel = 0;
1580 MAILSTREAM *stream = NULL;
1582 /* nothing to flip in a pseudo reply */
1583 if(reply && (reply->msgno || reply->uid)){
1584 int j;
1585 MAILSTREAM *m;
1588 * If an established stream will do, use it, else
1589 * build one unless we have an array of msgno's...
1591 * I was just mimicking what was already here. I don't really
1592 * understand why we use strcmp instead of same_stream_and_mailbox().
1593 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1594 * Hubert 2003-07-09
1596 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1597 m = ps_global->s_pool.streams[j];
1598 if(m && reply->mailbox && m->mailbox
1599 && !strcmp(reply->mailbox, m->mailbox))
1600 stream = m;
1603 if(!stream && reply->msgno)
1604 return;
1606 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1607 we_cancel = reply->forwarded
1608 ? busy_cue(_("Setting \"Forwarded\" Keyword"), NULL, 0)
1609 : busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1610 if(!stream){
1611 if((stream = pine_mail_open(NULL,
1612 reply->origmbox ? reply->origmbox
1613 : reply->mailbox,
1614 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1615 NULL)) != NULL){
1616 ourstream++;
1618 else{
1619 if(we_cancel)
1620 cancel_busy_cue(0);
1622 return;
1626 if(stream->uid_validity == reply->data.uid.validity){
1627 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1628 if(i){
1629 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1630 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1633 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1634 SIZEOF_20KBUF-(p-tmp_20k_buf));
1635 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1638 if(reply->forwarded){
1640 * $Forwarded is a regular keyword so we only try to
1641 * set it if the stream allows keywords.
1642 * We could mess up if the stream has keywords but just
1643 * isn't allowing anymore and $Forwarded already exists,
1644 * but what are the odds?
1646 if(stream && stream->kwd_create)
1647 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1648 FORWARDED_FLAG,
1649 ST_SET | ((reply->uid) ? ST_UID : 0L));
1651 else
1652 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1653 "\\ANSWERED",
1654 ST_SET | ((reply->uid) ? ST_UID : 0L));
1656 if(seq)
1657 fs_give((void **)&seq);
1660 if(ourstream)
1661 pine_mail_close(stream); /* clean up dangling stream */
1663 if(we_cancel)
1664 cancel_busy_cue(0);
1669 /*----------------------------------------------------------------------
1670 Call the mailer, SMTP, sendmail or whatever
1672 Args: header -- full header (envelope and local parts) of message to send
1673 body -- The full body of the message including text
1674 alt_smtp_servers --
1675 verbosefile -- non-null means caller wants verbose interaction and the resulting
1676 output file name to be returned
1678 Returns: -1 if failed, 1 if succeeded
1679 ----*/
1681 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1682 int flags, void (*bigresult_f)(char *, int),
1683 void (*pipecb_f)(PIPE_S *, int, void *))
1685 char error_buf[200], *error_mess = NULL, *postcmd;
1686 ADDRESS *a;
1687 ENVELOPE *fake_env = NULL;
1688 int addr_error_count, we_cancel = 0;
1689 long smtp_opts = 0L;
1690 char *verbose_file = NULL;
1691 BODY *bp = NULL;
1692 PINEFIELD *pf;
1693 BODY *origBody = body;
1695 dprint((4, "Sending mail...\n"));
1697 /* Check for any recipients */
1698 for(pf = header->local; pf && pf->name; pf = pf->next)
1699 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1700 break;
1702 if(!pf){
1703 q_status_message(SM_ORDER,3,3,
1704 _("Can't send message. No recipients specified!"));
1705 return(0);
1708 #ifdef SMIME
1709 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1710 int result;
1712 STORE_S *so = lmc.so;
1713 lmc.so = NULL;
1715 result = 1;
1717 if(ps_global->smime->do_sign){
1718 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1719 result = sign_outgoing_message(header, &body, 0, &bp);
1722 /* need to free new body from encrypt if sign fails? */
1723 if(result && ps_global->smime->do_encrypt)
1724 result = encrypt_outgoing_message(header, &body);
1726 lmc.so = so;
1728 if(!result)
1729 return 0;
1731 #endif
1733 /* set up counts and such to keep track sent percentage */
1734 send_bytes_sent = 0;
1735 gf_filter_init(); /* zero piped byte count, 'n */
1736 send_bytes_to_send = send_body_size(body); /* count body bytes */
1737 ps_global->c_client_error[0] = error_buf[0] = '\0';
1738 we_cancel = busy_cue(_("Sending mail"),
1739 send_bytes_to_send ? sent_percent : NULL, 0);
1741 #ifndef _WINDOWS
1743 /* try posting via local "<mta> <-t>" if specified */
1744 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1745 if(error_buf[0])
1746 error_mess = error_buf;
1748 goto done;
1751 #endif
1754 * If the user's asked for it, and we find that the first text
1755 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1757 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1758 smtp_opts |= SOP_8BITMIME;
1760 #ifdef DEBUG
1761 #ifndef DEBUGJOURNAL
1762 if(debug > 5 || (flags & CM_VERBOSE))
1763 #endif
1764 smtp_opts |= SOP_DEBUG;
1765 #endif
1767 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1768 smtp_opts |= SOP_DSN;
1769 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1770 if(flags & CM_DSN_DELAY)
1771 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1772 if(flags & CM_DSN_SUCCESS)
1773 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1776 * If it isn't Never, then we're always going to let them
1777 * know about failures. This means we don't allow for the
1778 * possibility of setting delay or success without failure.
1780 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1782 if(flags & CM_DSN_FULL)
1783 smtp_opts |= SOP_DSN_RETURN_FULL;
1789 * Set global header pointer so post_rfc822_output can get at it when
1790 * it's called back from c-client's sending routine...
1792 send_header = header;
1795 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1796 * The purpose is to give smtp_mail the list for SMTP RCPT when
1797 * there are recipients in pine's METAENV that are outside c-client's
1798 * envelope.
1800 * NOTE: If there aren't any, don't bother. Dealt with it below.
1802 for(pf = header->local; pf && pf->name; pf = pf->next)
1803 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1804 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1805 || *pf->addr == header->env->bcc))
1806 break;
1808 if(pf && pf->name){
1809 ADDRESS **tail;
1811 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1812 memset(fake_env, 0, sizeof(ENVELOPE));
1813 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1814 tail = &(fake_env->to);
1815 for(pf = header->local; pf && pf->name; pf = pf->next)
1816 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1817 *tail = rfc822_cpy_adr(*pf->addr);
1818 while(*tail)
1819 tail = &((*tail)->next);
1824 * Install our rfc822 output routine
1826 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1827 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1830 * Allow for verbose posting
1832 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1833 (void *) pine_smtp_verbose_out);
1836 * We do this because we want mm_log to put the error message into
1837 * c_client_error instead of showing it itself.
1839 ps_global->noshow_error = 1;
1842 * OK, who posts what? We tried an mta_handoff above, but there
1843 * was either none specified or we decided not to use it. So,
1844 * if there's an smtp-server defined anywhere,
1846 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1847 /*---------- SMTP ----------*/
1848 dprint((4, "call_mailer: via TCP (%s)\n",
1849 alt_smtp_servers[0]));
1850 TIME_STAMP("smtp-open start (tcp)", 1);
1851 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1853 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1854 && ps_global->VAR_SMTP_SERVER[0][0]){
1855 /*---------- SMTP ----------*/
1856 dprint((4, "call_mailer: via TCP\n"));
1857 TIME_STAMP("smtp-open start (tcp)", 1);
1858 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1860 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1861 char *cmdlist[2];
1863 /*----- Send via LOCAL SMTP agent ------*/
1864 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1866 TIME_STAMP("smtp-open start (pipe)", 1);
1867 fs_give((void **) &postcmd);
1868 cmdlist[0] = "localhost";
1869 cmdlist[1] = NULL;
1870 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1871 SMTPTCPPORT, smtp_opts);
1872 /* BUG: should provide separate stderr output! */
1875 ps_global->noshow_error = 0;
1877 TIME_STAMP("smtp open", 1);
1878 if(sending_stream){
1879 unsigned short save_encoding, added_encoding;
1881 dprint((1, "Opened SMTP server \"%s\"\n",
1882 net_host(sending_stream->netstream)
1883 ? net_host(sending_stream->netstream) : "?"));
1885 if(flags & CM_VERBOSE){
1886 TIME_STAMP("verbose start", 1);
1887 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1888 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1889 if(!smtp_verbose(sending_stream)){
1890 snprintf(error_mess = error_buf, sizeof(error_buf),
1891 "Mail not sent. VERBOSE mode error%s%.50s.",
1892 (sending_stream && sending_stream->reply)
1893 ? ": ": "",
1894 (sending_stream && sending_stream->reply)
1895 ? sending_stream->reply : "");
1896 error_buf[sizeof(error_buf)-1] = '\0';
1899 else{
1900 our_unlink(verbose_file);
1901 strncpy(error_mess = error_buf,
1902 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1903 error_buf[sizeof(error_buf)-1] = '\0';
1906 else{
1907 strncpy(error_mess = error_buf,
1908 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1909 error_buf[sizeof(error_buf)-1] = '\0';
1912 TIME_STAMP("verbose end", 1);
1916 * Before we actually send data, see if we have to protect
1917 * the first text body part from getting encoded. We protect
1918 * it from getting encoded in "pine_rfc822_output_body" by
1919 * temporarily inventing a synonym for ENC8BIT...
1920 * This works like so:
1921 * Suppose bp->encoding is set to ENC8BIT.
1922 * We change that here to some unused value (added_encoding) and
1923 * set body_encodings[added_encoding] to "8BIT".
1924 * Then post_rfc822_output is called which calls
1925 * pine_rfc822_output_body. Inside that routine
1926 * pine_write_body_header writes out the encoding for the
1927 * part. Normally it would see encoding == ENC8BIT and it would
1928 * change that to QUOTED-PRINTABLE, but since encoding has been
1929 * set to added_encoding it uses body_encodings[added_encoding]
1930 * which is "8BIT" instead. Then the actual body is written by
1931 * pine_write_body_header which does not do the gf_8bit_qp
1932 * filtering because encoding != ENC8BIT (instead it's equal
1933 * to added_encoding).
1935 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1936 && sending_stream->protocol.esmtp.eightbit.want){
1937 int i;
1939 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1942 if(i > ENCMAX){ /* no empty encoding slots! */
1943 bp = NULL;
1945 else {
1946 added_encoding = i;
1947 body_encodings[added_encoding] = body_encodings[ENC8BIT];
1948 save_encoding = bp->encoding;
1949 bp->encoding = added_encoding;
1950 #ifdef SMIME
1951 if(ps_global->smime && ps_global->smime->do_sign
1952 && body->nested.part->next
1953 && body->nested.part->next->body.contents.text.data
1954 && body->nested.part->next->body.mime.text.data){
1955 STORE_S *so;
1957 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
1958 so_give(&so);
1959 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
1960 body->nested.part->next->body.mime.text.data = NULL;
1962 #endif /* SMIME */
1966 if(sending_stream->protocol.esmtp.ok
1967 && sending_stream->protocol.esmtp.dsn.want
1968 && !sending_stream->protocol.esmtp.dsn.ok)
1969 q_status_message(SM_ORDER,3,3,
1970 _("Delivery Status Notification not available from this server."));
1972 TIME_STAMP("smtp start", 1);
1973 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
1974 fake_env ? fake_env : header->env, body)){
1976 snprintf(error_buf, sizeof(error_buf),
1977 _("Mail not sent. Sending error%s%s"),
1978 (sending_stream && sending_stream->reply) ? ": ": ".",
1979 (sending_stream && sending_stream->reply)
1980 ? sending_stream->reply : "");
1981 error_buf[sizeof(error_buf)-1] = '\0';
1982 dprint((1, error_buf));
1983 addr_error_count = 0;
1984 if(fake_env){
1985 for(a = fake_env->to; a != NULL; a = a->next)
1986 if(a->error != NULL){
1987 if(addr_error_count++ < MAX_ADDR_ERROR){
1990 * Too complicated to figure out which header line
1991 * has the error in the fake_env case, so just
1992 * leave cursor at default.
1996 if(error_mess) /* previous error? */
1997 q_status_message(SM_ORDER, 4, 7, error_mess);
1999 error_mess = tidy_smtp_mess(a->error,
2000 _("Mail not sent: %.80s"),
2001 error_buf, sizeof(error_buf));
2004 dprint((1, "Send Error: \"%s\"\n",
2005 a->error));
2008 else{
2009 for(pf = header->local; pf && pf->name; pf = pf->next)
2010 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2011 for(a = *pf->addr; a != NULL; a = a->next)
2012 if(a->error != NULL){
2013 if(addr_error_count++ < MAX_ADDR_ERROR){
2015 if(error_mess) /* previous error? */
2016 q_status_message(SM_ORDER, 4, 7, error_mess);
2018 error_mess = tidy_smtp_mess(a->error,
2019 _("Mail not sent: %.80s"),
2020 error_buf, sizeof(error_buf));
2023 dprint((1, "Send Error: \"%s\"\n",
2024 a->error));
2028 if(!error_mess)
2029 error_mess = error_buf;
2032 /* repair modified "body_encodings" array? */
2033 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2034 && sending_stream->protocol.esmtp.eightbit.want){
2035 body_encodings[added_encoding] = NULL;
2036 bp->encoding = save_encoding;
2039 TIME_STAMP("smtp closing", 1);
2040 smtp_close(sending_stream);
2041 sending_stream = NULL;
2042 TIME_STAMP("smtp done", 1);
2044 else if(!error_mess){
2045 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2046 ps_global->c_client_error[0] ? ": " : "",
2047 ps_global->c_client_error);
2048 error_buf[sizeof(error_buf)-1] = '\0';
2051 if(verbose_file){
2052 if(verbose_send_output){
2053 TIME_STAMP("verbose start", 1);
2054 fclose(verbose_send_output);
2055 verbose_send_output = NULL;
2056 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2058 if(bigresult_f)
2059 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2061 TIME_STAMP("verbose end", 1);
2064 fs_give((void **)&verbose_file);
2068 * Restore original 822 emitter...
2070 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2072 if(fake_env)
2073 mail_free_envelope(&fake_env);
2075 done:
2077 #ifdef SMIME
2078 /* Free replacement encrypted body */
2079 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2081 if(body->type == TYPEMULTIPART){
2082 /* Just get rid of first part, it's actually origBody */
2083 void *x = body->nested.part;
2085 body->nested.part = body->nested.part->next;
2087 fs_give(&x);
2090 pine_free_body(&body);
2092 #endif
2094 if(we_cancel)
2095 cancel_busy_cue(0);
2097 TIME_STAMP("call_mailer done", 1);
2098 /*-------- Did message make it ? ----------*/
2099 if(error_mess){
2100 /*---- Error sending mail -----*/
2101 if(lmc.so && !lmc.all_written)
2102 so_give(&lmc.so);
2104 if(error_mess){
2105 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2106 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2109 return(-1);
2111 else{
2112 lmc.all_written = 1;
2113 return(1);
2119 * write_postponed - exported method to write the given message
2120 * to the postponed folder
2123 write_postponed(METAENV *header, struct mail_bodystruct *body)
2125 char **pp, *folder;
2126 int rv = 0, sz;
2127 CONTEXT_S *fcc_cntxt = NULL;
2128 PINEFIELD *pf;
2129 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2131 if(!ps_global->VAR_POSTPONED_FOLDER
2132 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2133 q_status_message(SM_ORDER | SM_DING, 3, 3,
2134 _("No postponed file defined"));
2135 return(-1);
2138 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2140 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2142 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2144 if(lmc.so){
2145 /* BUG: writem sufficient ? */
2146 for(pf = header->local; pf && pf->name; pf = pf->next)
2147 for(pp = writem; *pp; pp++)
2148 if(!strucmp(pf->name, *pp)){
2149 pf->localcopy = 1;
2150 pf->writehdr = 1;
2151 break;
2155 * Work around c-client reply-to bug. C-client will
2156 * return a reply_to in an envelope even if there is
2157 * no reply-to header field. We want to note here whether
2158 * the reply-to is real or not.
2160 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2161 for(pf = header->local; pf; pf = pf->next)
2162 if(!strcmp(pf->name, "Reply-To")){
2163 pf->writehdr = 1;
2164 pf->localcopy = 1;
2165 if(header->env->reply_to)
2166 pf->textbuf = cpystr("Full");
2167 else
2168 pf->textbuf = cpystr("Empty");
2172 * Write the list of custom headers to the
2173 * X-Our-Headers header so that we can recover the
2174 * list in redraft.
2176 sz = 0;
2177 for(pf = header->custom; pf && pf->name; pf = pf->next)
2178 sz += strlen(pf->name) + 1;
2180 if(sz){
2181 int i;
2182 char *pstart, *pend;
2184 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2187 pf->writehdr = 1;
2188 pf->localcopy = 1;
2189 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2190 pf->text = &pf->textbuf;
2191 pf->textbuf[sz] = '\0'; /* tie off overflow */
2192 /* note: "pf" overloaded */
2193 for(pf = header->custom; pf && pf->name; pf = pf->next){
2194 int r = sz - (pend - pstart); /* remaining buffer */
2196 if(r > 0 && r != sz){
2197 r--;
2198 *pend++ = ',';
2201 sstrncpy(&pend, pf->name, r);
2205 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2206 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2207 rv = -1;
2209 so_give(&lmc.so);
2211 else {
2212 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2213 "Can't allocate internal storage: %s ",
2214 error_description(errno));
2215 rv = -1;
2218 fs_give((void **) &folder);
2219 return(rv);
2224 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2226 if(fcc && *fcc){
2227 lmc.all_written = lmc.text_written = 0;
2228 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2229 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2231 else
2232 lmc.so = NULL;
2234 return(TRUE);
2239 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2241 int rv = TRUE;
2243 if(lmc.so){
2244 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2245 char label[50];
2247 strncpy(label, "Fcc", sizeof(label));
2248 label[sizeof(label)-1] = '\0';
2249 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2250 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2251 label[sizeof(label)-1] = '\0';
2254 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2256 else{
2257 rv = FALSE;
2260 so_give(&lmc.so);
2263 return(rv);
2267 /*----------------------------------------------------------------------
2268 Checks to make sure the fcc is available and can be opened
2270 Args: fcc -- the name of the fcc to create. It can't be NULL.
2271 fcc_cntxt -- Returns the context the fcc is in.
2272 force -- suppress user option prompt
2274 Returns allocated storage object on success, NULL on failure
2275 ----*/
2276 STORE_S *
2277 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2279 int exists, ok = 0;
2281 ps_global->mm_log_error = 0;
2284 * check for fcc's existence...
2286 TIME_STAMP("open_fcc start", 1);
2287 if(!is_absolute_path(fcc) && context_isambig(fcc)
2288 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2289 int flip_dot = 0;
2292 * Don't want to preclude a user from Fcc'ing a .name'd folder
2294 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2295 flip_dot = 1;
2296 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2300 * We only want to set the "context" if fcc is an ambiguous
2301 * name. Otherwise, our "relativeness" rules for contexts
2302 * (implemented in context.c) might cause the name to be
2303 * interpreted in the wrong context...
2305 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2306 *fcc_cntxt = ps_global->context_list;
2308 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2309 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2310 if(force
2311 || (pith_opt_save_create_prompt
2312 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2314 ps_global->noshow_error = 1;
2316 if(context_create(*fcc_cntxt, NULL, fcc))
2317 ok++;
2319 ps_global->noshow_error = 0;
2321 else
2322 ok--; /* declined! */
2324 else
2325 ok++; /* found! */
2327 if(flip_dot)
2328 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2330 free_folder_list(*fcc_cntxt);
2332 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2333 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2334 ok++;
2336 else{
2337 if(force
2338 || (pith_opt_save_create_prompt
2339 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2341 ps_global->mm_log_error = 0;
2342 ps_global->noshow_error = 1;
2344 ok = pine_mail_create(NULL, fcc) != 0L;
2346 ps_global->noshow_error = 0;
2348 else
2349 ok--; /* declined! */
2353 TIME_STAMP("open_fcc done.", 1);
2354 if(ok > 0){
2355 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2357 else{
2358 int l1, l2, l3, wid, w;
2359 char *errstr, tmp[MAILTMPLEN];
2360 char *s1, *s2;
2362 if(ok == 0){
2363 if(ps_global->mm_log_error){
2364 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2365 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2367 l1 = strlen(s1);
2368 l2 = strlen(s2);
2369 l3 = strlen(ps_global->c_client_error);
2370 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2371 ? ps_global->ttyo->screen_cols : 80;
2372 w = wid - l1 - l2 - 5;
2374 snprintf(errstr = tmp, sizeof(tmp),
2375 "%.99s\"%.*s%.99s\".%.99s",
2377 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2378 ps_global->c_client_error,
2379 (l3 > w) ? "..." : "",
2380 s2);
2381 tmp[sizeof(tmp)-1] = '\0';
2384 else
2385 errstr = _("Fcc creation error. Message NOT sent or copied.");
2387 else
2388 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2390 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2393 return(NULL);
2397 /*----------------------------------------------------------------------
2398 mail_append() the fcc accumulated in temp_storage to proper destination
2400 Args: fcc -- name of folder
2401 fcc_cntxt -- context for folder
2402 temp_storage -- String of file where Fcc has been accumulated
2404 This copies the string of file to the actual folder, which might be IMAP
2405 or a disk folder. The temp_storage is freed after it is written.
2406 An error message is produced if this fails.
2407 ----*/
2409 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2410 MAILSTREAM *stream, char *label, char *flags)
2412 STRING msg;
2413 CONTEXT_S *cntxt;
2414 int we_cancel = 0;
2416 if(!tmp_storage)
2417 return(0);
2419 TIME_STAMP("write_fcc start.", 1);
2420 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2421 if(label && *label){
2422 char msg_buf[80];
2424 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2425 msg_buf[sizeof(msg_buf)-1] = '\0';
2426 strncat(msg_buf, label, sizeof(msg_buf)-10);
2427 we_cancel = busy_cue(msg_buf, NULL, 0);
2429 else
2430 we_cancel = busy_cue(NULL, NULL, 1);
2432 so_seek(tmp_storage, 0L, 0);
2435 * Before changing this note that these lines depend on the
2436 * definition of FCC_SOURCE.
2438 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2439 strlen((char *)so_text(tmp_storage)));
2441 cntxt = fcc_cntxt;
2443 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2444 cancel_busy_cue(-1);
2445 we_cancel = 0;
2447 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2448 "Write to \"%s\" FAILED!!!", fcc);
2449 dprint((1, "ERROR appending %s in \"%s\"",
2450 fcc ? fcc : "?",
2451 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2452 return(0);
2455 if(we_cancel)
2456 cancel_busy_cue(label ? 0 : -1);
2458 dprint((4, "done.\n"));
2459 TIME_STAMP("write_fcc done.", 1);
2460 return(1);
2465 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2466 * contained.
2468 BODY *
2469 first_text_8bit(struct mail_bodystruct *body)
2471 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2472 body = &body->nested.part->body;
2474 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2475 ? body : NULL);
2480 * Build and return the "From:" address for outbound messages from
2481 * global data...
2483 ADDRESS *
2484 generate_from(void)
2486 ADDRESS *addr = mail_newaddr();
2487 if(ps_global->VAR_PERSONAL_NAME){
2488 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2489 removing_leading_and_trailing_white_space(addr->personal);
2490 if(addr->personal[0] == '\0')
2491 fs_give((void **)&addr->personal);
2494 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2495 addr->host = cpystr(ps_global->maildomain);
2496 removing_leading_and_trailing_white_space(addr->mailbox);
2497 removing_leading_and_trailing_white_space(addr->host);
2498 return(addr);
2503 * set_mime_type_by_grope - sniff the given storage object to determine its
2504 * type, subtype, encoding, and charset
2506 * "Type" and "encoding" must be set before calling this routine.
2507 * If "type" is set to something other than TYPEOTHER on entry,
2508 * then that is the "type" we wish to use. Same for "encoding"
2509 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2510 * figure them out here. If "type" is already set, we also
2511 * leave subtype alone. If not, we figure out subtype here.
2512 * There is a chance that we will upgrade encoding to a "higher"
2513 * level. For example, if it comes in as 7BIT we may change
2514 * that to 8BIT if we find a From_ we want to escape.
2515 * We may also set the charset attribute if the type is TEXT.
2517 * NOTE: this is rather inefficient if the store object is a CharStar
2518 * but the win is all types are handled the same
2520 void
2521 set_mime_type_by_grope(struct mail_bodystruct *body)
2523 #define RBUFSZ (8193)
2524 unsigned char *buf, *p, *bol;
2525 register size_t n;
2526 long max_line = 0L,
2527 eight_bit_chars = 0L,
2528 line_so_far = 0L,
2529 len = 0L;
2530 STORE_S *so = (STORE_S *)body->contents.text.data;
2531 unsigned short new_encoding = ENCOTHER;
2532 int we_cancel = 0;
2533 #ifdef ENCODE_FROMS
2534 short froms = 0, dots = 0,
2535 bmap = 0x1, dmap = 0x1;
2536 #endif
2538 we_cancel = busy_cue(NULL, NULL, 1);
2540 buf = (unsigned char *)fs_get(RBUFSZ);
2541 so_seek(so, 0L, 0);
2543 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2546 buf[n] = '\0';
2548 if(n){ /* check first few bytes to look for magic numbers */
2549 if(body->type == TYPEOTHER){
2550 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2551 body->type = TYPEIMAGE;
2552 body->subtype = cpystr("GIF");
2554 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2555 && buf[2] == 0xFF && buf[3] == 0xE0
2556 && !strncmp((char *)&buf[6], "JFIF", 4)){
2557 body->type = TYPEIMAGE;
2558 body->subtype = cpystr("JPEG");
2560 else if((buf[0] == 'M' && buf[1] == 'M')
2561 || (buf[0] == 'I' && buf[1] == 'I')){
2562 body->type = TYPEIMAGE;
2563 body->subtype = cpystr("TIFF");
2565 else if((buf[0] == '%' && buf[1] == '!')
2566 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2567 body->type = TYPEAPPLICATION;
2568 body->subtype = cpystr("PostScript");
2570 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2571 body->type = TYPEAPPLICATION;
2572 body->subtype = cpystr("PDF");
2574 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2575 body->type = TYPEAUDIO;
2576 body->subtype = cpystr("Basic");
2578 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2579 && buf[2] == 0x16 && buf[3] == 0x00){
2580 body->type = TYPEAPPLICATION;
2581 body->subtype = cpystr("APPLEFILE");
2583 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2584 && buf[2] == 0x03 && buf[3] == 0x04){
2585 body->type = TYPEAPPLICATION;
2586 body->subtype = cpystr("ZIP");
2590 * if type was set above, but no encoding specified, go
2591 * ahead and make it BASE64...
2593 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2594 body->encoding = ENCBINARY;
2597 else{
2598 /* PROBLEM !!! */
2599 if(body->type == TYPEOTHER){
2600 body->type = TYPEAPPLICATION;
2601 body->subtype = cpystr("octet-stream");
2602 if(body->encoding == ENCOTHER)
2603 body->encoding = ENCBINARY;
2607 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2608 #if defined(DOS) || defined(OS2) /* for binary file detection */
2609 int lastchar = '\0';
2610 #define BREAKOUT 300 /* a value that a character can't be */
2611 #endif
2613 p = bol = buf;
2614 len = n;
2615 while (n--){
2616 /* Some people don't like quoted-printable caused by leading Froms */
2617 #ifdef ENCODE_FROMS
2618 Find_Froms(froms, dots, bmap, dmap, *p);
2619 #endif
2620 if(*p == '\n'){
2621 max_line = MAX(max_line, line_so_far + p - bol);
2622 bol = NULL; /* clear beginning of line */
2623 line_so_far = 0L; /* clear line count */
2624 #if defined(DOS) || defined(OS2)
2625 /* LF with no CR!! */
2626 if(lastchar != '\r') /* must be non-text data! */
2627 lastchar = BREAKOUT;
2628 #endif
2630 else if(*p & 0x80){
2631 eight_bit_chars++;
2633 else if(!*p){
2634 /* NULL found. Unless we're told otherwise, must be binary */
2635 if(body->type == TYPEOTHER){
2636 body->type = TYPEAPPLICATION;
2637 body->subtype = cpystr("octet-stream");
2641 * The "TYPETEXT" here handles the case that the NULL
2642 * comes from imported text generated by some external
2643 * editor that permits or inserts NULLS. Otherwise,
2644 * assume it's a binary segment...
2646 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2649 * Since we've already set encoding, count this as a
2650 * hi bit char and continue. The reason is that if this
2651 * is text, there may be a high percentage of encoded
2652 * characters, so base64 may get set below...
2654 if(body->type == TYPETEXT)
2655 eight_bit_chars++;
2656 else
2657 break;
2660 #if defined(DOS) || defined(OS2) /* for binary file detection */
2661 if(lastchar != BREAKOUT)
2662 lastchar = *p;
2663 #endif
2665 /* read another buffer in */
2666 if(n == 0){
2667 if(bol)
2668 line_so_far += p - bol;
2670 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2673 len += n;
2674 p = buf;
2676 else
2677 p++;
2680 * If there's no beginning-of-line pointer, then we must
2681 * have seen an end-of-line. Set bol to the start of the
2682 * new line...
2684 if(!bol)
2685 bol = p;
2687 #if defined(DOS) || defined(OS2) /* for binary file detection */
2688 /* either a lone \r or lone \n indicate binary file */
2689 if(lastchar == '\r' || lastchar == BREAKOUT){
2690 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2691 if(body->type == TYPEOTHER){
2692 body->type = TYPEAPPLICATION;
2693 body->subtype = cpystr("octet-stream");
2696 new_encoding = ENCBINARY;
2697 break;
2700 #endif
2704 /* stash away for later */
2705 so_attr(so, "maxline", long2string(max_line));
2707 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2709 * Since the type or encoding aren't set yet, fall thru a
2710 * series of tests to make sure an adequate type and
2711 * encoding are set...
2714 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2715 if(body->type == TYPEOTHER){
2717 * Since the types not set, then we didn't find a NULL.
2718 * If there's no NULL, then this is likely text. However,
2719 * since we can't be *completely* sure, we set it to
2720 * the generic type.
2722 body->type = TYPEAPPLICATION;
2723 body->subtype = cpystr("octet-stream");
2726 if(new_encoding != ENCBINARY)
2728 * As with NULL handling, if we're told it's text,
2729 * qp-encode it, else it gets base 64...
2731 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2734 if(eight_bit_chars == 0L){
2735 if(body->type == TYPEOTHER)
2736 body->type = TYPETEXT;
2738 if(new_encoding == ENCOTHER)
2739 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2741 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2743 * The 30% threshold is based on qp encoded readability
2744 * on non-MIME UA's.
2746 if(body->type == TYPEOTHER)
2747 body->type = TYPETEXT;
2749 if(new_encoding != ENCBINARY)
2750 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2752 else{
2753 if(body->type == TYPEOTHER){
2754 body->type = TYPEAPPLICATION;
2755 body->subtype = cpystr("octet-stream");
2759 * Apply maximal encoding regardless of previous
2760 * setting. This segment's either not text, or is
2761 * unlikely to be readable with > 30% of the
2762 * text encoded anyway, so we might as well save space...
2764 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2768 #ifdef ENCODE_FROMS
2769 /* If there were From_'s at the beginning of a line or standalone dots */
2770 if((froms || dots) && new_encoding != ENCBINARY)
2771 new_encoding = ENC8BIT;
2772 #endif
2774 /* Set the subtype */
2775 if(body->subtype == NULL)
2776 body->subtype = cpystr(rfc822_default_subtype(body->type));
2778 if(body->encoding == ENCOTHER)
2779 body->encoding = new_encoding;
2781 fs_give((void **)&buf);
2783 if(we_cancel)
2784 cancel_busy_cue(-1);
2789 * Call this to set the charset of an attachment we have
2790 * created. If the attachment contains any non-ascii characters
2791 * then we'll set the charset to the passed in charset, otherwise
2792 * we'll make it us-ascii.
2794 void
2795 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2797 unsigned char c;
2798 int can_be_ascii = 1;
2799 STORE_S *so = (STORE_S *)body->contents.text.data;
2800 int we_cancel = 0;
2802 if(!body || body->type != TYPETEXT)
2803 return;
2805 we_cancel = busy_cue(NULL, NULL, 1);
2807 so_seek(so, 0L, 0);
2809 while(can_be_ascii && so_readc(&c, so))
2810 if(!c || c & 0x80)
2811 can_be_ascii--;
2813 if(can_be_ascii)
2814 set_parameter(&body->parameter, "charset", "US-ASCII");
2815 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2816 set_parameter(&body->parameter, "charset", charset);
2817 else{
2819 * Else we don't know. There are non ascii characters but we either
2820 * don't have a charset to set it to or that charset is just us_ascii,
2821 * which is impossible. So we label it unknown. An alternative would
2822 * have been to strip the high bits instead and label it ascii.
2824 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2827 if(we_cancel)
2828 cancel_busy_cue(-1);
2833 * since encoding happens on the way out the door, this is basically
2834 * just needed to handle TYPEMULTIPART
2836 void
2837 pine_encode_body (struct mail_bodystruct *body)
2839 PART *part;
2841 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2842 if (body) switch (body->type) {
2843 char *freethis;
2845 case TYPEMULTIPART: /* multi-part */
2846 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2847 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2849 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2850 (long) getpid ());
2851 tmp[sizeof(tmp)-1] = '\0';
2852 set_parameter(&body->parameter, "boundary", tmp);
2855 if(freethis)
2856 fs_give((void **) &freethis);
2858 part = body->nested.part; /* encode body parts */
2859 do pine_encode_body (&part->body);
2860 while ((part = part->next) != NULL); /* until done */
2861 break;
2863 case TYPETEXT :
2865 * If the part is text we edited, then it is UTF-8.
2866 * The user may be asking us to send it as something else
2867 * or we may want to downconvert to a more-specific characterset.
2868 * Mark it for conversion here so the right MIME header's written.
2869 * Do conversion pine_rfc822_output_body.
2870 * Attachments are left as is.
2872 if(body->contents.text.data
2873 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2874 char *charset, *posting_charset, *lp;
2876 if(!((charset = parameter_val(body->parameter, "charset"))
2877 && !strucmp(charset, UNKNOWN_CHARSET))
2878 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2880 set_parameter(&body->parameter, "charset", posting_charset);
2883 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2884 * and doesn't use anything but ASCII characters.
2885 * Why is it not ENC7BIT already? Because when we set the encoding
2886 * in set_mime_type_by_grope we were groping through UTF-8 text
2887 * not 2022 text. Not only that, but we didn't know at that point
2888 * that it wouldn't stay UTF-8 when we sent it, which would require
2889 * encoding.
2891 if(!strucmp(posting_charset, "iso-2022-jp")
2892 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2893 && strlen(lp) < 4)
2894 body->encoding = ENC7BIT;
2897 if(charset)
2898 fs_give((void **)&charset);
2901 break;
2903 /* case MESSAGE: */ /* here for documentation */
2904 /* Encapsulated messages are always treated as text objects at this point.
2905 This means that you must replace body->contents.msg with
2906 body->contents.text, which probably involves copying
2907 body->contents.msg.text to body->contents.text */
2908 default: /* all else has some encoding */
2910 * but we'll delay encoding it until the message is on the way
2911 * into the mail slot...
2913 break;
2919 * pine_header_line - simple wrapper around c-client call to contain
2920 * repeated code, and to write fcc if required.
2923 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2924 int writehdr, int localcopy)
2926 int ret = 1;
2927 int flags;
2928 int big = 10000;
2929 char *value, *folded = NULL, *cs;
2930 char *converted;
2932 if(!text)
2933 return 1;
2935 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2937 if(converted){
2938 if(cs && !strucmp(cs, "us-ascii"))
2939 value = converted;
2940 else
2941 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2942 (unsigned char *) converted, cs,
2943 encode_whole_header(field, header));
2945 if(value && value == converted){ /* no encoding was done, have to fold */
2946 int fold_by, len;
2947 char *actual_field;
2949 len = ((header && header->env && header->env->remail)
2950 ? strlen("ReSent-") : 0) +
2951 (field ? strlen(field) : 0) + 2;
2953 actual_field = (char *)fs_get((len+1) * sizeof(char));
2954 snprintf(actual_field, len+1, "%s%s: ",
2955 (header && header->env && header->env->remail) ? "ReSent-" : "",
2956 field ? field : "");
2957 actual_field[len] = '\0';
2960 * We were folding everything except message-id, but that wasn't
2961 * sufficient. Since 822 only allows folding where linear-white-space
2962 * is allowed we'd need a smarter folder than "fold" to do it. So,
2963 * instead of inventing that smarter folder (which would have to
2964 * know 822 syntax)
2966 * We could just alloc space and copy the actual_field followed by
2967 * the value into it, but since that's what fold does anyway we'll
2968 * waste some cpu time and use fold with a big fold parameter.
2970 * We upped the references folding from 75 to 256 because we were
2971 * encountering longer-than-75 message ids, and to break one line
2972 * in references is to break them all.
2974 * Also, some users are adding long text without spaces to the subject
2975 * line (e.g. URLs) so we up that number too. For the moment this is
2976 * going to 512 (a url that is about 6 lines long.)
2978 flags = FLD_CRLF;
2979 if(field && !strucmp("Subject", field)){
2980 fold_by = 75;
2981 flags |= FLD_NEXTSPC;
2983 else if(field && !strucmp("References", field))
2984 fold_by = 256;
2985 else
2986 fold_by = big;
2988 folded = fold(value, fold_by, big, actual_field, " ", flags);
2990 if(actual_field)
2991 fs_give((void **)&actual_field);
2993 else if(value){ /* encoding was done */
2994 RFC822BUFFER rbuf;
2995 size_t ll;
2998 * rfc1522_encode already inserted continuation lines and did
2999 * the necessary folding so we don't have to do it. Let
3000 * rfc822_header_line add the trailing crlf and the resent- if
3001 * necessary. The 20 could actually be a 12.
3003 ll = strlen(field) + strlen(value) + 20;
3004 folded = (char *) fs_get(ll * sizeof(char));
3005 *folded = '\0';
3006 rbuf.f = dummy_soutr;
3007 rbuf.s = NULL;
3008 rbuf.beg = folded;
3009 rbuf.cur = folded;
3010 rbuf.end = folded+ll-1;
3011 rfc822_output_header_line(&rbuf, field,
3012 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3013 *rbuf.cur = '\0';
3016 if(value && folded){
3017 if(writehdr && f)
3018 ret = (*f)(s, folded);
3020 if(ret && localcopy && lmc.so && !lmc.all_written)
3021 ret = so_puts(lmc.so, folded);
3024 if(folded)
3025 fs_give((void **)&folded);
3027 if(converted && converted != text)
3028 fs_give((void **) &converted);
3030 else
3031 ret = 0;
3033 return(ret);
3038 * Do appropriate encoding of text header lines.
3039 * For some field types (those that consist of 822 *text) we just encode
3040 * the whole thing. For structured fields we encode only within comments
3041 * if possible.
3043 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3044 * s -- Source string.
3045 * charset -- Charset to encode with.
3046 * encode_all -- If set, encode the whole string. If not, try to encode
3047 * only within comments if possible.
3049 * Returns S is returned if no encoding is done. D is returned if encoding
3050 * was needed.
3052 char *
3053 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3055 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3056 int in_comment = 0;
3058 if(!s)
3059 return((char *)s);
3061 if(dlen < SIZEOF_20KBUF)
3062 alpine_panic("bad call to encode_header_value");
3064 if(!encode_all){
3066 * We don't have to worry about keeping track of quoted-strings because
3067 * none of these fields which aren't addresses contain quoted-strings.
3068 * We do keep track of escaped parens inside of comments and comment
3069 * nesting.
3071 p = d+7000;
3072 for(q = (char *)s; *q; q++){
3073 switch(*q){
3074 case LPAREN:
3075 if(in_comment++ == 0)
3076 start_of_comment = q;
3078 break;
3080 case RPAREN:
3081 if(--in_comment == 0){
3082 /* encode the comment, excluding the outer parens */
3083 if(p-d < dlen-1)
3084 *p++ = LPAREN;
3086 *q = '\0';
3087 r = rfc1522_encode(d+14000, dlen-14000,
3088 (unsigned char *)start_of_comment+1,
3089 charset);
3090 if(r != start_of_comment+1)
3091 value = d+7000; /* some encoding was done */
3093 start_of_comment = NULL;
3094 if(r)
3095 sstrncpy(&p, r, dlen-1-(p-d));
3097 *q = RPAREN;
3098 if(p-d < dlen-1)
3099 *p++ = *q;
3101 else if(in_comment < 0){
3102 in_comment = 0;
3103 if(p-d < dlen-1)
3104 *p++ = *q;
3107 break;
3109 case BSLASH:
3110 if(!in_comment && *(q+1)){
3111 if(p-d < dlen-2){
3112 *p++ = *q++;
3113 *p++ = *q;
3117 break;
3119 default:
3120 if(!in_comment && p-d < dlen-1)
3121 *p++ = *q;
3123 break;
3127 if(value){
3128 /* Unterminated comment (wasn't really a comment) */
3129 if(start_of_comment)
3130 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3132 *p = '\0';
3137 * We have to check if there is anything that needs to be encoded that
3138 * wasn't in a comment. If there is, we'd better just start over and
3139 * encode the whole thing. So, if no encoding has been done within
3140 * comments, or if encoding is needed both within and outside of
3141 * comments, then we encode the whole thing. Otherwise, go with
3142 * the version that has only comments encoded.
3144 if(!value || rfc1522_encode(d, dlen,
3145 (unsigned char *)value, charset) != value)
3146 return(rfc1522_encode(d, dlen, s, charset));
3147 else{
3148 strncpy(d, value, dlen-1);
3149 d[dlen-1] = '\0';
3150 return(d);
3156 * pine_address_line - write a header field containing addresses,
3157 * one by one (so there's no buffer limit), and
3158 * wrapping where necessary.
3159 * Note: we use c-client functions to properly build the text string,
3160 * but have to screw around with pointers to fool c-client functions
3161 * into not blatting all the text into a single buffer. Yeah, I know.
3164 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3165 soutr_t f, void *s, int writehdr, int localcopy)
3167 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3168 size_t alloced = 0, sz;
3169 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3170 char *converted, *cs;
3171 ADDRESS *atmp;
3172 int i, count;
3173 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3174 RFC822BUFFER rbuf;
3175 static char comma[] = ", ";
3176 static char end_group[] = ";";
3177 #define no_comma (&comma[1])
3179 if(!alist) /* nothing in field! */
3180 return(1);
3182 if(!alist->host && alist->mailbox){ /* c-client group convention */
3183 in_group++;
3184 was_start_of_group++;
3185 /* encode mailbox of group */
3186 mtmp = alist->mailbox;
3187 if(mtmp){
3188 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3189 buftmp[sizeof(buftmp)-1] = '\0';
3190 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3191 if(converted){
3192 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3193 (unsigned char *) converted, cs));
3194 if(converted && converted != buftmp)
3195 fs_give((void **) &converted);
3197 else{
3198 failed++;
3199 goto bail_out;
3203 else
3204 mtmp = NULL;
3206 ptmp = alist->personal; /* remember personal name */
3207 /* make sure personal name is encoded */
3208 if(ptmp){
3209 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3210 buftmp[sizeof(buftmp)-1] = '\0';
3211 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3212 if(converted){
3213 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3214 (unsigned char *) converted, cs));
3215 if(converted && converted != buftmp)
3216 fs_give((void **) &converted);
3218 else{
3219 failed++;
3220 goto bail_out;
3224 atmp = alist->next;
3225 alist->next = NULL; /* digest only first address! */
3227 /* use automatic buffer unless it isn't big enough */
3228 if((alloced = est_size(alist)) > sizeof(tmp)){
3229 tmpptr = (char *)fs_get(alloced);
3230 sz = alloced;
3232 else{
3233 tmpptr = tmp;
3234 sz = sizeof(tmp);
3237 rbuf.f = dummy_soutr;
3238 rbuf.s = NULL;
3239 rbuf.beg = tmpptr;
3240 rbuf.cur = tmpptr;
3241 rbuf.end = tmpptr+sz-1;
3242 rfc822_output_address_line(&rbuf, field,
3243 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3244 *rbuf.cur = '\0';
3246 alist->next = atmp; /* restore pointer to next addr */
3248 if(alist->personal && alist->personal != ptmp)
3249 fs_give((void **) &alist->personal);
3251 alist->personal = ptmp; /* in case it changed, restore name */
3253 if(mtmp){
3254 if(alist->mailbox && alist->mailbox != mtmp)
3255 fs_give((void **) &alist->mailbox);
3257 alist->mailbox = mtmp;
3260 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3261 count -= 2;
3262 tmpptr[count] = '\0';
3266 * If there is no sending_stream and we are writing the Lcc header,
3267 * then we are piping it to sendmail -t which expects it to be a bcc,
3268 * not lcc.
3270 * When we write it to the fcc or postponed (the lmc.so),
3271 * we want it to be lcc, not bcc, so we put it back.
3273 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3274 fix_lcc = 1;
3276 if(writehdr && f && *tmpptr){
3277 if(fix_lcc)
3278 tmpptr[0] = 'b';
3280 failed = !(*f)(s, tmpptr);
3281 if(fix_lcc)
3282 tmpptr[0] = 'L';
3284 if(failed)
3285 goto bail_out;
3288 if(localcopy && lmc.so &&
3289 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3290 goto bail_out;
3292 for(alist = atmp; alist; alist = alist->next){
3293 delim = comma;
3294 /* account for c-client's representation of group names */
3295 if(in_group){
3296 if(!alist->host){ /* end of group */
3297 in_group = 0;
3298 was_start_of_group = 0;
3300 * Rfc822_write_address no longer writes out the end of group
3301 * unless the whole group address is passed to it, so we do
3302 * it ourselves.
3304 delim = end_group;
3306 else if(!localcopy || !lmc.so || lmc.all_written)
3307 continue;
3309 /* start of new group, print phrase below */
3310 else if(!alist->host && alist->mailbox){
3311 in_group++;
3312 was_start_of_group++;
3315 /* no comma before first address in group syntax */
3316 if(was_start_of_group && alist->host){
3317 delim = no_comma;
3318 was_start_of_group = 0;
3321 /* write delimiter */
3322 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3323 || (localcopy && lmc.so && !lmc.all_written
3324 && !so_puts(lmc.so, delim)))
3325 goto bail_out;
3327 ptmp = alist->personal; /* remember personal name */
3328 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3329 buftmp[sizeof(buftmp)-1] = '\0';
3330 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3331 if(converted){
3332 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3333 (unsigned char *) converted, cs));
3334 if(converted && converted != buftmp)
3335 fs_give((void **) &converted);
3337 else{
3338 failed++;
3339 goto bail_out;
3342 atmp = alist->next;
3343 alist->next = NULL; /* tie off linked list */
3344 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3345 alloced = i;
3346 sz = alloced;
3347 fs_resize((void **)&tmpptr, alloced);
3350 *tmpptr = '\0';
3351 /* make sure we don't write out group end with rfc822_write_address */
3352 if(alist->host || alist->mailbox){
3353 rbuf.f = dummy_soutr;
3354 rbuf.s = NULL;
3355 rbuf.beg = tmpptr;
3356 rbuf.cur = tmpptr;
3357 rbuf.end = tmpptr+sz-1;
3358 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3359 *rbuf.cur = '\0';
3362 alist->next = atmp; /* restore next pointer */
3364 if(alist->personal && alist->personal != ptmp)
3365 fs_give((void **) &alist->personal);
3367 alist->personal = ptmp; /* in case it changed, restore name */
3370 * BUG
3371 * With group syntax addresses we no longer have two identical
3372 * streams of output. Instead, for the fcc/postpone copy we include
3373 * all of the addresses inside the :; of the group, and for the
3374 * mail we're sending we don't include them. That means we aren't
3375 * correctly keeping track of the column to wrap in, below. That is,
3376 * we are keeping track of the fcc copy but we aren't keeping track
3377 * of the regular copy. It could result in too long or too short
3378 * lines. Should almost never come up since group addresses are almost
3379 * never followed by other addresses in the same header, and even
3380 * when they are, you have to go out of your way to get the headers
3381 * messed up.
3383 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3384 count = i + 4;
3385 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3386 || (localcopy && lmc.so && !lmc.all_written &&
3387 !so_puts(lmc.so, "\015\012 ")))
3388 goto bail_out;
3390 else
3391 count += i + 2;
3393 if(((!in_group || was_start_of_group)
3394 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3395 || (localcopy && lmc.so && !lmc.all_written
3396 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3397 goto bail_out;
3400 bail_out:
3401 if(tmpptr && tmpptr != tmp)
3402 fs_give((void **)&tmpptr);
3404 if(failed)
3405 return(0);
3407 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3408 && ((localcopy && lmc.so
3409 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3414 * mutated pine version of c-client's rfc822_header() function.
3415 * changed to call pine-wrapped header and address functions
3416 * so we don't have to limit the header size to a fixed buffer.
3417 * This function also calls pine's body_header write function
3418 * because encoding is delayed until output_body() is called.
3420 long
3421 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3423 PINEFIELD *pf;
3424 int j;
3426 if(header->env->remail){ /* if remailing */
3427 long i = strlen (header->env->remail);
3428 if(i > 4 && header->env->remail[i-4] == '\015')
3429 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3431 if((f && !(*f)(s, header->env->remail))
3432 || (lmc.so && !lmc.all_written
3433 && !so_puts(lmc.so, header->env->remail)))
3434 return(0L); /* start with remail header */
3437 j = 0;
3438 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3439 switch(pf->type){
3441 * Warning: This is confusing. The 2nd to last argument used to
3442 * be just pf->writehdr. We want Bcc lines to be written out
3443 * if we are handing off to a sendmail temp file but not if we
3444 * are talking smtp, so bcc's writehdr is set to 0 and
3445 * pine_address_line was sending if writehdr OR !sending_stream.
3446 * That works as long as we want to write everything when
3447 * !sending_stream (an mta handoff to sendmail). But then we
3448 * added the undisclosed recipients line which should only get
3449 * written if writehdr is set, and not when we pass to a
3450 * sendmail temp file. So pine_address_line has been changed
3451 * so it bases its decision solely on the writehdr passed to it,
3452 * and the logic that worries about Bcc and sending_stream
3453 * was moved up to the caller (here) to decide when to set it.
3455 * So we have:
3456 * undisclosed recipients:; This will just be written
3457 * if writehdr was set and not
3458 * otherwise, nothing magical.
3459 *** We may want to change this, because sendmail -t doesn't handle
3460 *** the empty group syntax well unless it has been configured to
3461 *** do so. It isn't configured by default, or in any of the
3462 *** sendmail v8 configs. So we may want to not write this line
3463 *** if we're doing an mta_handoff (!sending_stream).
3465 * !sending_stream (which means a handoff to a sendmail -t)
3466 * bcc or lcc both set the arg so they'll get written
3467 * (There is also Lcc hocus pocus in pine_address_line
3468 * which converts the Lcc: to Bcc: for sendmail
3469 * processing.)
3470 * sending_stream (which means an smtp handoff)
3471 * bcc and lcc will never have writehdr set, so
3472 * will never be written (They both do have rcptto set,
3473 * so they both do cause RCPT TO commands.)
3475 * The localcopy is independent of sending_stream and is just
3476 * written if it is set for all of these.
3478 case Address:
3479 if(!pine_address_line(pf->name,
3480 header,
3481 pf->addr ? *pf->addr : NULL,
3484 (!strucmp("bcc",pf->name ? pf->name : "")
3485 || !strucmp("Lcc",pf->name ? pf->name : ""))
3486 ? !sending_stream
3487 : pf->writehdr,
3488 pf->localcopy))
3489 return(0L);
3491 break;
3493 case Fcc:
3494 case FreeText:
3495 case Subject:
3496 if(!pine_header_line(pf->name, header,
3497 pf->text ? *pf->text : NULL,
3498 f, s, pf->writehdr, pf->localcopy))
3499 return(0L);
3501 break;
3503 default:
3504 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3505 pf->name);
3506 break;
3511 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3513 * Add comforting "X-" header line indicating what sort of
3514 * authenticity the receiver can expect...
3516 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3517 NETMBX netmbox;
3518 char sstring[MAILTMPLEN], *label; /* place to write */
3519 MAILSTREAM *m;
3520 int i, anonymous = 1;
3522 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3523 m = ps_global->s_pool.streams[i];
3524 if(m && sp_flagged(m, SP_LOCKED)
3525 && mail_valid_net_parse(m->mailbox, &netmbox)
3526 && !netmbox.anoflag)
3527 anonymous = 0;
3530 if(!anonymous){
3531 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3532 *user = (*netmbox.user)
3533 ? netmbox.user
3534 : cached_user_name(netmbox.mailbox);
3535 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3536 isdigit((unsigned char)last_char) ? "[" : "",
3537 netmbox.host,
3538 isdigit((unsigned char) last_char) ? "]" : "");
3539 sstring[sizeof(sstring)-1] = '\0';
3540 label = "X-X-Sender"; /* Jeez. */
3541 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3542 label += 4;
3544 else{
3545 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3546 sstring[sizeof(sstring)-1] = '\0';
3547 label = "X-Warning";
3550 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3551 return(0L);
3553 #endif
3555 if(body && !header->env->remail){ /* not if remail or no body */
3556 if((f && !(*f)(s, MIME_VER))
3557 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3558 || !pine_write_body_header(body, f, s))
3559 return(0L);
3561 else{ /* write terminating newline */
3562 if((f && !(*f)(s, "\015\012"))
3563 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3564 return(0L);
3567 return(1L);
3572 * pine_rfc822_output - pine's version of c-client call. Necessary here
3573 * since we're not using its structures as intended!
3575 long
3576 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3578 int we_cancel = 0;
3579 long retval;
3581 dprint((4, "-- pine_rfc822_output\n"));
3583 we_cancel = busy_cue(NULL, NULL, 1);
3584 pine_encode_body(body); /* encode body as necessary */
3585 /* build and output RFC822 header, output body */
3586 retval = pine_rfc822_header(header, body, f, s)
3587 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3589 if(we_cancel)
3590 cancel_busy_cue(-1);
3592 return(retval);
3597 * post_rfc822_output - cloak for pine's 822 output routine. Since
3598 * we can't pass opaque envelope thru c-client posting
3599 * logic, we need to wrap the real output inside
3600 * something that c-client knows how to call.
3602 long
3603 post_rfc822_output(char *tmp,
3604 ENVELOPE *env,
3605 struct mail_bodystruct *body,
3606 soutr_t f,
3607 void *s,
3608 long int ok8bit)
3610 return(pine_rfc822_output(send_header, body, f, s));
3615 * posting_characterset- determine what transliteration is reasonable
3616 * for posting the given non-ascii message data.
3618 * preferred_charset is the charset the original data was labeled in.
3619 * If we can keep that we do.
3621 * Returns: always returns the preferred character set.
3623 char *
3624 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3626 unsigned long *charsetmap = NULL;
3627 unsigned long validbitmap;
3628 static char *ascii = "US-ASCII";
3629 static char *utf8 = "UTF-8";
3630 int notcjk = 0;
3632 if(!ps_global->post_utf8){
3633 validbitmap = 0;
3635 if(mp == HdrText){
3636 char *text = NULL;
3637 UCS *ucs = NULL, *ucsp;
3639 text = (char *) data;
3641 /* convert text in header to UCS characters */
3642 if(text)
3643 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3645 if(!(ucs && *ucs))
3646 return(ascii);
3649 * After the while loop is done the validbitmap has
3650 * a 1 bit for all the character sets that can
3651 * represent all of the characters of this header.
3653 charsetmap = init_charsetchecker(preferred_charset);
3655 if(!charsetmap){
3656 if (ucs != NULL) fs_give((void **) &ucs);
3657 return(utf8);
3660 validbitmap = ~0;
3661 while((validbitmap & ~0x1) && (*ucsp)){
3662 if(*ucsp > 0xffff){
3663 fs_give((void **) &ucs);
3664 return(utf8);
3667 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3670 fs_give((void **) &ucs);
3672 notcjk = validbitmap & 0x1;
3673 validbitmap &= ~0x1;
3675 if(!validbitmap)
3676 return(utf8);
3678 else{
3679 struct mail_bodystruct *body = NULL;
3680 STORE_S *the_text = NULL;
3681 int outchars;
3682 unsigned char c;
3683 UCS ucs;
3684 CBUF_S cbuf;
3686 cbuf.cbuf[0] = '\0';
3687 cbuf.cbufp = cbuf.cbuf;
3688 cbuf.cbufend = cbuf.cbuf;
3690 body = (struct mail_bodystruct *) data;
3692 if(body && body->type == TYPEMULTIPART)
3693 body = &body->nested.part->body;
3695 if(body && body->type == TYPETEXT)
3696 the_text = (STORE_S *) body->contents.text.data;
3698 if(!the_text)
3699 return(ascii);
3701 so_seek(the_text, 0L, 0); /* rewind */
3703 charsetmap = init_charsetchecker(preferred_charset);
3705 if(!charsetmap)
3706 return(utf8);
3708 validbitmap = ~0;
3711 * Read a stream of UTF-8 characters from the_text
3712 * and convert them to UCS-4 characters for the translatable
3713 * test.
3715 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3716 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3717 /* got a ucs character */
3718 if(ucs > 0xffff)
3719 return(utf8);
3721 validbitmap &= charsetmap[(unsigned long) ucs];
3725 notcjk = validbitmap & 0x1;
3726 validbitmap &= ~0x1;
3728 if(!validbitmap)
3729 return(utf8);
3732 /* user chooses something other than UTF-8 */
3733 if(strucmp(ps_global->posting_charmap, utf8)){
3735 * If we're to post in other than UTF-8, and it can be
3736 * transliterated without losing fidelity, do it.
3737 * Else, use UTF-8.
3740 /* if ascii works, always use that */
3741 if(representable_in_charset(validbitmap, ascii))
3742 return(ascii);
3744 /* does the user's posting character set work? */
3745 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3746 return(ps_global->posting_charmap);
3748 /* this is the charset the message we are replying to was in */
3749 if(preferred_charset
3750 && strucmp(preferred_charset, ascii)
3751 && representable_in_charset(validbitmap, preferred_charset))
3752 return(preferred_charset);
3754 /* else, use UTF-8 */
3757 /* user chooses nothing, going with the default */
3758 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3759 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3760 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3761 char *most_preferred;
3764 * In this case the user didn't specify a posting character set
3765 * and we will choose the most-specific one from our list.
3768 /* ascii is best */
3769 if(representable_in_charset(validbitmap, ascii))
3770 return(ascii);
3772 /* Can we keep the original from the message we're replying to? */
3773 if(preferred_charset
3774 && strucmp(preferred_charset, ascii)
3775 && representable_in_charset(validbitmap, preferred_charset))
3776 return(preferred_charset);
3778 /* choose the best of the rest */
3779 most_preferred = most_preferred_charset(validbitmap);
3780 if(!most_preferred)
3781 return(utf8);
3784 * If the text we're labeling contains something like
3785 * smart quotes but no CJK characters, then instead of
3786 * labeling it as ISO-2022-JP we want to use UTF-8.
3788 if(notcjk){
3789 const CHARSET *cs;
3791 cs = utf8_charset(most_preferred);
3792 if(!cs
3793 || cs->script == SC_CHINESE_SIMPLIFIED
3794 || cs->script == SC_CHINESE_TRADITIONAL
3795 || cs->script == SC_JAPANESE
3796 || cs->script == SC_KOREAN)
3797 return(utf8);
3800 return(most_preferred);
3802 /* user explicitly chooses UTF-8 */
3803 else{
3804 /* if ascii works, always use that */
3805 if(representable_in_charset(validbitmap, ascii))
3806 return(ascii);
3808 /* else, use UTF-8 */
3813 return(utf8);
3817 static char **charsetlist = NULL;
3818 static int items_in_charsetlist = 0;
3819 static unsigned long *charsetmap = NULL;
3821 static char *downgrades[] = {
3822 "US-ASCII",
3823 "ISO-8859-15",
3824 "ISO-8859-1",
3825 "ISO-8859-2",
3826 "VISCII",
3827 "KOI8-R",
3828 "KOI8-U",
3829 "ISO-8859-7",
3830 "ISO-8859-6",
3831 "ISO-8859-8",
3832 "TIS-620",
3833 "ISO-2022-JP",
3834 "GB2312",
3835 "BIG5",
3836 "EUC-KR"
3840 unsigned long *
3841 init_charsetchecker(char *preferred_charset)
3843 int i, count = 0, reset = 0;
3844 char *ascii = "US-ASCII";
3845 char *utf8 = "UTF-8";
3848 * When user doesn't set a posting character set posting_charmap ends up
3849 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3850 * That's where the strange set of if-else's come from.
3853 /* user chooses something other than UTF-8 */
3854 if(strucmp(ps_global->posting_charmap, utf8)){
3855 count++; /* US-ASCII */
3856 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3857 reset++;
3859 /* if posting_charmap is valid, include it in list */
3860 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3861 && strucmp(ps_global->posting_charmap, ascii)
3862 && strucmp(ps_global->posting_charmap, utf8)
3863 && utf8_charset(ps_global->posting_charmap)){
3864 count++;
3865 if(!reset
3866 && (items_in_charsetlist < count
3867 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3868 reset++;
3871 if(preferred_charset && preferred_charset[0]
3872 && strucmp(preferred_charset, ascii)
3873 && strucmp(preferred_charset, utf8)
3874 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3875 count++;
3876 if(!reset
3877 && (items_in_charsetlist < count
3878 || strucmp(charsetlist[count-1], preferred_charset)))
3879 reset++;
3882 if(items_in_charsetlist != count)
3883 reset++;
3885 if(reset){
3886 if(charsetlist)
3887 free_list_array(&charsetlist);
3889 items_in_charsetlist = count;
3890 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3892 i = 0;
3893 charsetlist[i++] = cpystr(ascii);
3895 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3896 && strucmp(ps_global->posting_charmap, ascii)
3897 && strucmp(ps_global->posting_charmap, utf8)
3898 && utf8_charset(ps_global->posting_charmap))
3899 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3901 if(preferred_charset && preferred_charset[0]
3902 && strucmp(preferred_charset, ascii)
3903 && strucmp(preferred_charset, utf8)
3904 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3905 charsetlist[i++] = cpystr(preferred_charset);
3907 charsetlist[i] = NULL;
3910 /* user chooses nothing, going with the default */
3911 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3912 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3913 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3914 int add_preferred = 0;
3916 /* does preferred_charset have to be added to the list? */
3917 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3918 add_preferred = 1;
3919 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3920 if(!strucmp(downgrades[i], preferred_charset))
3921 add_preferred = 0;
3924 if(add_preferred){
3925 /* existing list is right size already */
3926 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3927 /* just check to see if last list item is the preferred_charset */
3928 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3929 /* no, fix it */
3930 reset++;
3931 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3932 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3935 else{
3936 reset++;
3937 if(charsetlist)
3938 free_list_array(&charsetlist);
3940 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3941 items_in_charsetlist = count;
3942 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3943 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3944 charsetlist[i] = cpystr(downgrades[i]);
3946 charsetlist[i++] = cpystr(preferred_charset);
3947 charsetlist[i] = NULL;
3950 else{
3951 /* if list is same size as downgrades, consider it good */
3952 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
3953 reset++;
3955 if(reset){
3956 if(charsetlist)
3957 free_list_array(&charsetlist);
3959 count = sizeof(downgrades)/sizeof(downgrades[0]);
3960 items_in_charsetlist = count;
3961 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3962 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3963 charsetlist[i] = cpystr(downgrades[i]);
3965 charsetlist[i] = NULL;
3969 /* user explicitly chooses UTF-8 */
3970 else{
3971 /* include possibility of ascii even if they explicitly ask for UTF-8 */
3972 count++; /* US-ASCII */
3973 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3974 reset++;
3976 if(items_in_charsetlist != count)
3977 reset++;
3979 if(reset){
3980 if(charsetlist)
3981 free_list_array(&charsetlist);
3983 /* the list is just ascii and nothing else */
3984 items_in_charsetlist = count;
3985 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3987 i = 0;
3988 charsetlist[i++] = cpystr(ascii);
3989 charsetlist[i] = NULL;
3994 if(reset){
3995 if(charsetmap)
3996 fs_give((void **) &charsetmap);
3998 if(charsetlist)
3999 charsetmap = utf8_csvalidmap(charsetlist);
4002 return(charsetmap);
4006 /* total reset */
4007 void
4008 free_charsetchecker(void)
4010 if(charsetlist)
4011 free_list_array(&charsetlist);
4013 items_in_charsetlist = 0;
4015 if(charsetmap)
4016 fs_give((void **) &charsetmap);
4021 representable_in_charset(unsigned long validbitmap, char *charset)
4023 int i, done = 0, ret = 0;
4024 unsigned long j;
4026 if(!(charset && charset[0]))
4027 return ret;
4029 if(!strucmp(charset, "UTF-8"))
4030 return 1;
4032 for(i = 0; !done && i < items_in_charsetlist; i++){
4033 if(!strucmp(charset, charsetlist[i])){
4034 j = 1;
4035 j <<= (i+1);
4036 done++;
4037 if(validbitmap & j)
4038 ret = 1;
4042 return ret;
4046 char *
4047 most_preferred_charset(unsigned long validbitmap)
4049 unsigned long bm;
4050 unsigned long rm;
4051 int index;
4053 if(!(validbitmap && items_in_charsetlist > 0))
4054 return("UTF-8");
4056 /* careful, find_rightmost_bit modifies the bitmap */
4057 bm = validbitmap;
4058 rm = find_rightmost_bit(&bm);
4059 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4061 return(charsetlist[index]);
4064 void
4065 remove_parameter(PARAMETER **param, char *paramname)
4067 PARAMETER *pm;
4069 if(param == NULL || *param == NULL
4070 || paramname == NULL || *paramname == '\0')
4071 return;
4073 for(pm = *param;
4074 pm != NULL && pm->attribute != NULL && strucmp(pm->attribute, paramname);
4075 pm = pm->next);
4077 if(pm){
4078 PARAMETER om, *qm;
4079 om.next = *param;
4080 for(qm = &om; qm->next != pm; qm = qm->next);
4081 qm->next = copy_parameters(pm->next);
4082 mail_free_body_parameter(&pm);
4083 *param = om.next;
4089 * Set parameter to new value.
4091 void
4092 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4094 PARAMETER *pm;
4096 if(!param || !(paramname && *paramname))
4097 return;
4099 if(*param == NULL){
4100 pm = (*param) = mail_newbody_parameter();
4101 pm->attribute = cpystr(paramname);
4103 else{
4104 int nomatch;
4106 for(pm = *param;
4107 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4108 pm = pm->next)
4109 ;/* searching for paramname parameter */
4111 if(nomatch){ /* add charset parameter */
4112 pm->next = mail_newbody_parameter();
4113 pm = pm->next;
4114 pm->attribute = cpystr(paramname);
4116 /* else pm is existing paramname parameter */
4119 if(pm){
4120 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4121 if(pm->value)
4122 fs_give((void **) &pm->value);
4124 if(new_value)
4125 pm->value = cpystr(new_value);
4131 /*----------------------------------------------------------------------
4132 Remove the leading digits from SMTP error messages
4133 -----*/
4134 char *
4135 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4137 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4138 (*error == '.' && isdigit((unsigned char)*(error+1))))
4139 error++;
4141 snprintf(outbuf, outbuflen, printstring, error);
4142 outbuf[outbuflen-1] = '\0';
4143 return(outbuf);
4148 * Local globals pine's body output routine needs
4150 static soutr_t l_f;
4151 static TCPSTREAM *l_stream;
4152 static unsigned c_in_buf = 0;
4155 * def to make our pipe write's more friendly
4157 #ifdef PIPE_MAX
4158 #if PIPE_MAX > 20000
4159 #undef PIPE_MAX
4160 #endif
4161 #endif
4163 #ifndef PIPE_MAX
4164 #define PIPE_MAX 1024
4165 #endif
4169 * l_flust_net - empties gf_io terminal function's buffer
4172 l_flush_net(int force)
4174 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4175 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4177 tmp_20k_buf[c_in_buf] = '\0';
4178 if(!force){
4180 * The start of each write is expected to be the start of a
4181 * "record" (i.e., a CRLF terminated line). Make sure that is true
4182 * else we might screw up SMTP dot quoting...
4184 for(p = tmp_20k_buf, lp = NULL;
4185 (p = strstr(p, "\015\012")) != NULL;
4186 lp = (p += 2))
4190 if(!lp && c_in_buf > 2) /* no CRLF! */
4191 for(p = &tmp_20k_buf[c_in_buf] - 2;
4192 p > &tmp_20k_buf[0] && *p == '.';
4193 p--) /* find last non-dot */
4196 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4197 /* snippet remains */
4198 c = *lp;
4199 *lp = '\0';
4203 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4204 || (lmc.so && !lmc.all_written
4205 && !(lmc.text_only && lmc.text_written)
4206 && !so_puts(lmc.so, tmp_20k_buf)))
4207 return(0);
4209 c_in_buf = 0;
4210 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4211 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4212 c_in_buf++, lp++;
4215 return(1);
4220 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4224 l_putc(int c)
4226 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4227 tmp_20k_buf[c_in_buf++] = (char) c;
4229 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4235 * pine_rfc822_output_body - pine's version of c-client call. Again,
4236 * necessary since c-client doesn't know about how
4237 * we're treating attachments
4239 long
4240 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4242 STORE_S *bodyso;
4243 PART *part;
4244 PARAMETER *param;
4245 char *t, *cookie = NIL, *encode_error;
4246 char tmp[MAILTMPLEN];
4247 int add_trailing_crlf;
4248 LOC_2022_JP ljp;
4249 gf_io_t gc;
4251 dprint((4, "-- pine_rfc822_output_body: %d\n",
4252 body ? body->type : 0));
4254 bodyso = (STORE_S *) body->contents.text.data;
4256 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4257 part = body->nested.part; /* first body part */
4258 /* find cookie */
4259 for (param = body->parameter; param && !cookie; param = param->next)
4260 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4261 if (!cookie) cookie = "-"; /* yucky default */
4264 * Output a bit of text before the first multipart delimiter
4265 * to warn unsuspecting users of non-mime-aware ua's that
4266 * they should expect weirdness. We do not add this when signing a
4267 * message, though...
4269 #ifdef SMIME
4270 if(!ps_global->smime || !ps_global->smime->do_sign)
4271 #endif /* SMIME */
4272 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"))
4273 return(0);
4275 do { /* for each part */
4276 /* build cookie */
4277 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4278 tmp[sizeof(tmp)-1] = '\0';
4279 /* append cookie,mini-hdr,contents */
4280 if((f && !(*f)(s, tmp))
4281 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4282 || !pine_write_body_header(&part->body,f,s)
4283 || !pine_rfc822_output_body (&part->body,f,s))
4284 return(0);
4285 #if 0 /* temporariy disable this code */
4286 if(part == body->nested.part
4287 && ps_global->smime
4288 && ps_global->smime->do_sign
4289 && ((f && !(*f)(s, "\015\012"))
4290 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012"))))
4291 return 0;
4292 #endif /* SMIME */
4293 } while ((part = part->next) != NULL); /* until done */
4294 /* output trailing cookie */
4295 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4296 tmp[sizeof(tmp)-1] = '\0';
4297 #ifdef SMIME
4298 if(ps_global->smime && ps_global->smime->do_sign
4299 && strlen(tmp) < sizeof(tmp)-2)
4300 strncat(tmp, "\015\012", sizeof(tmp) - strlen(tmp) - 1);
4301 #endif
4302 if(lmc.so && !lmc.all_written){
4303 so_puts(lmc.so, t);
4304 so_puts(lmc.so, "\015\012");
4307 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4310 l_f = f; /* set up for writing chars... */
4311 l_stream = s; /* out other end of pipe... */
4312 gf_filter_init();
4313 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4314 body->size.bytes));
4316 if(bodyso)
4317 gf_set_so_readc(&gc, bodyso);
4318 else
4319 return(1);
4322 * Don't add trailing line if it is ExternalText, which already guarantees
4323 * a trailing newline.
4325 add_trailing_crlf = !(bodyso->src == ExternalText);
4327 so_seek(bodyso, 0L, 0);
4329 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4330 char *charset;
4332 if(body->type == TYPETEXT
4333 && so_attr(bodyso, "edited", NULL)
4334 && (charset = parameter_val(body->parameter, "charset"))){
4335 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4336 if(!strucmp(charset, "iso-2022-jp")){
4337 ljp.report_err = 0;
4338 gf_link_filter(gf_line_test,
4339 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4341 else{
4342 void *table = utf8_rmap(charset);
4344 if(table){
4345 gf_link_filter(gf_convert_utf8_charset,
4346 gf_convert_utf8_charset_opt(table,0));
4348 else{
4349 /* else, just send it? */
4350 set_parameter(&body->parameter, "charset", "UTF-8");
4355 fs_give((void **)&charset);
4359 * Convert text pieces to canonical form
4360 * BEFORE applying any encoding (rfc1341: appendix G)...
4361 * NOTE: almost all filters expect CRLF newlines
4363 if(body->type == TYPETEXT
4364 && body->encoding != ENCBASE64
4365 && !so_attr(bodyso, "rawbody", NULL)){
4366 gf_link_filter(gf_local_nvtnl, NULL);
4369 switch (body->encoding) { /* all else needs filtering */
4370 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4371 gf_link_filter(gf_8bit_qp, NULL);
4372 break;
4374 case ENCBINARY: /* encode binary into BASE64 */
4375 gf_link_filter(gf_binary_b64, NULL);
4376 break;
4378 default: /* otherwise text */
4379 break;
4383 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4384 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4385 _("Encoding Error \"%s\""), encode_error);
4386 display_message('x');
4389 gf_clear_so_readc(bodyso);
4391 if(encode_error || !l_flush_net(TRUE))
4392 return(0);
4394 send_bytes_sent += gf_bytes_piped();
4395 so_release((STORE_S *)body->contents.text.data);
4397 if(lmc.so && !lmc.all_written && lmc.text_only){
4398 if(lmc.text_written){ /* we have some splainin' to do */
4399 char tmp[MAILTMPLEN];
4400 char *name = NULL;
4402 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4403 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4404 return(0);
4407 * BUG: If this name is not ascii it's going to cause trouble.
4409 name = parameter_val(body->parameter, "name");
4410 snprintf(tmp, sizeof(tmp),
4411 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4412 body_type_names(body->type),
4413 body->subtype ? body->subtype : "Unknown",
4414 name ? " (Name=\"" : "",
4415 name ? name : "",
4416 name ? "\")" : "",
4417 comatose(body->size.bytes));
4418 tmp[sizeof(tmp)-1] = '\0';
4419 if(name)
4420 fs_give((void **)&name);
4422 if(!so_puts(lmc.so, tmp))
4423 return(0);
4425 else /* suppress everything after first text part */
4426 lmc.text_written = (body->type == TYPETEXT
4427 && (!body->subtype
4428 || !strucmp(body->subtype, "plain")));
4431 if(add_trailing_crlf)
4432 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4433 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4434 else
4435 return(1);
4438 char *
4439 ToLower(char *s, char *t)
4441 int i;
4443 for(i = 0; s != NULL && s[i] != '\0'; i++)
4444 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4445 t[i] = '\0';
4447 return t;
4451 * pine_write_body_header - another c-client clone. This time
4452 * so the final encoding labels get set
4453 * correctly since it hasn't happened yet,
4454 * and to be paranoid about line lengths.
4456 * Returns: TRUE/nonzero on success, zero on error
4459 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4461 char tmp[MAILTMPLEN];
4462 RFC822BUFFER rbuf;
4463 int i;
4464 unsigned char c;
4465 STRINGLIST *stl;
4466 STORE_S *so;
4467 extern const char *tspecials;
4469 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4470 if(!(so_puts(so, "Content-Type: ")
4471 && so_puts(so, ToLower(body_types[body->type], tmp))
4472 && so_puts(so, "/")
4473 && so_puts(so, ToLower(body->subtype
4474 ? body->subtype
4475 : rfc822_default_subtype (body->type),tmp))))
4476 return(pwbh_finish(0, so));
4478 if(body->parameter){
4479 if(!pine_write_params(body->parameter, so))
4480 return(pwbh_finish(0, so));
4482 else if ((body->type != TYPEMESSAGE
4483 || (body->subtype && strucmp(body->subtype, "RFC822")))
4484 && (!so_puts(so, "; CHARSET=US-ASCII")))
4485 return(pwbh_finish(0, so));
4487 if(!so_puts(so, "\015\012"))
4488 return(pwbh_finish(0, so));
4490 /* do not change the encoding of a TYPEMESSAGE part */
4491 if ((body->encoding /* note: encoding 7BIT never output! */
4492 && !(so_puts(so, "Content-Transfer-Encoding: ")
4493 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4494 ? (body->type == TYPEMESSAGE ? ENCBINARY : ENCBASE64)
4495 : (body->encoding == ENC8BIT)
4496 ?(body->type == TYPEMESSAGE ? ENC8BIT : ENCQUOTEDPRINTABLE)
4497 : (body->encoding <= ENCMAX)
4498 ? body->encoding
4499 : ENCOTHER])
4500 && so_puts(so, "\015\012")))
4502 * If requested, strip Content-ID headers that don't look like they
4503 * are needed. Microsoft's Outlook XP has a bug that causes it to
4504 * not show that there is an attachment when there is a Content-ID
4505 * header present on that attachment.
4507 * If user has quell-content-id turned on, don't output content-id
4508 * unless it is of type message/external-body.
4509 * Since this code doesn't look inside messages being forwarded
4510 * type message content-ids will remain as is and type multipart
4511 * alternative will remain as is. We don't create those on our
4512 * own. If we did, we'd have to worry about getting this right.
4514 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4515 || (body->type == TYPEMESSAGE
4516 && body->subtype
4517 && !strucmp(body->subtype, "external-body")))
4518 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4519 && so_puts(so, "\015\012")))
4520 || (body->description
4521 && strlen(body->description) < 5000 /* arbitrary! */
4522 && !pine_write_header_line("Content-Description: ", body->description, so))
4523 || (body->md5
4524 && !(so_puts(so, "Content-MD5: ")
4525 && so_puts(so, body->md5)
4526 && so_puts(so, "\015\012"))))
4527 return(pwbh_finish(0, so));
4529 if ((stl = body->language) != NULL) {
4530 if(!so_puts(so, "Content-Language: "))
4531 return(pwbh_finish(0, so));
4533 do {
4534 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4535 return(pwbh_finish(0, so));
4537 tmp[0] = '\0';
4538 rbuf.f = dummy_soutr;
4539 rbuf.s = NULL;
4540 rbuf.beg = tmp;
4541 rbuf.cur = tmp;
4542 rbuf.end = tmp+sizeof(tmp)-1;
4543 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4544 *rbuf.cur = '\0';
4546 if(!so_puts(so, tmp)
4547 || ((stl = stl->next) && !so_puts(so, ", ")))
4548 return(pwbh_finish(0, so));
4550 while (stl);
4552 if(!so_puts(so, "\015\012"))
4553 return(pwbh_finish(0, so));
4556 if (body->disposition.type) {
4557 if(!(so_puts(so, "Content-Disposition: ")
4558 && so_puts(so, body->disposition.type)))
4559 return(pwbh_finish(0, so));
4561 if(!pine_write_params(body->disposition.parameter, so))
4562 return(pwbh_finish(0, so));
4564 if(!so_puts(so, "\015\012"))
4565 return(pwbh_finish(0, so));
4568 /* copy out of so, a line at a time (or less than a K)
4569 * and send it down the pike
4571 so_seek(so, 0L, 0);
4572 i = 0;
4573 while(so_readc(&c, so))
4574 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4575 tmp[i] = '\0';
4576 if((f && !(*f)(s, tmp))
4577 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4578 return(pwbh_finish(0, so));
4580 i = 0;
4583 /* Finally, write blank line */
4584 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4585 return(pwbh_finish(0, so));
4587 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4590 return(0);
4595 * pine_write_header - convert, encode (if needed) and
4596 * write "header-name: field-body"
4599 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4601 char *cv, *cs, *vp;
4602 int rv;
4604 cs = posting_characterset(val, NULL, HdrText);
4605 cv = utf8_to_charset(val, cs, 0);
4606 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4607 (unsigned char *) cv, cs);
4609 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4611 if(cv && cv != val)
4612 fs_give((void **) &cv);
4615 return(rv);
4620 * pine_write_param - convert, encode and write MIME header-field parameters
4623 pine_write_params(PARAMETER *param, STORE_S *so)
4625 for(; param; param = param->next){
4626 int rv;
4627 char *cv, *cs;
4628 extern const char *tspecials;
4630 cs = posting_characterset(param->value, NULL, HdrText);
4631 cv = utf8_to_charset(param->value, cs, 0);
4632 rv = (so_puts(so, "; ")
4633 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4635 if(cv && cv != param->value)
4636 fs_give((void **) &cv);
4638 if(!rv)
4639 return(0);
4642 return(1);
4648 lmc_body_header_line(char *line, int beginning)
4650 if(lmc.so && !lmc.all_written){
4651 if(beginning && lmc.text_only && lmc.text_written
4652 && (!struncmp(line, "content-type:", 13)
4653 || !struncmp(line, "content-transfer-encoding:", 26)
4654 || !struncmp(line, "content-disposition:", 20))){
4656 * "comment out" the real values since our comment isn't
4657 * likely the same type, disposition nor encoding...
4659 if(!so_puts(lmc.so, "X-"))
4660 return(FALSE);
4663 return(so_puts(lmc.so, line));
4666 return(TRUE);
4671 lmc_body_header_finish(void)
4673 if(lmc.so && !lmc.all_written){
4674 if(lmc.text_only && lmc.text_written
4675 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4676 return(FALSE);
4678 return(so_puts(lmc.so, "\015\012"));
4681 return(TRUE);
4687 pwbh_finish(int rv, STORE_S *so)
4689 if(so)
4690 so_give(&so);
4692 return(rv);
4697 * pine_free_body - c-client call wrapper so the body data pointer we
4698 * we're using in a way c-client doesn't know about
4699 * gets free'd appropriately.
4701 void
4702 pine_free_body(struct mail_bodystruct **body)
4705 * Preempt c-client's contents.text.data clean up since we've
4706 * usurped it's meaning for our own purposes...
4708 pine_free_body_data (*body);
4710 /* Then let c-client handle the rest... */
4711 mail_free_body(body);
4716 * pine_free_body_data - free pine's interpretations of the body part's
4717 * data pointer.
4719 void
4720 pine_free_body_data(struct mail_bodystruct *body)
4722 if(body){
4723 if(body->type == TYPEMULTIPART){
4724 PART *part = body->nested.part;
4725 do /* for each part */
4726 pine_free_body_data(&part->body);
4727 while ((part = part->next) != NULL); /* until done */
4729 else if(body->contents.text.data)
4730 so_give((STORE_S **) &body->contents.text.data);
4735 long
4736 send_body_size(struct mail_bodystruct *body)
4738 long l = 0L;
4739 PART *part;
4741 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4742 part = body->nested.part; /* first body part */
4743 do /* for each part */
4744 l += send_body_size(&part->body);
4745 while ((part = part->next) != NULL); /* until done */
4746 return(l);
4749 return(l + body->size.bytes);
4754 sent_percent(void)
4756 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4757 / send_bytes_to_send);
4758 return(MIN(i, 100));
4763 * pine_smtp_verbose_out - write
4765 void
4766 pine_smtp_verbose_out(char *s)
4768 #ifdef _WINDOWS
4769 LPTSTR slpt;
4770 #endif
4771 if(verbose_send_output && s){
4772 char *p, last = '\0';
4774 for(p = s; *p; p++)
4775 if(*p == '\015')
4776 *p = ' ';
4777 else
4778 last = *p;
4780 #ifdef _WINDOWS
4782 * The stream is opened in Unicode mode, so we need to fix the
4783 * argument to fputs.
4785 slpt = utf8_to_lptstr((LPSTR) s);
4786 if(slpt){
4787 _fputts(slpt, verbose_send_output);
4788 fs_give((void **) &slpt);
4791 if(last != '\012')
4792 _fputtc(L'\n', verbose_send_output);
4793 #else
4794 fputs(s, verbose_send_output);
4795 if(last != '\012')
4796 fputc('\n', verbose_send_output);
4797 #endif
4804 * pine_header_forbidden - is this name a "forbidden" header?
4806 * name - the header name to check
4807 * We don't allow user to change these.
4810 pine_header_forbidden(char *name)
4812 char **p;
4813 static char *forbidden_headers[] = {
4814 "sender",
4815 "x-sender",
4816 "x-x-sender",
4817 "date",
4818 "received",
4819 "message-id",
4820 "in-reply-to",
4821 "path",
4822 "resent-message-id",
4823 "resent-date",
4824 "resent-from",
4825 "resent-sender",
4826 "resent-to",
4827 "resent-cc",
4828 "resent-reply-to",
4829 "mime-version",
4830 "content-type",
4831 "x-priority",
4832 "user-agent",
4833 "list-help", /* rfc 2369, section 3 */
4834 "list-unsubscribe",
4835 "list-subscribe",
4836 "list-post",
4837 "list-owner",
4838 "list-archive",
4839 NULL
4842 for(p = forbidden_headers; *p; p++)
4843 if(!strucmp(name, *p))
4844 break;
4846 return((*p) ? 1 : 0);
4851 * hdr_is_in_list - is there a custom value for this header?
4853 * hdr - the header name to check
4854 * custom - the list to check in
4855 * Returns 1 if there is a custom value, 0 otherwise.
4858 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4860 PINEFIELD *pf;
4862 for(pf = custom; pf && pf->name; pf = pf->next)
4863 if(strucmp(pf->name, hdr) == 0)
4864 return 1;
4866 return 0;
4871 * count_custom_hdrs_pf - returns number of custom headers in arg
4872 * custom -- the list to be counted
4873 * only_nonstandard -- only count headers which aren't standard pine headers
4876 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4878 int ret = 0;
4880 for(; custom && custom->name; custom = custom->next)
4881 if(!only_nonstandard || !custom->standard)
4882 ret++;
4884 return(ret);
4889 * count_custom_hdrs_list - returns number of custom headers in arg
4892 count_custom_hdrs_list(char **list)
4894 char **p;
4895 char *q = NULL;
4896 char *name;
4897 char *t;
4898 char save;
4899 int ret = 0;
4901 if(list){
4902 for(p = list; (q = *p) != NULL; p++){
4903 if(q[0]){
4904 /* remove leading whitespace */
4905 name = skip_white_space(q);
4907 /* look for colon or space or end */
4908 for(t = name;
4909 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4910 ;/* do nothing */
4912 save = *t;
4913 *t = '\0';
4914 if(!pine_header_forbidden(name))
4915 ret++;
4917 *t = save;
4922 return(ret);
4927 * set_default_hdrval - put the user's default value for this header
4928 * into pf->textbuf.
4929 * setthis - the pinefield to be set
4930 * custom - where to look for the default
4932 CustomType
4933 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4935 PINEFIELD *pf;
4936 CustomType ret = NoMatch;
4938 if(!setthis || !setthis->name){
4939 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4940 return(ret);
4943 setthis->textbuf = NULL;
4945 for(pf = custom; pf && pf->name; pf = pf->next){
4946 if(strucmp(pf->name, setthis->name) != 0)
4947 continue;
4949 ret = pf->cstmtype;
4951 /* turn on editing */
4952 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4953 setthis->canedit = 1;
4955 if(pf->val)
4956 setthis->textbuf = cpystr(pf->val);
4959 if(!setthis->textbuf)
4960 setthis->textbuf = cpystr("");
4962 return(ret);
4967 * pine_header_standard - is this name a "standard" header?
4969 * name - the header name to check
4971 FieldType
4972 pine_header_standard(char *name)
4974 int i;
4976 /* check to see if this is a standard header */
4977 for(i = 0; pf_template[i].name; i++)
4978 if(!strucmp(name, pf_template[i].name))
4979 return(pf_template[i].type);
4981 return(TypeUnknown);
4986 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
4987 * Allocates space for each name and addr ptr.
4988 * Allocates space for default in textbuf, even if empty.
4990 * head - the first PINEFIELD to fill in
4991 * list - the list to parse
4993 void
4994 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
4996 char **p, *q, *t, *name, *value, save;
4997 PINEFIELD *pf;
4999 pf = head;
5001 if(list){
5002 for(p = list; (q = *p) != NULL; p++){
5004 if(q[0]){
5006 /* anything after leading whitespace? */
5007 if(!*(name = skip_white_space(q)))
5008 continue;
5010 /* look for colon or space or end */
5011 for(t = name;
5012 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5013 ;/* do nothing */
5015 /* if there is a space in the field-name, skip it */
5016 if(isspace((unsigned char)*t)){
5017 q_status_message1(SM_ORDER, 3, 3,
5018 _("Space not allowed in header name (%s)"),
5019 name);
5020 continue;
5023 save = *t;
5024 *t = '\0';
5026 /* Don't allow any of the forbidden headers. */
5027 if(pine_header_forbidden(name)){
5028 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5029 _("Not allowed to change header \"%s\""),
5030 name);
5032 *t = save;
5033 continue;
5036 if(pf){
5037 if(pine_header_standard(name) != TypeUnknown)
5038 pf->standard = 1;
5040 pf->name = cpystr(name);
5041 pf->type = FreeText;
5042 pf->cstmtype = cstmtype;
5043 pf->next = pf+1;
5045 #ifdef OLDWAY
5047 * Some mailers apparently break if we change
5048 * user@domain into Fred <user@domain> for
5049 * return-receipt-to,
5050 * so we'll just call this a FreeText field, too.
5053 * For now, all custom headers are FreeText except for
5054 * this one that we happen to know about. We might
5055 * have to add some syntax to the config option so that
5056 * people can tell us their custom header takes addresses.
5058 if(!strucmp(pf->name, "Return-Receipt-to")){
5059 pf->type = Address;
5060 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5061 *pf->addr = (ADDRESS *)NULL;
5063 #endif /* OLDWAY */
5065 *t = save;
5067 /* remove space between name and colon */
5068 value = skip_white_space(t);
5070 /* give them an alloc'd default, even if empty */
5071 pf->textbuf = cpystr((*value == ':')
5072 ? skip_white_space(++value) : "");
5073 if(pf->textbuf && pf->textbuf[0])
5074 pf->val = cpystr(pf->textbuf);
5076 pf++;
5078 else
5079 *t = save;
5084 /* fix last next pointer */
5085 if(head && pf != head)
5086 (pf-1)->next = NULL;
5091 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5092 * setup except that it doesn't have default values.
5093 * Add those defaults if they exist in "list".
5095 * head - the first PINEFIELD to add a default to
5096 * list - the list to get the defaults from
5098 void
5099 add_defaults_from_list(PINEFIELD *head, char **list)
5101 char **p, *q, *t, *name, *value, save;
5102 PINEFIELD *pf;
5104 for(pf = head; pf && list; pf = pf->next){
5105 if(!pf->name)
5106 continue;
5108 for(p = list; (q = *p) != NULL; p++){
5110 if(q[0]){
5112 /* anything after leading whitespace? */
5113 if(!*(name = skip_white_space(q)))
5114 continue;
5116 /* look for colon or space or end */
5117 for(t = name;
5118 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5119 ;/* do nothing */
5121 /* if there is a space in the field-name, skip it */
5122 if(isspace((unsigned char)*t))
5123 continue;
5125 save = *t;
5126 *t = '\0';
5128 if(strucmp(name, pf->name) != 0){
5129 *t = save;
5130 continue;
5133 *t = save;
5136 * Found the right header. See if it has a default
5137 * value set.
5140 /* remove space between name and colon */
5141 value = skip_white_space(t);
5143 if(*value == ':'){
5144 char *defval;
5146 defval = skip_white_space(++value);
5147 if(defval && *defval){
5148 if(pf->val)
5149 fs_give((void **)&pf->val);
5151 pf->val = cpystr(defval);
5155 break; /* on to next pf */
5163 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5164 * fill in the defaults
5165 * Args - list -- The list to parse.
5166 * cstmtype -- Fill the in cstmtype field with this value
5168 PINEFIELD *
5169 parse_custom_hdrs(char **list, CustomType cstmtype)
5171 PINEFIELD *pfields;
5172 int i;
5175 * add one for possible use by fcc
5176 * What is this "possible use"? I don't see it. I don't
5177 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5179 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5180 pfields = (PINEFIELD *)fs_get((size_t) i);
5181 memset(pfields, 0, (size_t) i);
5183 /* set up the custom header pfields */
5184 customized_hdr_setup(pfields, list, cstmtype);
5186 return(pfields);
5191 * Combine the two lists of headers into one list which is allocated here
5192 * and freed by the caller. Eliminate duplicates with values from the role
5193 * taking precedence over values from the default.
5195 PINEFIELD *
5196 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5198 PINEFIELD *pfields, *pf, *pf2;
5199 int max_hdrs, i;
5201 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5202 count_custom_hdrs_pf(dflthdrs,0);
5204 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5205 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5207 pf = pfields;
5208 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5209 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5210 pf->type = pf2->type;
5211 pf->cstmtype = pf2->cstmtype;
5212 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5213 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5214 pf->standard = pf2->standard;
5215 pf->next = pf+1;
5216 pf++;
5219 /* if these aren't already there, add them */
5220 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5221 /* check for already there */
5222 for(i = 0;
5223 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5224 i++)
5227 if(!pfields[i].name){ /* this is a new one */
5228 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5229 pf->type = pf2->type;
5230 pf->cstmtype = pf2->cstmtype;
5231 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5232 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5233 pf->standard = pf2->standard;
5234 pf->next = pf+1;
5235 pf++;
5239 /* fix last next pointer */
5240 if(pf != pfields)
5241 (pf-1)->next = NULL;
5243 return(pfields);
5248 * free_customs - free misc. resources associated with custom header fields
5250 * pf - pointer to first custom field
5252 void
5253 free_customs(PINEFIELD *head)
5255 PINEFIELD *pf;
5257 for(pf = head; pf && pf->name; pf = pf->next){
5259 fs_give((void **)&pf->name);
5261 if(pf->val)
5262 fs_give((void **)&pf->val);
5264 /* only true for FreeText */
5265 if(pf->textbuf)
5266 fs_give((void **)&pf->textbuf);
5268 /* only true for Address */
5269 if(pf->addr && *pf->addr)
5270 mail_free_address(pf->addr);
5273 fs_give((void **)&head);
5278 * encode_whole_header
5280 * Returns 1 if whole value should be encoded
5281 * 0 to encode only within comments
5284 encode_whole_header(char *field, METAENV *header)
5286 int retval = 0;
5287 PINEFIELD *pf;
5289 if(field && (!strucmp(field, "Subject") ||
5290 !strucmp(field, "Comment") ||
5291 !struncmp(field, "X-", 2)))
5292 retval++;
5293 else if(field && *field && header && header->custom){
5294 for(pf = header->custom; pf && pf->name; pf = pf->next){
5296 if(!pf->standard && !strucmp(pf->name, field)){
5297 retval++;
5298 break;
5303 return(retval);
5307 /*----------------------------------------------------------------------
5308 Post news via NNTP or inews
5310 Args: env -- envelope of message to post
5311 body -- body of message to post
5313 Returns: -1 if failed or cancelled, 1 if succeeded
5315 WARNING: This call function has the side effect of writing the message
5316 to the lmc.so object.
5317 ----*/
5319 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5320 void (*pipecb_f)(PIPE_S *, int, void *))
5322 char *error_mess, error_buf[200], **news_servers;
5323 char **servers_to_use;
5324 int we_cancel = 0, server_no = 0, done_posting = 0;
5325 void *orig_822_output;
5326 BODY *bp = NULL;
5328 error_buf[0] = '\0';
5329 we_cancel = busy_cue("Posting news", NULL, 0);
5331 dprint((4, "Posting: [%s]\n",
5332 (header && header->env && header->env->newsgroups)
5333 ? header->env->newsgroups : "?"));
5335 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5336 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5337 && ps_global->VAR_NNTP_SERVER[0][0])){
5338 /*---------- NNTP server defined ----------*/
5339 error_mess = NULL;
5340 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5341 && alt_nntp_servers[0][0])
5342 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5345 * Install our rfc822 output routine
5347 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5348 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5349 (void *)post_rfc822_output);
5351 server_no = 0;
5352 news_servers = (char **)fs_get(2 * sizeof(char *));
5353 news_servers[1] = NULL;
5354 while(!done_posting && servers_to_use[server_no] &&
5355 servers_to_use[server_no][0]){
5356 news_servers[0] = servers_to_use[server_no];
5357 ps_global->noshow_error = 1;
5358 #ifdef DEBUG
5359 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5360 #else
5361 sending_stream = nntp_open(news_servers, 0L);
5362 #endif
5363 ps_global->noshow_error = 0;
5365 if(sending_stream != NULL) {
5366 unsigned short save_encoding, added_encoding;
5369 * Fake that we've got clearance from the transport agent
5370 * for 8bit transport for the benefit of our output routines...
5372 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5373 && (bp = first_text_8bit(body))){
5374 int i;
5376 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5379 if(i > ENCMAX){ /* no empty encoding slots! */
5380 bp = NULL;
5382 else {
5383 added_encoding = i;
5384 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5385 save_encoding = bp->encoding;
5386 bp->encoding = added_encoding;
5391 * Set global header pointer so we can get at it later...
5393 send_header = header;
5394 ps_global->noshow_error = 1;
5395 if(nntp_mail(sending_stream, header->env, body) == 0)
5396 snprintf(error_mess = error_buf, sizeof(error_buf),
5397 _("Error posting message: %s"),
5398 sending_stream->reply);
5399 else{
5400 done_posting = 1;
5401 error_buf[0] = '\0';
5402 error_mess = NULL;
5405 error_buf[sizeof(error_buf)-1] = '\0';
5406 smtp_close(sending_stream);
5407 ps_global->noshow_error = 0;
5408 sending_stream = NULL;
5409 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5410 body_encodings[added_encoding] = NULL;
5411 bp->encoding = save_encoding;
5414 } else {
5415 /*---- Open of NNTP connection failed ------ */
5416 snprintf(error_mess = error_buf, sizeof(error_buf),
5417 _("Error connecting to news server: %s"),
5418 ps_global->c_client_error);
5419 error_buf[sizeof(error_buf)-1] = '\0';
5420 dprint((1, error_buf));
5422 server_no++;
5424 fs_give((void **)&news_servers);
5425 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5426 } else {
5427 /*----- Post via local mechanism -------*/
5428 #ifdef _WINDOWS
5429 snprintf(error_mess = error_buf, sizeof(error_buf),
5430 _("Can't post, NNTP-server must be defined!"));
5431 #else /* UNIX */
5432 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5433 pipecb_f);
5434 #endif
5437 if(we_cancel)
5438 cancel_busy_cue(0);
5440 if(error_mess){
5441 if(lmc.so && !lmc.all_written)
5442 so_give(&lmc.so); /* clean up any fcc data */
5444 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5445 return(-1);
5448 lmc.all_written = 1;
5449 return(1);
5453 /* ----------------------------------------------------------------------
5454 Figure out command to start local SMTP agent
5456 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5458 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5460 ----*/
5461 char *
5462 smtp_command(char *errbuf, size_t errbuflen)
5464 #ifdef _WINDOWS
5465 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5466 && ps_global->VAR_SMTP_SERVER[0][0]))
5467 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5469 errbuf[errbuflen-1] = '\0';
5470 #else /* UNIX */
5471 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5472 #define SENDTMPLEN 256
5473 char tmp[SENDTMPLEN];
5474 /* SENDTMPLEN == sizeof(tmp) */
5475 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
5476 (SENDTMPLEN-3)/2, SENDMAILFLAGS);
5477 return(cpystr(tmp));
5478 # else
5479 strncpy(errbuf, _("No default posting command."), errbuflen);
5480 errbuf[errbuflen-1] = '\0';
5481 # endif
5482 #endif
5484 return(NULL);
5489 #ifndef _WINDOWS
5490 /*----------------------------------------------------------------------
5491 Hand off given message to local posting agent
5493 Args: envelope -- The envelope for the BCC and debugging
5494 header -- The text of the message header
5495 errbuf -- buffer for reporting errors (assumed non-NULL)
5496 len -- Length of errbuf
5498 ----*/
5500 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5501 char *errbuf, size_t len,
5502 void (*bigresult_f) (char *, int),
5503 void (*pipecb_f)(PIPE_S *, int, void *))
5505 #ifdef DF_SENDMAIL_PATH
5506 char cmd_buf[256];
5507 #endif
5508 char *cmd = NULL;
5511 * A bit of complicated policy implemented here.
5512 * There are two posting variables sendmail-path and smtp-server.
5513 * Precedence is in that order.
5514 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5515 * Precedence is in that order.
5516 * Said differently, the order goes something like what's below.
5518 * NOTE: the fixed/command-line/user precedence handling is also
5519 * indicated by what's pointed to by ps_global->VAR_*, but since
5520 * that also includes the global defaults, it's not sufficient.
5523 if(ps_global->FIX_SENDMAIL_PATH
5524 && ps_global->FIX_SENDMAIL_PATH[0]){
5525 cmd = ps_global->FIX_SENDMAIL_PATH;
5527 else if(!(ps_global->FIX_SMTP_SERVER
5528 && ps_global->FIX_SMTP_SERVER[0])){
5529 if(ps_global->COM_SENDMAIL_PATH
5530 && ps_global->COM_SENDMAIL_PATH[0]){
5531 cmd = ps_global->COM_SENDMAIL_PATH;
5533 else if(!(ps_global->COM_SMTP_SERVER
5534 && ps_global->COM_SMTP_SERVER[0])){
5535 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5536 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5537 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5538 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5539 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5540 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5541 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5542 else
5543 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5545 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5546 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5547 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5548 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5549 if(ps_global->GLO_SENDMAIL_PATH
5550 && ps_global->GLO_SENDMAIL_PATH[0]){
5551 cmd = ps_global->GLO_SENDMAIL_PATH;
5553 #ifdef DF_SENDMAIL_PATH
5555 * This defines the default method of posting. So,
5556 * unless we're told otherwise use it...
5558 else if(!(ps_global->GLO_SMTP_SERVER
5559 && ps_global->GLO_SMTP_SERVER[0])){
5560 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5561 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5563 #endif
5568 *errbuf = '\0';
5569 if(cmd){
5570 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5572 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5573 return(1);
5575 else
5576 return(0);
5581 /*----------------------------------------------------------------------
5582 Hand off given message to local posting agent
5584 Args: envelope -- The envelope for the BCC and debugging
5585 header -- The text of the message header
5586 errbuf -- buffer for reporting errors (assumed non-NULL)
5587 errbuflen -- Length of errbuf
5589 Fork off mailer process and pipe the message into it
5590 Called to post news via Inews when NNTP is unavailable
5592 ----*/
5593 char *
5594 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5595 size_t errbuflen,
5596 void (*bigresult_f) (char *, int),
5597 void (*pipecb_f)(PIPE_S *, int, void *))
5599 char *err = NULL;
5600 #ifdef SENDNEWS
5601 char *s;
5602 char tmp[200];
5604 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5605 *s = '\0';
5607 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5608 strncpy(tmp, err, sizeof(tmp)-1);
5609 tmp[sizeof(tmp)-1] = '\0';
5610 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5611 SENDNEWS, tmp);
5614 if(s)
5615 *s = ' '; /* restore the date */
5617 #else /* !SENDNEWS */ /* this is the default case */
5618 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5619 err[errbuflen-1] = '\0';
5620 #endif /* !SENDNEWS */
5621 return(err);
5626 /*----------------------------------------------------------------------
5627 Hand off message to local MTA; it parses recipients from 822 header
5629 Args: header -- struct containing header data
5630 body -- struct containing message body data
5631 cmd -- command to use for handoff (%s says where file should go)
5632 errs -- pointer to buf to hold errors
5634 ----*/
5635 char *
5636 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5637 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5638 void (*pipecb_f)(PIPE_S *, int, void *))
5640 char *result = NULL;
5641 PIPE_S *pipe;
5643 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5645 if((pipe = open_system_pipe(cmd, &result, NULL,
5646 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5647 0, pipecb_f, pipe_report_error)) != NULL){
5648 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5649 (TCPSTREAM *) pipe)){
5650 strncpy(errs, _("Error posting."), errslen-1);
5651 errs[errslen-1] = '\0';
5654 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5655 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5656 if(result && bigresult_f)
5657 (*bigresult_f)(result, CM_BR_ERROR);
5660 else
5661 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5663 if(result){
5664 our_unlink(result);
5665 fs_give((void **)&result);
5668 return(*errs ? errs : NULL);
5673 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5674 * pipes rather than a tcp stream
5676 long
5677 pine_pipe_soutr_nl (void *stream, char *s)
5679 long rv = T;
5680 char *p;
5681 size_t n;
5683 while(*s && rv){
5684 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0){
5685 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5686 if(rv < 0){
5687 if(errno != EINTR){
5688 rv = 0;
5689 break;
5692 else{
5693 s += rv;
5694 n -= rv;
5698 if(p && rv){
5699 s = p + 2; /* write UNIX EOL */
5700 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5701 if(rv < 0 && errno != EINTR){
5702 rv = 0;
5703 break;
5706 else
5707 break;
5710 return(rv);
5712 #endif
5715 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5719 * helpful def's
5721 #define S(X) ((PIPE_S *)(X))
5722 #define GETBUFLEN (4 * MAILTMPLEN)
5725 void *
5726 piped_smtp_open (char *host, char *service, long unsigned int port)
5728 char *postcmd;
5729 void *rv = NULL;
5731 if(strucmp(host, "localhost")){
5732 char tmp[MAILTMPLEN];
5733 /* MAILTMPLEN = sizeof(tmp) */
5734 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5735 MAILTMPLEN-50, host);
5736 tmp[sizeof(tmp)-1] = '\0';
5737 mm_log(tmp, ERROR);
5739 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5740 rv = open_system_pipe(postcmd, NULL, NULL,
5741 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5742 0, NULL, pipe_report_error);
5743 fs_give((void **) &postcmd);
5745 else
5746 mm_log(ps_global->c_client_error, ERROR);
5748 return(rv);
5752 void *
5753 piped_aopen (NETMBX *mb, char *service, char *user)
5755 return(NULL);
5760 * piped_soutr - Replacement for tcp_soutr that writes one of our
5761 * pipes rather than a tcp stream
5763 long
5764 piped_soutr (void *stream, char *s)
5766 return(piped_sout(stream, s, strlen(s)));
5771 * piped_sout - Replacement for tcp_soutr that writes one of our
5772 * pipes rather than a tcp stream
5774 long
5775 piped_sout (void *stream, char *s, long unsigned int size)
5777 int i, o;
5779 if(S(stream)->out.d < 0)
5780 return(0L);
5782 if((i = (int) size) != 0){
5783 while((o = write(S(stream)->out.d, s, i)) != i)
5784 if(o < 0){
5785 if(errno != EINTR){
5786 piped_abort(stream);
5787 return(0L);
5790 else{
5791 s += o; /* try again, fix up counts */
5792 i -= o;
5796 return(1L);
5801 * piped_getline - Replacement for tcp_getline that reads one
5802 * of our pipes rather than a tcp pipe
5804 * C-client expects that the \r\n will be stripped off.
5806 char *
5807 piped_getline (void *stream)
5809 static int cnt;
5810 static char *ptr;
5811 int n, m;
5812 char *ret, *s, *sp, c = '\0', d;
5814 if(S(stream)->in.d < 0)
5815 return(NULL);
5817 if(!S(stream)->tmp){ /* initialize! */
5818 /* alloc space to collect input (freed in close_system_pipe) */
5819 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5820 memset(S(stream)->tmp, 0, GETBUFLEN);
5821 cnt = -1;
5824 while(cnt < 0){
5825 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5826 if(errno != EINTR){
5827 piped_abort(stream);
5828 return(NULL);
5831 if(cnt == 0){
5832 piped_abort(stream);
5833 return(NULL);
5836 ptr = S(stream)->tmp;
5839 s = ptr;
5840 n = 0;
5841 while(cnt--){
5842 d = *ptr++;
5843 if((c == '\015') && (d == '\012')){
5844 ret = (char *)fs_get (n--);
5845 memcpy(ret, s, n);
5846 ret[n] = '\0';
5847 return(ret);
5850 n++;
5851 c = d;
5853 /* copy partial string from buffer */
5854 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5855 /* get more data */
5856 while(cnt < 0){
5857 memset(S(stream)->tmp, 0, GETBUFLEN);
5858 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5859 if(errno != EINTR){
5860 fs_give((void **) &ret);
5861 piped_abort(stream);
5862 return(NULL);
5865 if(cnt == 0){
5866 if(n > 0)
5867 ret[n-1] = '\0'; /* to try to get error message logged */
5868 else{
5869 piped_abort(stream);
5870 return(NULL);
5874 ptr = S(stream)->tmp;
5877 if(c == '\015' && *ptr == '\012'){
5878 ptr++;
5879 cnt--;
5880 ret[n - 1] = '\0'; /* tie off string with null */
5882 else if ((s = piped_getline(stream)) != NULL) {
5883 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5884 memcpy(ret, sp, n); /* copy first part */
5885 memcpy(ret + n, s, m); /* and second part */
5886 fs_give((void **) &sp); /* flush first part */
5887 fs_give((void **) &s); /* flush second part */
5888 ret[n + m] = '\0'; /* tie off string with null */
5891 return(ret);
5896 * piped_close - Replacement for tcp_close that closes pipes to our
5897 * child rather than a tcp connection
5899 void
5900 piped_close(void *stream)
5903 * Uninstall our hooks into smtp_send since it's being used by
5904 * the nntp driver as well...
5906 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5911 * piped_abort - close down the pipe we were using to post
5913 void
5914 piped_abort(void *stream)
5916 if(S(stream)->in.d >= 0){
5917 close(S(stream)->in.d);
5918 S(stream)->in.d = -1;
5921 if(S(stream)->out.d){
5922 close(S(stream)->out.d);
5923 S(stream)->out.d = -1;
5928 char *
5929 piped_host(void *stream)
5931 return(ps_global->hostname ? ps_global->hostname : "localhost");
5935 unsigned long
5936 piped_port(void *stream)
5938 return(0L);