* new version 2.19.9991
[alpine.git] / pith / send.c
blobb01ff7e53616a85111f87ab450db1f164dab4781
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-2014 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 * Phone home hash controls
215 #define PH_HASHBITS 24
216 #define PH_MAXHASH (1<<(PH_HASHBITS))
220 * postponed_stream - return stream associated with postponed messages
221 * in argument.
224 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
226 MAILSTREAM *stream = NULL;
227 CONTEXT_S *p_cntxt = NULL;
228 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
229 int exists;
231 if(!(streamp && mbox))
232 return(0);
234 *streamp = NULL;
237 * find default context to look for folder...
239 * The "mbox" is assumed to be local if we're given what looks
240 * like an absolute path. This is different from Goto/Save
241 * where we do alot of work to interpret paths relative to the
242 * server. This reason is to support all the pre-4.00 pinerc'
243 * that specified a path and because there's yet to be a way
244 * in c-client to specify otherwise in the face of a remote
245 * context.
247 if(!is_absolute_path(mbox)
248 && !(p_cntxt = default_save_context(ps_global->context_list)))
249 p_cntxt = ps_global->context_list;
251 /* check to see if the folder exists, the user wants to continue
252 * and that we can actually read something in...
254 exists = folder_name_exists(p_cntxt, mbox, &fullname);
255 if(fullname)
256 mbox = fullname;
258 if(exists & FEX_ISFILE){
259 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
260 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
262 * The mbox is relative to the home directory.
263 * Make it absolute so we can compare it to
264 * stream->mailbox.
266 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
267 SIZEOF_20KBUF);
268 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
269 tmp[sizeof(tmp)-1] = '\0';
272 if((stream = ps_global->mail_stream)
273 && !(stream->mailbox
274 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
275 || (*tmp == '{'
276 && same_stream(tmp, stream)
277 && (p = strchr(tmp, '}'))
278 && (q = strchr(stream->mailbox,'}'))
279 && !strcmp(p + 1, q + 1)))))
280 stream = NULL;
282 if(!stream){
283 stream = context_open(p_cntxt, NULL, mbox,
284 SP_USEPOOL|SP_TEMPUSE, NULL);
285 if(stream && !stream->halfopen){
286 if(stream->nmsgs > 0)
287 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
289 if(checknmsgs && stream->nmsgs < 1){
290 pine_mail_close(stream);
291 exists = 0;
292 stream = NULL;
295 else{
296 q_status_message2(SM_ORDER | SM_DING, 3, 3,
297 _("Can't open %s mailbox: %s"), type, mbox);
298 if(stream)
299 pine_mail_close(stream);
301 exists = 0;
302 stream = NULL;
306 else{
307 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
308 q_status_message1(SM_ORDER | SM_DING, 3, 3,
309 _("%s message folder doesn't exist!"), type);
313 if(fullname)
314 fs_give((void **) &fullname);
316 *streamp = stream;
318 return(exists);
323 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
324 struct mail_bodystruct **body, char **fcc, char **lcc,
325 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
326 ACTION_S **role, int flags, STORE_S *so)
328 MAILSTREAM *stream;
329 ENVELOPE *e = NULL;
330 BODY *b;
331 PART *part;
332 PINEFIELD *pf;
333 gf_io_t pc;
334 char *extras, **fields, **values, *p;
335 char *hdrs[2], *h, *charset;
336 char **smtp_servers = NULL, **nntp_servers = NULL;
337 int i, pine_generated = 0, our_replyto = 0;
338 int added_to_role = 0;
339 unsigned gbpt_flags = GBPT_NONE;
340 MESSAGECACHE *mc;
342 if(!(streamp && *streamp))
343 return(redraft_cleanup(streamp, TRUE, flags));
345 stream = *streamp;
347 if(flags & REDRAFT_HTML)
348 gbpt_flags |= GBPT_HTML_OK;
350 /* grok any user-defined or non-c-client headers */
351 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
354 * The custom headers to look for in the suspended message should
355 * have been stored in the X-Our-Headers header. So first we get
356 * that list. If we can't find it (version that stored the
357 * message < 4.30) then we use the global custom list.
359 hdrs[0] = OUR_HDRS_LIST;
360 hdrs[1] = NULL;
361 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
362 int commas = 0;
363 char **list;
364 char *hdrval = NULL;
366 if((hdrval = strindex(h, ':')) != NULL){
367 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
368 hdrval++)
372 /* count elements in list */
373 for(p = hdrval; p && *p; p++)
374 if(*p == ',')
375 commas++;
377 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
379 *custom = parse_custom_hdrs(list, Replace);
380 add_defaults_from_list(*custom,
381 ps_global->VAR_CUSTOM_HDRS);
382 free_list_array(&list);
385 if(*custom && !(*custom)->name){
386 free_customs(*custom);
387 *custom = NULL;
390 fs_give((void **)&h);
393 if(!*custom)
394 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
396 #define INDEX_FCC 0
397 #define INDEX_POSTERR 1
398 #define INDEX_REPLYUID 2
399 #define INDEX_REPLYMBOX 3
400 #define INDEX_SMTP 4
401 #define INDEX_NNTP 5
402 #define INDEX_CURSORPOS 6
403 #define INDEX_OUR_REPLYTO 7
404 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
405 #define FIELD_COUNT 9
407 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
410 * Having these two fields separated isn't the slickest, but
411 * converting the pointer array for fetchheader_lines() to
412 * a list of structures or some such for simple_header_parse()
413 * is too goonie. We could do something like re-use c-client's
414 * PARAMETER struct which is a simple char * pairing, but that
415 * doesn't make sense to pass to fetchheader_lines()...
417 fields = (char **) fs_get((size_t) i * sizeof(char *));
418 values = (char **) fs_get((size_t) i * sizeof(char *));
419 memset(fields, 0, (size_t) i * sizeof(char *));
420 memset(values, 0, (size_t) i * sizeof(char *));
422 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
423 fields[++i] = "X-Post-Error"; /* posting errors too */
424 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
425 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
426 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
427 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
428 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
429 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
430 fields[++i] = "Lcc"; /* Lcc: too... */
431 if(++i != FIELD_COUNT)
432 panic("Fix FIELD_COUNT");
434 for(pf = *custom; pf && pf->name; pf = pf->next)
435 if(!pf->standard)
436 fields[i++] = pf->name; /* assign custom fields */
438 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
439 simple_header_parse(extras, fields, values);
440 fs_give((void **) &extras);
443 * translate RFC 1522 strings,
444 * starting with "Lcc" field
446 for(i = INDEX_LCC; fields[i]; i++)
447 if(values[i]){
448 size_t len;
449 char *bufp, *biggerbuf = NULL;
451 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
452 len++;
453 biggerbuf = (char *)fs_get(len * sizeof(char));
454 bufp = biggerbuf;
456 else{
457 bufp = tmp_20k_buf;
458 len = SIZEOF_20KBUF;
461 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
463 if(p == tmp_20k_buf){
464 fs_give((void **)&values[i]);
465 values[i] = cpystr(p);
468 if(biggerbuf)
469 fs_give((void **)&biggerbuf);
472 for(pf = *custom, i = FIELD_COUNT;
473 pf && pf->name;
474 pf = pf->next){
475 if(pf->standard){
477 * Because the value is already in the envelope.
479 pf->cstmtype = NoMatch;
480 continue;
483 if(values[i]){ /* use this instead of default */
484 if(pf->textbuf)
485 fs_give((void **)&pf->textbuf);
487 pf->textbuf = values[i]; /* freed in pine_send! */
489 else if(pf->textbuf) /* was erased before postpone */
490 fs_give((void **)&pf->textbuf);
492 i++;
495 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
496 pine_generated = 1; /* we put it there? */
499 * Since c-client fills in the reply_to field in the envelope
500 * even if there isn't a Reply-To header in the message we
501 * have to work around that. When we postpone we add
502 * a second header that has value "Empty" if there really
503 * was a Reply-To and it was empty. It has the
504 * value "Full" if we put the Reply-To contents there
505 * intentionally (and it isn't empty).
507 if(values[INDEX_OUR_REPLYTO]){
508 if(values[INDEX_OUR_REPLYTO][0] == 'E')
509 our_replyto = 'E'; /* we put an empty one there */
510 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
511 our_replyto = 'F'; /* we put it there */
513 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
516 if(fcc) /* fcc: special case... */
517 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
518 else if(values[INDEX_FCC])
519 fs_give((void **) &values[INDEX_FCC]);
521 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
522 q_status_message(SM_ORDER|SM_DING, 4, 4,
523 values[INDEX_POSTERR]);
524 fs_give((void **) &values[INDEX_POSTERR]);
527 if(values[INDEX_REPLYUID]){
528 if(reply)
529 *reply = build_reply_uid(values[INDEX_REPLYUID]);
531 fs_give((void **) &values[INDEX_REPLYUID]);
533 if(values[INDEX_REPLYMBOX] && reply && *reply)
534 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
536 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
537 (*reply)->origmbox = cpystr((*reply)->mailbox);
540 if(values[INDEX_REPLYMBOX])
541 fs_give((void **) &values[INDEX_REPLYMBOX]);
543 if(values[INDEX_SMTP]){
544 char *q;
545 size_t cnt = 0;
548 * Turn the space delimited list of smtp servers into
549 * a char ** list.
551 p = values[INDEX_SMTP];
553 if(!*p || isspace((unsigned char) *p))
554 cnt++;
555 } while(*p++);
557 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
558 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
560 cnt = 0;
561 q = p = values[INDEX_SMTP];
563 if(!*p || isspace((unsigned char) *p)){
564 if(*p){
565 *p = '\0';
566 smtp_servers[cnt++] = cpystr(q);
567 *p = ' ';
568 q = p+1;
570 else
571 smtp_servers[cnt++] = cpystr(q);
573 } while(*p++);
575 fs_give((void **) &values[INDEX_SMTP]);
578 if(values[INDEX_NNTP]){
579 char *q;
580 size_t cnt = 0;
583 * Turn the space delimited list of smtp nntp into
584 * a char ** list.
586 p = values[INDEX_NNTP];
588 if(!*p || isspace((unsigned char) *p))
589 cnt++;
590 } while(*p++);
592 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
593 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
595 cnt = 0;
596 q = p = values[INDEX_NNTP];
598 if(!*p || isspace((unsigned char) *p)){
599 if(*p){
600 *p = '\0';
601 nntp_servers[cnt++] = cpystr(q);
602 *p = ' ';
603 q = p+1;
605 else
606 nntp_servers[cnt++] = cpystr(q);
608 } while(*p++);
610 fs_give((void **) &values[INDEX_NNTP]);
613 if(values[INDEX_CURSORPOS]){
615 * The redraft cursor position is written as two fields
616 * separated by a space. First comes the name of the
617 * header field we're in, or just a ":" if we're in the
618 * body. Then comes the offset into that header or into
619 * the body.
621 if(redraft_pos){
622 char *q1, *q2;
624 *redraft_pos
625 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
626 (*redraft_pos)->offset = 0L;
628 q1 = skip_white_space(values[INDEX_CURSORPOS]);
629 if(*q1 && (q2 = strindex(q1, SPACE))){
630 *q2 = '\0';
631 (*redraft_pos)->hdrname = cpystr(q1);
632 q1 = skip_white_space(q2+1);
633 if(*q1)
634 (*redraft_pos)->offset = atol(q1);
636 else
637 (*redraft_pos)->hdrname = cpystr(":");
640 fs_give((void **) &values[INDEX_CURSORPOS]);
643 if(lcc)
644 *lcc = values[INDEX_LCC];
645 else
646 fs_give((void **) &values[INDEX_LCC]);
649 fs_give((void **)&fields);
650 fs_give((void **)&values);
652 *outgoing = copy_envelope(e);
655 * If the postponed message has a From which is different from
656 * the default, it is either because allow-changing-from is on
657 * or because there was a role with a from that allowed it to happen.
658 * If allow-changing-from is not on, put this back in a role
659 * so that it will be allowed again in pine_send.
661 if(role && *role == NULL &&
662 !ps_global->never_allow_changing_from &&
663 *outgoing){
665 * Now check to see if the from is different from default from.
667 ADDRESS *deffrom;
669 deffrom = generate_from();
670 if(!((*outgoing)->from &&
671 address_is_same(deffrom, (*outgoing)->from) &&
672 ((!(deffrom->personal && deffrom->personal[0]) &&
673 !((*outgoing)->from->personal &&
674 (*outgoing)->from->personal[0])) ||
675 (deffrom->personal && (*outgoing)->from->personal &&
676 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
678 *role = (ACTION_S *)fs_get(sizeof(**role));
679 memset((void *)*role, 0, sizeof(**role));
680 if(!(*outgoing)->from)
681 (*outgoing)->from = mail_newaddr();
683 (*role)->from = (*outgoing)->from;
684 (*outgoing)->from = NULL;
685 added_to_role++;
688 mail_free_address(&deffrom);
692 * Look at each empty address and see if the user has specified
693 * a default for that field or not. If they have, that means
694 * they have erased it before postponing, so they won't want
695 * the default to come back. If they haven't specified a default,
696 * then the default should be generated in pine_send. We prevent
697 * the default from being assigned by assigning an empty address
698 * to the variable here.
700 * BUG: We should do this for custom Address headers, too, but
701 * there isn't such a thing yet.
703 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
704 (*outgoing)->to = mail_newaddr();
705 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
706 (*outgoing)->cc = mail_newaddr();
707 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
708 (*outgoing)->bcc = mail_newaddr();
710 if(our_replyto == 'E'){
711 /* user erased reply-to before postponing */
712 if((*outgoing)->reply_to)
713 mail_free_address(&(*outgoing)->reply_to);
716 * If empty is not the normal default, make the outgoing
717 * reply_to be an emtpy address. If it is default, leave it
718 * as NULL and the default will be used.
720 if(hdr_is_in_list("reply-to", *custom)){
721 PINEFIELD pf;
723 pf.name = "reply-to";
724 set_default_hdrval(&pf, *custom);
725 if(pf.textbuf){
726 if(pf.textbuf[0]) /* empty is not default */
727 (*outgoing)->reply_to = mail_newaddr();
729 fs_give((void **)&pf.textbuf);
733 else if(our_replyto == 'F'){
734 int add_to_role = 0;
737 * The reply-to is real. If it is different from the default
738 * reply-to, put it in the role so that it will show up when
739 * the user edits.
741 if(hdr_is_in_list("reply-to", *custom)){
742 PINEFIELD pf;
743 char *str;
745 pf.name = "reply-to";
746 set_default_hdrval(&pf, *custom);
747 if(pf.textbuf && pf.textbuf[0]){
748 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
749 if(!strcmp(str, pf.textbuf)){
750 /* standard value, leave it alone */
753 else /* not standard, put in role */
754 add_to_role++;
756 fs_give((void **)&str);
759 else /* not standard, put in role */
760 add_to_role++;
762 if(pf.textbuf)
763 fs_give((void **)&pf.textbuf);
765 else /* not standard, put in role */
766 add_to_role++;
768 if(add_to_role && role && (*role == NULL || added_to_role)){
769 if(*role == NULL){
770 added_to_role++;
771 *role = (ACTION_S *)fs_get(sizeof(**role));
772 memset((void *)*role, 0, sizeof(**role));
775 (*role)->replyto = (*outgoing)->reply_to;
776 (*outgoing)->reply_to = NULL;
779 else{
780 /* this is a bogus c-client generated replyto */
781 if((*outgoing)->reply_to)
782 mail_free_address(&(*outgoing)->reply_to);
785 if((smtp_servers || nntp_servers)
786 && role && (*role == NULL || added_to_role)){
787 if(*role == NULL){
788 *role = (ACTION_S *)fs_get(sizeof(**role));
789 memset((void *)*role, 0, sizeof(**role));
792 if(smtp_servers)
793 (*role)->smtp = smtp_servers;
794 if(nntp_servers)
795 (*role)->nntp = nntp_servers;
798 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
799 (*outgoing)->subject = cpystr("");
801 if(!pine_generated){
803 * Now, this is interesting. We should have found
804 * the "fcc:" field if pine wrote the message being
805 * redrafted. Hence, we probably can't trust the
806 * "originator" type fields, so we'll blast them and let
807 * them get set later in pine_send. This should allow
808 * folks with custom or edited From's and such to still
809 * use redraft reasonably, without inadvertently sending
810 * messages that appear to be "From" others...
812 if((*outgoing)->from)
813 mail_free_address(&(*outgoing)->from);
816 * Ditto for Reply-To and Sender...
818 if((*outgoing)->reply_to)
819 mail_free_address(&(*outgoing)->reply_to);
821 if((*outgoing)->sender)
822 mail_free_address(&(*outgoing)->sender);
825 if(!pine_generated || !(flags & REDRAFT_DEL)){
828 * Generate a fresh message id for pretty much the same
829 * reason From and such got wacked...
830 * Also, if we're coming from a form letter, we need to
831 * generate a different id each time.
833 if((*outgoing)->message_id)
834 fs_give((void **)&(*outgoing)->message_id);
836 (*outgoing)->message_id = generate_message_id();
839 if(b && b->type != TYPETEXT){
840 if(b->type == TYPEMULTIPART){
841 if(strucmp(b->subtype, "mixed")){
842 q_status_message1(SM_INFO, 3, 4,
843 "Converting Multipart/%s to Multipart/Mixed",
844 b->subtype);
845 fs_give((void **)&b->subtype);
846 b->subtype = cpystr("mixed");
849 else{
850 q_status_message2(SM_ORDER | SM_DING, 3, 4,
851 "Unable to resume type %s/%s message",
852 body_types[b->type], b->subtype);
853 return(redraft_cleanup(streamp, TRUE, flags));
857 gf_set_so_writec(&pc, so);
859 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
860 *body = copy_body(NULL, b);
861 part = (*body)->nested.part;
862 part->body.contents.text.data = (void *)so;
863 set_mime_type_by_grope(&part->body);
864 if(part->body.type != TYPETEXT){
865 q_status_message2(SM_ORDER | SM_DING, 3, 4,
866 "Unable to resume; first part is non-text: %s/%s",
867 body_types[part->body.type],
868 part->body.subtype);
869 return(redraft_cleanup(streamp, TRUE, flags));
872 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
873 /* let outgoing routines decide on charset */
874 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
875 set_parameter(&part->body.parameter, "charset", NULL);
877 fs_give((void **) &charset);
880 ps_global->postpone_no_flow = 1;
882 get_body_part_text(stream, &b->nested.part->body,
883 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
884 ps_global->postpone_no_flow = 0;
886 if(!fetch_contents(stream, cont_msg, NULL, *body))
887 q_status_message(SM_ORDER | SM_DING, 3, 4,
888 _("Error including all message parts"));
890 else{
891 *body = mail_newbody();
892 (*body)->type = TYPETEXT;
893 if(b->subtype)
894 (*body)->subtype = cpystr(b->subtype);
896 if((charset = parameter_val(b->parameter,"charset")) != NULL){
897 /* let outgoing routines decide on charset */
898 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
899 fs_give((void **) &charset);
900 else{
901 (*body)->parameter = mail_newbody_parameter();
902 (*body)->parameter->attribute = cpystr("charset");
903 if(utf8_charset(charset)){
904 fs_give((void **) &charset);
905 (*body)->parameter->value = cpystr("UTF-8");
907 else
908 (*body)->parameter->value = charset;
912 (*body)->contents.text.data = (void *)so;
913 ps_global->postpone_no_flow = 1;
914 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
915 NULL, NULL, gbpt_flags);
916 ps_global->postpone_no_flow = 0;
919 gf_clear_so_writec(so);
921 /* We have what we want, blast this message... */
922 if((flags & REDRAFT_DEL)
923 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
924 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
925 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
927 else
928 return(redraft_cleanup(streamp, TRUE, flags));
930 return(redraft_cleanup(streamp, FALSE, flags));
934 /*----------------------------------------------------------------------
935 Clear deleted messages from given stream and expunge if necessary
937 Args: stream --
938 problem --
940 ----*/
942 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
944 MAILSTREAM *stream;
946 if(!(streamp && *streamp))
947 return(0);
949 if(!problem && streamp && (stream = *streamp)){
950 if(stream->nmsgs){
951 ps_global->expunge_in_progress = 1;
952 mail_expunge(stream); /* clean out deleted */
953 ps_global->expunge_in_progress = 0;
956 if(!stream->nmsgs){ /* close and delete folder */
957 int do_the_broach = 0;
958 char *mbox = NULL;
960 if(stream){
961 if(stream->original_mailbox && stream->original_mailbox[0])
962 mbox = cpystr(stream->original_mailbox);
963 else if(stream->mailbox && stream->mailbox[0])
964 mbox = cpystr(stream->mailbox);
967 /* if it is current, we have to change folders */
968 if(stream == ps_global->mail_stream)
969 do_the_broach++;
972 * This is the stream to the empty postponed-msgs folder.
973 * We are going to delete the folder in a second. It is
974 * probably preferable to unselect the mailbox and leave
975 * this stream open for re-use instead of actually closing it,
976 * so we do that if possible.
978 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
980 * This does the UNSELECT on the stream. A NULL
981 * return should mean that something went wrong and
982 * a mail_close already happened, so that should have
983 * cleaned things up in the callback.
985 if((stream=mail_open(stream, stream->mailbox,
986 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
987 /* now close it so it is put into the stream cache */
988 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
989 pine_mail_close(stream);
992 else
993 pine_mail_actually_close(stream);
995 *streamp = NULL;
997 if(do_the_broach){
998 ps_global->mail_stream = NULL; /* already closed above */
1001 if(mbox && !pine_mail_delete(NULL, mbox))
1002 q_status_message1(SM_ORDER|SM_DING, 3, 3,
1003 /* TRANSLATORS: Arg is a mailbox name */
1004 _("Can't delete %s"), mbox);
1006 if(mbox)
1007 fs_give((void **) &mbox);
1011 return(!problem);
1015 /*----------------------------------------------------------------------
1016 Parse the given header text for any given fields
1018 Args: text -- Text to parse for fcc and attachments refs
1019 fields -- array of field names to look for
1020 values -- array of pointer to save values to, returned NULL if
1021 fields isn't in text.
1023 This function simply looks for the given fields in the given header
1024 text string.
1025 NOTE: newlines are expected CRLF, and we'll ignore continuations
1026 ----*/
1027 void
1028 simple_header_parse(char *text, char **fields, char **values)
1030 int i, n;
1031 char *p, *t;
1033 for(i = 0; fields[i]; i++)
1034 values[i] = NULL; /* clear values array */
1036 /*---- Loop until the end of the header ----*/
1037 for(p = text; *p; ){
1038 for(i = 0; fields[i]; i++) /* find matching field? */
1039 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1040 for(p += n + 1; *p; p++){ /* find start of value */
1041 if(*p == '\015' && *(p+1) == '\012'
1042 && !isspace((unsigned char) *(p+2)))
1043 break;
1045 if(!isspace((unsigned char) *p))
1046 break; /* order here is key... */
1049 if(!values[i]){ /* if we haven't already */
1050 values[i] = fs_get(strlen(text) + 1);
1051 values[i][0] = '\0'; /* alloc space for it */
1054 if(*p && *p != '\015'){ /* non-blank value. */
1055 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1056 while(*p){ /* check for cont'n lines */
1057 if(*p == '\015' && *(p+1) == '\012'){
1058 if(isspace((unsigned char) *(p+2))){
1059 p += 2;
1060 continue;
1062 else
1063 break;
1066 *t++ = *p++;
1069 *t = '\0';
1072 break;
1075 /* Skip to end of line, what ever it was */
1076 for(; *p ; p++)
1077 if(*p == '\015' && *(p+1) == '\012'){
1078 p += 2;
1079 break;
1085 /*----------------------------------------------------------------------
1086 build a fresh REPLY_S from the given string (see pine_send for format)
1088 Args: s -- "X-Reply-UID" header value
1090 Returns: filled in REPLY_S or NULL on parse error
1091 ----*/
1092 REPLY_S *
1093 build_reply_uid(char *s)
1095 char *p, *prefix = NULL, *val, *seq, *mbox;
1096 int i, nseq, forwarded = 0;
1097 REPLY_S *reply = NULL;
1099 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1100 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1101 if(*s == '('){
1102 if(*(p = s + 1) == ')'){
1103 forwarded = 1;
1105 else{
1106 for(; isdigit(*p); p++)
1109 if(*p == ' '){
1110 *p++ = '\0';
1112 if((i = atoi(s+1)) && i < strlen(p)){
1113 prefix = p;
1114 *(p += i) = '\0';
1117 else
1118 return(NULL);
1121 if(*++p == '(' && *++p){
1122 for(seq = p; isdigit(*p); p++)
1125 if(*p == ' '){
1126 *p++ = '\0';
1127 for(val = p; isdigit(*p); p++)
1130 if(*p == ' '){
1131 *p++ = '\0';
1133 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1134 && (p = strchr(p, ')')) && *(mbox = ++p)){
1135 imapuid_t *uidl;
1137 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1138 for(i = 0; i < nseq; i++)
1139 if((p = strchr(seq,',')) != NULL){
1140 *p = '\0';
1141 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1142 seq = ++p;
1143 else
1144 break;
1146 else if((p = strchr(seq, ')')) != NULL){
1147 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1148 i++;
1150 break;
1153 if(i == nseq){
1154 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1155 memset(reply, 0, sizeof(REPLY_S));
1156 reply->uid = 1;
1157 reply->data.uid.validity = strtoul(val, NULL, 10);
1158 if(forwarded)
1159 reply->forwarded = 1;
1160 else
1161 reply->prefix = cpystr(prefix);
1163 reply->mailbox = cpystr(mbox);
1164 uidl[nseq] = 0;
1165 reply->data.uid.msgs = uidl;
1167 else
1168 fs_give((void **) &uidl);
1175 return(reply);
1180 * pine_new_env - allocate a new METAENV and fill it in.
1182 METAENV *
1183 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1185 int cnt, i, stdcnt;
1186 char *p;
1187 PINEFIELD *pfields, *pf, **sending_order;
1188 METAENV *header;
1190 header = (METAENV *) fs_get(sizeof(METAENV));
1192 /* how many fields are there? */
1193 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1196 stdcnt = cnt;
1198 for(pf = custom; pf; pf = pf->next)
1199 cnt++;
1201 /* temporary PINEFIELD array */
1202 i = (cnt + 1) * sizeof(PINEFIELD);
1203 pfields = (PINEFIELD *)fs_get((size_t) i);
1204 memset(pfields, 0, (size_t) i);
1206 i = (cnt + 1) * sizeof(PINEFIELD *);
1207 sending_order = (PINEFIELD **)fs_get((size_t) i);
1208 memset(sending_order, 0, (size_t) i);
1210 header->env = outgoing;
1211 header->local = pfields;
1212 header->custom = custom;
1213 header->sending_order = sending_order;
1215 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1216 # define NN 4
1217 #else
1218 # define NN 3
1219 #endif
1221 /* initialize pfield */
1222 pf = pfields;
1223 for(i=0; i < stdcnt; i++, pf++){
1225 pf->name = cpystr(pf_template[i].name);
1226 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
1227 /* slide string over so it is Sender instead of X-X-Sender */
1228 for(p=pf->name; *(p+1); p++)
1229 *p = *(p+4);
1231 pf->type = pf_template[i].type;
1232 pf->canedit = pf_template[i].canedit;
1233 pf->rcptto = pf_template[i].rcptto;
1234 pf->writehdr = pf_template[i].writehdr;
1235 pf->localcopy = pf_template[i].localcopy;
1236 pf->extdata = NULL; /* unused */
1237 pf->next = pf + 1;
1239 switch(pf->type){
1240 case FreeText:
1241 switch(i){
1242 case N_AUTHRCVD:
1243 sending_order[0] = pf;
1244 break;
1246 case N_NEWS:
1247 pf->text = &outgoing->newsgroups;
1248 sending_order[1] = pf;
1249 break;
1251 case N_DATE:
1252 pf->text = (char **) &outgoing->date;
1253 sending_order[2] = pf;
1254 break;
1256 case N_INREPLY:
1257 pf->text = &outgoing->in_reply_to;
1258 sending_order[NN+9] = pf;
1259 break;
1261 case N_MSGID:
1262 pf->text = &outgoing->message_id;
1263 sending_order[NN+10] = pf;
1264 break;
1266 case N_REF: /* won't be used here */
1267 sending_order[NN+11] = pf;
1268 break;
1270 case N_PRIORITY:
1271 sending_order[NN+12] = pf;
1272 break;
1274 case N_USERAGENT:
1275 pf->text = &pf->textbuf;
1276 pf->textbuf = generate_user_agent();
1277 sending_order[NN+13] = pf;
1278 break;
1280 case N_POSTERR: /* won't be used here */
1281 sending_order[NN+14] = pf;
1282 break;
1284 case N_RPLUID: /* won't be used here */
1285 sending_order[NN+15] = pf;
1286 break;
1288 case N_RPLMBOX: /* won't be used here */
1289 sending_order[NN+16] = pf;
1290 break;
1292 case N_SMTP: /* won't be used here */
1293 sending_order[NN+17] = pf;
1294 break;
1296 case N_NNTP: /* won't be used here */
1297 sending_order[NN+18] = pf;
1298 break;
1300 case N_CURPOS: /* won't be used here */
1301 sending_order[NN+19] = pf;
1302 break;
1304 case N_OURREPLYTO: /* won't be used here */
1305 sending_order[NN+20] = pf;
1306 break;
1308 case N_OURHDRS: /* won't be used here */
1309 sending_order[NN+21] = pf;
1310 break;
1312 default:
1313 q_status_message1(SM_ORDER,3,3,
1314 "Internal error: 1)FreeText header %s", comatose(i));
1315 break;
1318 break;
1320 case Attachment:
1321 break;
1323 case Address:
1324 switch(i){
1325 case N_FROM:
1326 sending_order[3] = pf;
1327 pf->addr = &outgoing->from;
1328 break;
1330 case N_TO:
1331 sending_order[NN+2] = pf;
1332 pf->addr = &outgoing->to;
1333 if(tobufpp)
1334 (*tobufpp) = &pf->scratch;
1336 break;
1338 case N_CC:
1339 sending_order[NN+3] = pf;
1340 pf->addr = &outgoing->cc;
1341 break;
1343 case N_BCC:
1344 sending_order[NN+4] = pf;
1345 pf->addr = &outgoing->bcc;
1346 break;
1348 case N_REPLYTO:
1349 sending_order[NN+1] = pf;
1350 pf->addr = &outgoing->reply_to;
1351 break;
1353 case N_LCC: /* won't be used here */
1354 sending_order[NN+7] = pf;
1355 break;
1357 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1358 case N_SENDER:
1359 sending_order[4] = pf;
1360 pf->addr = &outgoing->sender;
1361 break;
1362 #endif
1364 case N_NOBODY: /* won't be used here */
1365 sending_order[NN+5] = pf;
1366 break;
1368 default:
1369 q_status_message1(SM_ORDER,3,3,
1370 "Internal error: Address header %s", comatose(i));
1371 break;
1373 break;
1375 case Fcc:
1376 sending_order[NN+8] = pf;
1377 pf->text = fccp;
1378 break;
1380 case Subject:
1381 sending_order[NN+6] = pf;
1382 pf->text = &outgoing->subject;
1383 break;
1385 default:
1386 q_status_message1(SM_ORDER,3,3,
1387 "Unknown header type %d in pine_new_send", (void *)pf->type);
1388 break;
1392 if(((--pf)->next = custom) != NULL){
1393 i--;
1396 * NOTE: "i" is assumed to now index first custom field in sending
1397 * order.
1399 for(pf = pf->next; pf && pf->name; pf = pf->next){
1400 if(pf->standard)
1401 continue;
1403 pf->canedit = 1;
1404 pf->rcptto = 0;
1405 pf->writehdr = 1;
1406 pf->localcopy = 1;
1408 switch(pf->type){
1409 case Address:
1410 if(pf->addr){ /* better be set */
1411 char *addr = NULL;
1412 BuildTo bldto;
1414 bldto.type = Str;
1415 bldto.arg.str = pf->textbuf;
1417 sending_order[i++] = pf;
1418 /* change default text into an ADDRESS */
1419 /* strip quotes around whole default */
1420 removing_trailing_white_space(pf->textbuf);
1421 (void)removing_double_quotes(pf->textbuf);
1422 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1423 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1424 fs_give((void **)&addr);
1425 if(pf->textbuf)
1426 fs_give((void **)&pf->textbuf);
1429 break;
1431 case FreeText:
1432 sending_order[i++] = pf;
1433 pf->text = &pf->textbuf;
1434 break;
1436 default:
1437 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1438 (void *)pf->type);
1439 break;
1445 return(header);
1449 void
1450 pine_free_env(METAENV **menv)
1452 int cnt;
1455 if((*menv)->local){
1456 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1459 for(; cnt >= 0; cnt--){
1460 if((*menv)->local[cnt].textbuf)
1461 fs_give((void **) &(*menv)->local[cnt].textbuf);
1463 fs_give((void **) &(*menv)->local[cnt].name);
1466 fs_give((void **) &(*menv)->local);
1469 if((*menv)->sending_order)
1470 fs_give((void **) &(*menv)->sending_order);
1472 fs_give((void **) menv);
1476 /*----------------------------------------------------------------------
1477 Check for addresses the user is not permitted to send to, or probably
1478 doesn't want to send to
1480 Returns: 0 if OK
1481 1 if there are only empty groups
1482 -1 if the message shouldn't be sent
1484 Queues a message indicating what happened
1485 ---*/
1487 check_addresses(METAENV *header)
1489 PINEFIELD *pf;
1490 ADDRESS *a;
1491 int send_daemon = 0, rv = CA_EMPTY;
1493 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1494 for(pf = header->local; pf && pf->name; pf = pf->next)
1495 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1496 for(a = *pf->addr; a != NULL; a = a->next){
1497 if(a->host && (a->host[0] == '.'
1498 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1499 && a->host[0] == '@'))){
1500 q_status_message2(SM_ORDER, 4, 7,
1501 /* TRANSLATORS: First arg is the address we can't
1502 send to, second arg is "not in addressbook". */
1503 _("Can't send to address %s: %s"),
1504 a->mailbox,
1505 (a->host[0] == '.')
1506 ? a->host
1507 : _("not in addressbook"));
1508 return(CA_BAD);
1510 else if(ps_global->restricted
1511 && !address_is_us(*pf->addr, ps_global)){
1512 q_status_message(SM_ORDER, 3, 3,
1513 "Restricted demo version of Alpine. You may only send mail to yourself");
1514 return(CA_BAD);
1516 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1517 send_daemon = 1;
1518 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1520 else if(a->mailbox && a->host){
1521 rv = CA_OK;
1525 return(rv);
1530 * If this isn't general enough we can modify it. The value passed in
1531 * is expected to be one of the desc settings from the priorities array,
1532 * like "High". The header value is X-Priority: 2 (High)
1533 * or something similar. If value doesn't match any of the values then
1534 * the actual value is used instead.
1536 PINEFIELD *
1537 set_priority_header(METAENV *header, char *value)
1539 PINEFIELD *pf;
1541 for(pf = header->local; pf && pf->name; pf = pf->next)
1542 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1543 break;
1545 if(pf){
1546 if(pf->textbuf)
1547 fs_give((void **) &pf->textbuf);
1549 if(value){
1550 PRIORITY_S *p;
1552 for(p = priorities; p && p->desc; p++)
1553 if(!strcmp(p->desc, value))
1554 break;
1556 if(p && p->desc){
1557 char buf[100];
1559 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1560 pf->textbuf = cpystr(buf);
1562 else
1563 pf->textbuf = cpystr(value);
1566 return pf;
1570 /*----------------------------------------------------------------------
1571 Set answered flags for messages specified by reply structure
1573 Args: reply --
1575 Returns: with appropriate flags set and index cache entries suitably tweeked
1576 ----*/
1577 void
1578 update_answered_flags(REPLY_S *reply)
1580 char *seq = NULL, *p;
1581 long i, ourstream = 0, we_cancel = 0;
1582 MAILSTREAM *stream = NULL;
1584 /* nothing to flip in a pseudo reply */
1585 if(reply && (reply->msgno || reply->uid)){
1586 int j;
1587 MAILSTREAM *m;
1590 * If an established stream will do, use it, else
1591 * build one unless we have an array of msgno's...
1593 * I was just mimicking what was already here. I don't really
1594 * understand why we use strcmp instead of same_stream_and_mailbox().
1595 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1596 * Hubert 2003-07-09
1598 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1599 m = ps_global->s_pool.streams[j];
1600 if(m && reply->mailbox && m->mailbox
1601 && !strcmp(reply->mailbox, m->mailbox))
1602 stream = m;
1605 if(!stream && reply->msgno)
1606 return;
1609 * This is here only for people who ran pine4.42 and are
1610 * processing postponed mail from 4.42 now. Pine4.42 saved the
1611 * original mailbox name in the canonical name's position in
1612 * the postponed-msgs folder so it won't match the canonical
1613 * name from the stream.
1615 if(!stream && (!reply->origmbox ||
1616 (reply->mailbox &&
1617 !strcmp(reply->origmbox, reply->mailbox))))
1618 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1620 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1621 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1622 if(!stream){
1623 if((stream = pine_mail_open(NULL,
1624 reply->origmbox ? reply->origmbox
1625 : reply->mailbox,
1626 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1627 NULL)) != NULL){
1628 ourstream++;
1630 else{
1631 if(we_cancel)
1632 cancel_busy_cue(0);
1634 return;
1638 if(stream->uid_validity == reply->data.uid.validity){
1639 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1640 if(i){
1641 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1642 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1645 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1646 SIZEOF_20KBUF-(p-tmp_20k_buf));
1647 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1650 if(reply->forwarded){
1652 * $Forwarded is a regular keyword so we only try to
1653 * set it if the stream allows keywords.
1654 * We could mess up if the stream has keywords but just
1655 * isn't allowing anymore and $Forwarded already exists,
1656 * but what are the odds?
1658 if(stream && stream->kwd_create)
1659 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1660 FORWARDED_FLAG,
1661 ST_SET | ((reply->uid) ? ST_UID : 0L));
1663 else
1664 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1665 "\\ANSWERED",
1666 ST_SET | ((reply->uid) ? ST_UID : 0L));
1668 if(seq)
1669 fs_give((void **)&seq);
1672 if(ourstream)
1673 pine_mail_close(stream); /* clean up dangling stream */
1675 if(we_cancel)
1676 cancel_busy_cue(0);
1682 * phone_home_from - make phone home request's from address IMpersonal.
1683 * Doesn't include user's personal name.
1685 ADDRESS *
1686 phone_home_from(void)
1688 ADDRESS *addr = mail_newaddr();
1689 char tmp[32];
1691 /* garble up mailbox name */
1692 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1693 tmp[sizeof(tmp)-1] = '\0';
1694 addr->mailbox = cpystr(tmp);
1695 addr->host = cpystr(ps_global->maildomain);
1696 return(addr);
1701 * one-way-hash a username into an 8-digit decimal number
1703 * Corey Satten, corey@cac.washington.edu, 7/15/98
1705 unsigned int
1706 phone_home_hash(char *s)
1708 unsigned int h;
1710 for (h=0; *s; ++s) {
1711 if (h & 1)
1712 h = (h>>1) | (PH_MAXHASH/2);
1713 else
1714 h = (h>>1);
1716 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1719 return (h);
1723 /*----------------------------------------------------------------------
1724 Call the mailer, SMTP, sendmail or whatever
1726 Args: header -- full header (envelope and local parts) of message to send
1727 body -- The full body of the message including text
1728 alt_smtp_servers --
1729 verbosefile -- non-null means caller wants verbose interaction and the resulting
1730 output file name to be returned
1732 Returns: -1 if failed, 1 if succeeded
1733 ----*/
1735 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1736 int flags, void (*bigresult_f)(char *, int),
1737 void (*pipecb_f)(PIPE_S *, int, void *))
1739 char error_buf[200], *error_mess = NULL, *postcmd;
1740 ADDRESS *a;
1741 ENVELOPE *fake_env = NULL;
1742 int addr_error_count, we_cancel = 0;
1743 long smtp_opts = 0L;
1744 char *verbose_file = NULL;
1745 BODY *bp = NULL;
1746 PINEFIELD *pf;
1747 BODY *origBody = body;
1749 dprint((4, "Sending mail...\n"));
1751 /* Check for any recipients */
1752 for(pf = header->local; pf && pf->name; pf = pf->next)
1753 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1754 break;
1756 if(!pf){
1757 q_status_message(SM_ORDER,3,3,
1758 _("Can't send message. No recipients specified!"));
1759 return(0);
1762 #ifdef SMIME
1763 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1764 int result;
1766 STORE_S *so = lmc.so;
1767 lmc.so = NULL;
1769 result = 1;
1771 if(ps_global->smime->do_sign){
1772 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1773 result = sign_outgoing_message(header, &body, 0, &bp);
1776 /* need to free new body from encrypt if sign fails? */
1777 if(result && ps_global->smime->do_encrypt)
1778 result = encrypt_outgoing_message(header, &body);
1780 lmc.so = so;
1782 if(!result)
1783 return 0;
1785 #endif
1787 /* set up counts and such to keep track sent percentage */
1788 send_bytes_sent = 0;
1789 gf_filter_init(); /* zero piped byte count, 'n */
1790 send_bytes_to_send = send_body_size(body); /* count body bytes */
1791 ps_global->c_client_error[0] = error_buf[0] = '\0';
1792 we_cancel = busy_cue(_("Sending mail"),
1793 send_bytes_to_send ? sent_percent : NULL, 0);
1795 #ifndef _WINDOWS
1797 /* try posting via local "<mta> <-t>" if specified */
1798 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1799 if(error_buf[0])
1800 error_mess = error_buf;
1802 goto done;
1805 #endif
1808 * If the user's asked for it, and we find that the first text
1809 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1811 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1812 smtp_opts |= SOP_8BITMIME;
1814 #ifdef DEBUG
1815 #ifndef DEBUGJOURNAL
1816 if(debug > 5 || (flags & CM_VERBOSE))
1817 #endif
1818 smtp_opts |= SOP_DEBUG;
1819 #endif
1821 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1822 smtp_opts |= SOP_DSN;
1823 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1824 if(flags & CM_DSN_DELAY)
1825 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1826 if(flags & CM_DSN_SUCCESS)
1827 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1830 * If it isn't Never, then we're always going to let them
1831 * know about failures. This means we don't allow for the
1832 * possibility of setting delay or success without failure.
1834 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1836 if(flags & CM_DSN_FULL)
1837 smtp_opts |= SOP_DSN_RETURN_FULL;
1843 * Set global header pointer so post_rfc822_output can get at it when
1844 * it's called back from c-client's sending routine...
1846 send_header = header;
1849 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1850 * The purpose is to give smtp_mail the list for SMTP RCPT when
1851 * there are recipients in pine's METAENV that are outside c-client's
1852 * envelope.
1854 * NOTE: If there aren't any, don't bother. Dealt with it below.
1856 for(pf = header->local; pf && pf->name; pf = pf->next)
1857 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1858 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1859 || *pf->addr == header->env->bcc))
1860 break;
1862 if(pf && pf->name){
1863 ADDRESS **tail;
1865 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1866 memset(fake_env, 0, sizeof(ENVELOPE));
1867 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1868 tail = &(fake_env->to);
1869 for(pf = header->local; pf && pf->name; pf = pf->next)
1870 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1871 *tail = rfc822_cpy_adr(*pf->addr);
1872 while(*tail)
1873 tail = &((*tail)->next);
1878 * Install our rfc822 output routine
1880 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1881 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1884 * Allow for verbose posting
1886 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1887 (void *) pine_smtp_verbose_out);
1890 * We do this because we want mm_log to put the error message into
1891 * c_client_error instead of showing it itself.
1893 ps_global->noshow_error = 1;
1896 * OK, who posts what? We tried an mta_handoff above, but there
1897 * was either none specified or we decided not to use it. So,
1898 * if there's an smtp-server defined anywhere,
1900 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1901 /*---------- SMTP ----------*/
1902 dprint((4, "call_mailer: via TCP (%s)\n",
1903 alt_smtp_servers[0]));
1904 TIME_STAMP("smtp-open start (tcp)", 1);
1905 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1907 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1908 && ps_global->VAR_SMTP_SERVER[0][0]){
1909 /*---------- SMTP ----------*/
1910 dprint((4, "call_mailer: via TCP\n"));
1911 TIME_STAMP("smtp-open start (tcp)", 1);
1912 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1914 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1915 char *cmdlist[2];
1917 /*----- Send via LOCAL SMTP agent ------*/
1918 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1920 TIME_STAMP("smtp-open start (pipe)", 1);
1921 fs_give((void **) &postcmd);
1922 cmdlist[0] = "localhost";
1923 cmdlist[1] = NULL;
1924 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1925 SMTPTCPPORT, smtp_opts);
1926 /* BUG: should provide separate stderr output! */
1929 ps_global->noshow_error = 0;
1931 TIME_STAMP("smtp open", 1);
1932 if(sending_stream){
1933 unsigned short save_encoding, added_encoding;
1935 dprint((1, "Opened SMTP server \"%s\"\n",
1936 net_host(sending_stream->netstream)
1937 ? net_host(sending_stream->netstream) : "?"));
1939 if(flags & CM_VERBOSE){
1940 TIME_STAMP("verbose start", 1);
1941 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1942 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1943 if(!smtp_verbose(sending_stream)){
1944 snprintf(error_mess = error_buf, sizeof(error_buf),
1945 "Mail not sent. VERBOSE mode error%s%.50s.",
1946 (sending_stream && sending_stream->reply)
1947 ? ": ": "",
1948 (sending_stream && sending_stream->reply)
1949 ? sending_stream->reply : "");
1950 error_buf[sizeof(error_buf)-1] = '\0';
1953 else{
1954 our_unlink(verbose_file);
1955 strncpy(error_mess = error_buf,
1956 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1957 error_buf[sizeof(error_buf)-1] = '\0';
1960 else{
1961 strncpy(error_mess = error_buf,
1962 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1963 error_buf[sizeof(error_buf)-1] = '\0';
1966 TIME_STAMP("verbose end", 1);
1970 * Before we actually send data, see if we have to protect
1971 * the first text body part from getting encoded. We protect
1972 * it from getting encoded in "pine_rfc822_output_body" by
1973 * temporarily inventing a synonym for ENC8BIT...
1974 * This works like so:
1975 * Suppose bp->encoding is set to ENC8BIT.
1976 * We change that here to some unused value (added_encoding) and
1977 * set body_encodings[added_encoding] to "8BIT".
1978 * Then post_rfc822_output is called which calls
1979 * pine_rfc822_output_body. Inside that routine
1980 * pine_write_body_header writes out the encoding for the
1981 * part. Normally it would see encoding == ENC8BIT and it would
1982 * change that to QUOTED-PRINTABLE, but since encoding has been
1983 * set to added_encoding it uses body_encodings[added_encoding]
1984 * which is "8BIT" instead. Then the actual body is written by
1985 * pine_write_body_header which does not do the gf_8bit_qp
1986 * filtering because encoding != ENC8BIT (instead it's equal
1987 * to added_encoding).
1989 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1990 && sending_stream->protocol.esmtp.eightbit.want){
1991 int i;
1993 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1996 if(i > ENCMAX){ /* no empty encoding slots! */
1997 bp = NULL;
1999 else {
2000 added_encoding = i;
2001 body_encodings[added_encoding] = body_encodings[ENC8BIT];
2002 save_encoding = bp->encoding;
2003 bp->encoding = added_encoding;
2004 #ifdef SMIME
2005 if(ps_global->smime && ps_global->smime->do_sign
2006 && body->nested.part->next
2007 && body->nested.part->next->body.contents.text.data
2008 && body->nested.part->next->body.mime.text.data){
2009 STORE_S *so;
2011 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
2012 so_give(&so);
2013 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
2014 body->nested.part->next->body.mime.text.data = NULL;
2016 #endif /* SMIME */
2020 if(sending_stream->protocol.esmtp.ok
2021 && sending_stream->protocol.esmtp.dsn.want
2022 && !sending_stream->protocol.esmtp.dsn.ok)
2023 q_status_message(SM_ORDER,3,3,
2024 _("Delivery Status Notification not available from this server."));
2026 TIME_STAMP("smtp start", 1);
2027 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2028 fake_env ? fake_env : header->env, body)){
2030 snprintf(error_buf, sizeof(error_buf),
2031 _("Mail not sent. Sending error%s%s"),
2032 (sending_stream && sending_stream->reply) ? ": ": ".",
2033 (sending_stream && sending_stream->reply)
2034 ? sending_stream->reply : "");
2035 error_buf[sizeof(error_buf)-1] = '\0';
2036 dprint((1, error_buf));
2037 addr_error_count = 0;
2038 if(fake_env){
2039 for(a = fake_env->to; a != NULL; a = a->next)
2040 if(a->error != NULL){
2041 if(addr_error_count++ < MAX_ADDR_ERROR){
2044 * Too complicated to figure out which header line
2045 * has the error in the fake_env case, so just
2046 * leave cursor at default.
2050 if(error_mess) /* previous error? */
2051 q_status_message(SM_ORDER, 4, 7, error_mess);
2053 error_mess = tidy_smtp_mess(a->error,
2054 _("Mail not sent: %.80s"),
2055 error_buf, sizeof(error_buf));
2058 dprint((1, "Send Error: \"%s\"\n",
2059 a->error));
2062 else{
2063 for(pf = header->local; pf && pf->name; pf = pf->next)
2064 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2065 for(a = *pf->addr; a != NULL; a = a->next)
2066 if(a->error != NULL){
2067 if(addr_error_count++ < MAX_ADDR_ERROR){
2069 if(error_mess) /* previous error? */
2070 q_status_message(SM_ORDER, 4, 7, error_mess);
2072 error_mess = tidy_smtp_mess(a->error,
2073 _("Mail not sent: %.80s"),
2074 error_buf, sizeof(error_buf));
2077 dprint((1, "Send Error: \"%s\"\n",
2078 a->error));
2082 if(!error_mess)
2083 error_mess = error_buf;
2086 /* repair modified "body_encodings" array? */
2087 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2088 && sending_stream->protocol.esmtp.eightbit.want){
2089 body_encodings[added_encoding] = NULL;
2090 bp->encoding = save_encoding;
2093 TIME_STAMP("smtp closing", 1);
2094 smtp_close(sending_stream);
2095 sending_stream = NULL;
2096 TIME_STAMP("smtp done", 1);
2098 else if(!error_mess){
2099 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2100 ps_global->c_client_error[0] ? ": " : "",
2101 ps_global->c_client_error);
2102 error_buf[sizeof(error_buf)-1] = '\0';
2105 if(verbose_file){
2106 if(verbose_send_output){
2107 TIME_STAMP("verbose start", 1);
2108 fclose(verbose_send_output);
2109 verbose_send_output = NULL;
2110 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2112 if(bigresult_f)
2113 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2115 TIME_STAMP("verbose end", 1);
2118 fs_give((void **)&verbose_file);
2122 * Restore original 822 emitter...
2124 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2126 if(fake_env)
2127 mail_free_envelope(&fake_env);
2129 done:
2131 #ifdef SMIME
2132 /* Free replacement encrypted body */
2133 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2135 if(body->type == TYPEMULTIPART){
2136 /* Just get rid of first part, it's actually origBody */
2137 void *x = body->nested.part;
2139 body->nested.part = body->nested.part->next;
2141 fs_give(&x);
2144 pine_free_body(&body);
2146 #endif
2148 if(we_cancel)
2149 cancel_busy_cue(0);
2151 TIME_STAMP("call_mailer done", 1);
2152 /*-------- Did message make it ? ----------*/
2153 if(error_mess){
2154 /*---- Error sending mail -----*/
2155 if(lmc.so && !lmc.all_written)
2156 so_give(&lmc.so);
2158 if(error_mess){
2159 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2160 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2163 return(-1);
2165 else{
2166 lmc.all_written = 1;
2167 return(1);
2173 * write_postponed - exported method to write the given message
2174 * to the postponed folder
2177 write_postponed(METAENV *header, struct mail_bodystruct *body)
2179 char **pp, *folder;
2180 int rv = 0, sz;
2181 CONTEXT_S *fcc_cntxt = NULL;
2182 PINEFIELD *pf;
2183 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2185 if(!ps_global->VAR_POSTPONED_FOLDER
2186 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2187 q_status_message(SM_ORDER | SM_DING, 3, 3,
2188 _("No postponed file defined"));
2189 return(-1);
2192 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2194 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2196 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2198 if(lmc.so){
2199 /* BUG: writem sufficient ? */
2200 for(pf = header->local; pf && pf->name; pf = pf->next)
2201 for(pp = writem; *pp; pp++)
2202 if(!strucmp(pf->name, *pp)){
2203 pf->localcopy = 1;
2204 pf->writehdr = 1;
2205 break;
2209 * Work around c-client reply-to bug. C-client will
2210 * return a reply_to in an envelope even if there is
2211 * no reply-to header field. We want to note here whether
2212 * the reply-to is real or not.
2214 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2215 for(pf = header->local; pf; pf = pf->next)
2216 if(!strcmp(pf->name, "Reply-To")){
2217 pf->writehdr = 1;
2218 pf->localcopy = 1;
2219 if(header->env->reply_to)
2220 pf->textbuf = cpystr("Full");
2221 else
2222 pf->textbuf = cpystr("Empty");
2226 * Write the list of custom headers to the
2227 * X-Our-Headers header so that we can recover the
2228 * list in redraft.
2230 sz = 0;
2231 for(pf = header->custom; pf && pf->name; pf = pf->next)
2232 sz += strlen(pf->name) + 1;
2234 if(sz){
2235 int i;
2236 char *pstart, *pend;
2238 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2241 pf->writehdr = 1;
2242 pf->localcopy = 1;
2243 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2244 pf->text = &pf->textbuf;
2245 pf->textbuf[sz] = '\0'; /* tie off overflow */
2246 /* note: "pf" overloaded */
2247 for(pf = header->custom; pf && pf->name; pf = pf->next){
2248 int r = sz - (pend - pstart); /* remaining buffer */
2250 if(r > 0 && r != sz){
2251 r--;
2252 *pend++ = ',';
2255 sstrncpy(&pend, pf->name, r);
2259 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2260 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2261 rv = -1;
2263 so_give(&lmc.so);
2265 else {
2266 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2267 "Can't allocate internal storage: %s ",
2268 error_description(errno));
2269 rv = -1;
2272 fs_give((void **) &folder);
2273 return(rv);
2278 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2280 if(fcc && *fcc){
2281 lmc.all_written = lmc.text_written = 0;
2282 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2283 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2285 else
2286 lmc.so = NULL;
2288 return(TRUE);
2293 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2295 int rv = TRUE;
2297 if(lmc.so){
2298 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2299 char label[50];
2301 strncpy(label, "Fcc", sizeof(label));
2302 label[sizeof(label)-1] = '\0';
2303 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2304 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2305 label[sizeof(label)-1] = '\0';
2308 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2310 else{
2311 rv = FALSE;
2314 so_give(&lmc.so);
2317 return(rv);
2321 /*----------------------------------------------------------------------
2322 Checks to make sure the fcc is available and can be opened
2324 Args: fcc -- the name of the fcc to create. It can't be NULL.
2325 fcc_cntxt -- Returns the context the fcc is in.
2326 force -- supress user option prompt
2328 Returns allocated storage object on success, NULL on failure
2329 ----*/
2330 STORE_S *
2331 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2333 int exists, ok = 0;
2335 ps_global->mm_log_error = 0;
2338 * check for fcc's existance...
2340 TIME_STAMP("open_fcc start", 1);
2341 if(!is_absolute_path(fcc) && context_isambig(fcc)
2342 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2343 int flip_dot = 0;
2346 * Don't want to preclude a user from Fcc'ing a .name'd folder
2348 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2349 flip_dot = 1;
2350 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2354 * We only want to set the "context" if fcc is an ambiguous
2355 * name. Otherwise, our "relativeness" rules for contexts
2356 * (implemented in context.c) might cause the name to be
2357 * interpreted in the wrong context...
2359 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2360 *fcc_cntxt = ps_global->context_list;
2362 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2363 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2364 if(force
2365 || (pith_opt_save_create_prompt
2366 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2368 ps_global->noshow_error = 1;
2370 if(context_create(*fcc_cntxt, NULL, fcc))
2371 ok++;
2373 ps_global->noshow_error = 0;
2375 else
2376 ok--; /* declined! */
2378 else
2379 ok++; /* found! */
2381 if(flip_dot)
2382 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2384 free_folder_list(*fcc_cntxt);
2386 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2387 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2388 ok++;
2390 else{
2391 if(force
2392 || (pith_opt_save_create_prompt
2393 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2395 ps_global->mm_log_error = 0;
2396 ps_global->noshow_error = 1;
2398 ok = pine_mail_create(NULL, fcc) != 0L;
2400 ps_global->noshow_error = 0;
2402 else
2403 ok--; /* declined! */
2407 TIME_STAMP("open_fcc done.", 1);
2408 if(ok > 0){
2409 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2411 else{
2412 int l1, l2, l3, wid, w;
2413 char *errstr, tmp[MAILTMPLEN];
2414 char *s1, *s2;
2416 if(ok == 0){
2417 if(ps_global->mm_log_error){
2418 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2419 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2421 l1 = strlen(s1);
2422 l2 = strlen(s2);
2423 l3 = strlen(ps_global->c_client_error);
2424 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2425 ? ps_global->ttyo->screen_cols : 80;
2426 w = wid - l1 - l2 - 5;
2428 snprintf(errstr = tmp, sizeof(tmp),
2429 "%.99s\"%.*s%.99s\".%.99s",
2431 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2432 ps_global->c_client_error,
2433 (l3 > w) ? "..." : "",
2434 s2);
2435 tmp[sizeof(tmp)-1] = '\0';
2438 else
2439 errstr = _("Fcc creation error. Message NOT sent or copied.");
2441 else
2442 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2444 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2447 return(NULL);
2451 /*----------------------------------------------------------------------
2452 mail_append() the fcc accumulated in temp_storage to proper destination
2454 Args: fcc -- name of folder
2455 fcc_cntxt -- context for folder
2456 temp_storage -- String of file where Fcc has been accumulated
2458 This copies the string of file to the actual folder, which might be IMAP
2459 or a disk folder. The temp_storage is freed after it is written.
2460 An error message is produced if this fails.
2461 ----*/
2463 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2464 MAILSTREAM *stream, char *label, char *flags)
2466 STRING msg;
2467 CONTEXT_S *cntxt;
2468 int we_cancel = 0;
2470 if(!tmp_storage)
2471 return(0);
2473 TIME_STAMP("write_fcc start.", 1);
2474 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2475 if(label && *label){
2476 char msg_buf[80];
2478 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2479 msg_buf[sizeof(msg_buf)-1] = '\0';
2480 strncat(msg_buf, label, sizeof(msg_buf)-10);
2481 we_cancel = busy_cue(msg_buf, NULL, 0);
2483 else
2484 we_cancel = busy_cue(NULL, NULL, 1);
2486 so_seek(tmp_storage, 0L, 0);
2489 * Before changing this note that these lines depend on the
2490 * definition of FCC_SOURCE.
2492 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2493 strlen((char *)so_text(tmp_storage)));
2495 cntxt = fcc_cntxt;
2497 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2498 cancel_busy_cue(-1);
2499 we_cancel = 0;
2501 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2502 "Write to \"%s\" FAILED!!!", fcc);
2503 dprint((1, "ERROR appending %s in \"%s\"",
2504 fcc ? fcc : "?",
2505 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2506 return(0);
2509 if(we_cancel)
2510 cancel_busy_cue(label ? 0 : -1);
2512 dprint((4, "done.\n"));
2513 TIME_STAMP("write_fcc done.", 1);
2514 return(1);
2519 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2520 * contained.
2522 BODY *
2523 first_text_8bit(struct mail_bodystruct *body)
2525 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2526 body = &body->nested.part->body;
2528 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2529 ? body : NULL);
2534 * Build and return the "From:" address for outbound messages from
2535 * global data...
2537 ADDRESS *
2538 generate_from(void)
2540 ADDRESS *addr = mail_newaddr();
2541 if(ps_global->VAR_PERSONAL_NAME){
2542 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2543 removing_leading_and_trailing_white_space(addr->personal);
2544 if(addr->personal[0] == '\0')
2545 fs_give((void **)&addr->personal);
2548 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2549 addr->host = cpystr(ps_global->maildomain);
2550 removing_leading_and_trailing_white_space(addr->mailbox);
2551 removing_leading_and_trailing_white_space(addr->host);
2552 return(addr);
2557 * set_mime_type_by_grope - sniff the given storage object to determine its
2558 * type, subtype, encoding, and charset
2560 * "Type" and "encoding" must be set before calling this routine.
2561 * If "type" is set to something other than TYPEOTHER on entry,
2562 * then that is the "type" we wish to use. Same for "encoding"
2563 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2564 * figure them out here. If "type" is already set, we also
2565 * leave subtype alone. If not, we figure out subtype here.
2566 * There is a chance that we will upgrade encoding to a "higher"
2567 * level. For example, if it comes in as 7BIT we may change
2568 * that to 8BIT if we find a From_ we want to escape.
2569 * We may also set the charset attribute if the type is TEXT.
2571 * NOTE: this is rather inefficient if the store object is a CharStar
2572 * but the win is all types are handled the same
2574 void
2575 set_mime_type_by_grope(struct mail_bodystruct *body)
2577 #define RBUFSZ (8193)
2578 unsigned char *buf, *p, *bol;
2579 register size_t n;
2580 long max_line = 0L,
2581 eight_bit_chars = 0L,
2582 line_so_far = 0L,
2583 len = 0L;
2584 STORE_S *so = (STORE_S *)body->contents.text.data;
2585 unsigned short new_encoding = ENCOTHER;
2586 int we_cancel = 0;
2587 #ifdef ENCODE_FROMS
2588 short froms = 0, dots = 0,
2589 bmap = 0x1, dmap = 0x1;
2590 #endif
2592 we_cancel = busy_cue(NULL, NULL, 1);
2594 buf = (unsigned char *)fs_get(RBUFSZ);
2595 so_seek(so, 0L, 0);
2597 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2600 buf[n] = '\0';
2602 if(n){ /* check first few bytes to look for magic numbers */
2603 if(body->type == TYPEOTHER){
2604 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2605 body->type = TYPEIMAGE;
2606 body->subtype = cpystr("GIF");
2608 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2609 && buf[2] == 0xFF && buf[3] == 0xE0
2610 && !strncmp((char *)&buf[6], "JFIF", 4)){
2611 body->type = TYPEIMAGE;
2612 body->subtype = cpystr("JPEG");
2614 else if((buf[0] == 'M' && buf[1] == 'M')
2615 || (buf[0] == 'I' && buf[1] == 'I')){
2616 body->type = TYPEIMAGE;
2617 body->subtype = cpystr("TIFF");
2619 else if((buf[0] == '%' && buf[1] == '!')
2620 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2621 body->type = TYPEAPPLICATION;
2622 body->subtype = cpystr("PostScript");
2624 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2625 body->type = TYPEAPPLICATION;
2626 body->subtype = cpystr("PDF");
2628 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2629 body->type = TYPEAUDIO;
2630 body->subtype = cpystr("Basic");
2632 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2633 && buf[2] == 0x16 && buf[3] == 0x00){
2634 body->type = TYPEAPPLICATION;
2635 body->subtype = cpystr("APPLEFILE");
2637 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2638 && buf[2] == 0x03 && buf[3] == 0x04){
2639 body->type = TYPEAPPLICATION;
2640 body->subtype = cpystr("ZIP");
2644 * if type was set above, but no encoding specified, go
2645 * ahead and make it BASE64...
2647 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2648 body->encoding = ENCBINARY;
2651 else{
2652 /* PROBLEM !!! */
2653 if(body->type == TYPEOTHER){
2654 body->type = TYPEAPPLICATION;
2655 body->subtype = cpystr("octet-stream");
2656 if(body->encoding == ENCOTHER)
2657 body->encoding = ENCBINARY;
2661 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2662 #if defined(DOS) || defined(OS2) /* for binary file detection */
2663 int lastchar = '\0';
2664 #define BREAKOUT 300 /* a value that a character can't be */
2665 #endif
2667 p = bol = buf;
2668 len = n;
2669 while (n--){
2670 /* Some people don't like quoted-printable caused by leading Froms */
2671 #ifdef ENCODE_FROMS
2672 Find_Froms(froms, dots, bmap, dmap, *p);
2673 #endif
2674 if(*p == '\n'){
2675 max_line = MAX(max_line, line_so_far + p - bol);
2676 bol = NULL; /* clear beginning of line */
2677 line_so_far = 0L; /* clear line count */
2678 #if defined(DOS) || defined(OS2)
2679 /* LF with no CR!! */
2680 if(lastchar != '\r') /* must be non-text data! */
2681 lastchar = BREAKOUT;
2682 #endif
2684 else if(*p & 0x80){
2685 eight_bit_chars++;
2687 else if(!*p){
2688 /* NULL found. Unless we're told otherwise, must be binary */
2689 if(body->type == TYPEOTHER){
2690 body->type = TYPEAPPLICATION;
2691 body->subtype = cpystr("octet-stream");
2695 * The "TYPETEXT" here handles the case that the NULL
2696 * comes from imported text generated by some external
2697 * editor that permits or inserts NULLS. Otherwise,
2698 * assume it's a binary segment...
2700 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2703 * Since we've already set encoding, count this as a
2704 * hi bit char and continue. The reason is that if this
2705 * is text, there may be a high percentage of encoded
2706 * characters, so base64 may get set below...
2708 if(body->type == TYPETEXT)
2709 eight_bit_chars++;
2710 else
2711 break;
2714 #if defined(DOS) || defined(OS2) /* for binary file detection */
2715 if(lastchar != BREAKOUT)
2716 lastchar = *p;
2717 #endif
2719 /* read another buffer in */
2720 if(n == 0){
2721 if(bol)
2722 line_so_far += p - bol;
2724 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2727 len += n;
2728 p = buf;
2730 else
2731 p++;
2734 * If there's no beginning-of-line pointer, then we must
2735 * have seen an end-of-line. Set bol to the start of the
2736 * new line...
2738 if(!bol)
2739 bol = p;
2741 #if defined(DOS) || defined(OS2) /* for binary file detection */
2742 /* either a lone \r or lone \n indicate binary file */
2743 if(lastchar == '\r' || lastchar == BREAKOUT){
2744 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2745 if(body->type == TYPEOTHER){
2746 body->type = TYPEAPPLICATION;
2747 body->subtype = cpystr("octet-stream");
2750 new_encoding = ENCBINARY;
2751 break;
2754 #endif
2758 /* stash away for later */
2759 so_attr(so, "maxline", long2string(max_line));
2761 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2763 * Since the type or encoding aren't set yet, fall thru a
2764 * series of tests to make sure an adequate type and
2765 * encoding are set...
2768 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2769 if(body->type == TYPEOTHER){
2771 * Since the types not set, then we didn't find a NULL.
2772 * If there's no NULL, then this is likely text. However,
2773 * since we can't be *completely* sure, we set it to
2774 * the generic type.
2776 body->type = TYPEAPPLICATION;
2777 body->subtype = cpystr("octet-stream");
2780 if(new_encoding != ENCBINARY)
2782 * As with NULL handling, if we're told it's text,
2783 * qp-encode it, else it gets base 64...
2785 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2788 if(eight_bit_chars == 0L){
2789 if(body->type == TYPEOTHER)
2790 body->type = TYPETEXT;
2792 if(new_encoding == ENCOTHER)
2793 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2795 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2797 * The 30% threshold is based on qp encoded readability
2798 * on non-MIME UA's.
2800 if(body->type == TYPEOTHER)
2801 body->type = TYPETEXT;
2803 if(new_encoding != ENCBINARY)
2804 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2806 else{
2807 if(body->type == TYPEOTHER){
2808 body->type = TYPEAPPLICATION;
2809 body->subtype = cpystr("octet-stream");
2813 * Apply maximal encoding regardless of previous
2814 * setting. This segment's either not text, or is
2815 * unlikely to be readable with > 30% of the
2816 * text encoded anyway, so we might as well save space...
2818 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2822 #ifdef ENCODE_FROMS
2823 /* If there were From_'s at the beginning of a line or standalone dots */
2824 if((froms || dots) && new_encoding != ENCBINARY)
2825 new_encoding = ENC8BIT;
2826 #endif
2828 /* Set the subtype */
2829 if(body->subtype == NULL)
2830 body->subtype = cpystr(rfc822_default_subtype(body->type));
2832 if(body->encoding == ENCOTHER)
2833 body->encoding = new_encoding;
2835 fs_give((void **)&buf);
2837 if(we_cancel)
2838 cancel_busy_cue(-1);
2843 * Call this to set the charset of an attachment we have
2844 * created. If the attachment contains any non-ascii characters
2845 * then we'll set the charset to the passed in charset, otherwise
2846 * we'll make it us-ascii.
2848 void
2849 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2851 unsigned char c;
2852 int can_be_ascii = 1;
2853 STORE_S *so = (STORE_S *)body->contents.text.data;
2854 int we_cancel = 0;
2856 if(!body || body->type != TYPETEXT)
2857 return;
2859 we_cancel = busy_cue(NULL, NULL, 1);
2861 so_seek(so, 0L, 0);
2863 while(can_be_ascii && so_readc(&c, so))
2864 if(!c || c & 0x80)
2865 can_be_ascii--;
2867 if(can_be_ascii)
2868 set_parameter(&body->parameter, "charset", "US-ASCII");
2869 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2870 set_parameter(&body->parameter, "charset", charset);
2871 else{
2873 * Else we don't know. There are non ascii characters but we either
2874 * don't have a charset to set it to or that charset is just us_ascii,
2875 * which is impossible. So we label it unknown. An alternative would
2876 * have been to strip the high bits instead and label it ascii.
2878 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2881 if(we_cancel)
2882 cancel_busy_cue(-1);
2887 * since encoding happens on the way out the door, this is basically
2888 * just needed to handle TYPEMULTIPART
2890 void
2891 pine_encode_body (struct mail_bodystruct *body)
2893 PART *part;
2895 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2896 if (body) switch (body->type) {
2897 char *freethis;
2899 case TYPEMULTIPART: /* multi-part */
2900 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2901 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2903 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2904 (long) getpid ());
2905 tmp[sizeof(tmp)-1] = '\0';
2906 set_parameter(&body->parameter, "BOUNDARY", tmp);
2909 if(freethis)
2910 fs_give((void **) &freethis);
2912 part = body->nested.part; /* encode body parts */
2913 do pine_encode_body (&part->body);
2914 while ((part = part->next) != NULL); /* until done */
2915 break;
2917 case TYPETEXT :
2919 * If the part is text we edited, then it is UTF-8.
2920 * The user may be asking us to send it as something else
2921 * or we may want to downconvert to a more-specific characterset.
2922 * Mark it for conversion here so the right MIME header's written.
2923 * Do conversion pine_rfc822_output_body.
2924 * Attachments are left as is.
2926 if(body->contents.text.data
2927 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2928 char *charset, *posting_charset, *lp;
2930 if(!((charset = parameter_val(body->parameter, "charset"))
2931 && !strucmp(charset, UNKNOWN_CHARSET))
2932 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2934 set_parameter(&body->parameter, "charset", posting_charset);
2937 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2938 * and doesn't use anything but ASCII characters.
2939 * Why is it not ENC7BIT already? Because when we set the encoding
2940 * in set_mime_type_by_grope we were groping through UTF-8 text
2941 * not 2022 text. Not only that, but we didn't know at that point
2942 * that it wouldn't stay UTF-8 when we sent it, which would require
2943 * encoding.
2945 if(!strucmp(posting_charset, "iso-2022-jp")
2946 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2947 && strlen(lp) < 4)
2948 body->encoding = ENC7BIT;
2951 if(charset)
2952 fs_give((void **)&charset);
2955 break;
2957 /* case MESSAGE: */ /* here for documentation */
2958 /* Encapsulated messages are always treated as text objects at this point.
2959 This means that you must replace body->contents.msg with
2960 body->contents.text, which probably involves copying
2961 body->contents.msg.text to body->contents.text */
2962 default: /* all else has some encoding */
2964 * but we'll delay encoding it until the message is on the way
2965 * into the mail slot...
2967 break;
2973 * pine_header_line - simple wrapper around c-client call to contain
2974 * repeated code, and to write fcc if required.
2977 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2978 int writehdr, int localcopy)
2980 int ret = 1;
2981 int big = 10000;
2982 char *value, *folded = NULL, *cs;
2983 char *converted;
2985 if(!text)
2986 return 1;
2988 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2990 if(converted){
2991 if(cs && !strucmp(cs, "us-ascii"))
2992 value = converted;
2993 else
2994 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2995 (unsigned char *) converted, cs,
2996 encode_whole_header(field, header));
2998 if(value && value == converted){ /* no encoding was done, have to fold */
2999 int fold_by, len;
3000 char *actual_field;
3002 len = ((header && header->env && header->env->remail)
3003 ? strlen("ReSent-") : 0) +
3004 (field ? strlen(field) : 0) + 2;
3006 actual_field = (char *)fs_get((len+1) * sizeof(char));
3007 snprintf(actual_field, len+1, "%s%s: ",
3008 (header && header->env && header->env->remail) ? "ReSent-" : "",
3009 field ? field : "");
3010 actual_field[len] = '\0';
3013 * We were folding everything except message-id, but that wasn't
3014 * sufficient. Since 822 only allows folding where linear-white-space
3015 * is allowed we'd need a smarter folder than "fold" to do it. So,
3016 * instead of inventing that smarter folder (which would have to
3017 * know 822 syntax)
3019 * We could just alloc space and copy the actual_field followed by
3020 * the value into it, but since that's what fold does anyway we'll
3021 * waste some cpu time and use fold with a big fold parameter.
3023 * We upped the references folding from 75 to 256 because we were
3024 * encountering longer-than-75 message ids, and to break one line
3025 * in references is to break them all.
3027 if(field && !strucmp("Subject", field))
3028 fold_by = 75;
3029 else if(field && !strucmp("References", field))
3030 fold_by = 256;
3031 else
3032 fold_by = big;
3034 folded = fold(value, fold_by, big, actual_field, " ", FLD_CRLF);
3036 if(actual_field)
3037 fs_give((void **)&actual_field);
3039 else if(value){ /* encoding was done */
3040 RFC822BUFFER rbuf;
3041 size_t ll;
3044 * rfc1522_encode already inserted continuation lines and did
3045 * the necessary folding so we don't have to do it. Let
3046 * rfc822_header_line add the trailing crlf and the resent- if
3047 * necessary. The 20 could actually be a 12.
3049 ll = strlen(field) + strlen(value) + 20;
3050 folded = (char *) fs_get(ll * sizeof(char));
3051 *folded = '\0';
3052 rbuf.f = dummy_soutr;
3053 rbuf.s = NULL;
3054 rbuf.beg = folded;
3055 rbuf.cur = folded;
3056 rbuf.end = folded+ll-1;
3057 rfc822_output_header_line(&rbuf, field,
3058 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3059 *rbuf.cur = '\0';
3062 if(value && folded){
3063 if(writehdr && f)
3064 ret = (*f)(s, folded);
3066 if(ret && localcopy && lmc.so && !lmc.all_written)
3067 ret = so_puts(lmc.so, folded);
3070 if(folded)
3071 fs_give((void **)&folded);
3073 if(converted && converted != text)
3074 fs_give((void **) &converted);
3076 else
3077 ret = 0;
3079 return(ret);
3084 * Do appropriate encoding of text header lines.
3085 * For some field types (those that consist of 822 *text) we just encode
3086 * the whole thing. For structured fields we encode only within comments
3087 * if possible.
3089 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3090 * s -- Source string.
3091 * charset -- Charset to encode with.
3092 * encode_all -- If set, encode the whole string. If not, try to encode
3093 * only within comments if possible.
3095 * Returns S is returned if no encoding is done. D is returned if encoding
3096 * was needed.
3098 char *
3099 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3101 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3102 int in_comment = 0;
3104 if(!s)
3105 return((char *)s);
3107 if(dlen < SIZEOF_20KBUF)
3108 panic("bad call to encode_header_value");
3110 if(!encode_all){
3112 * We don't have to worry about keeping track of quoted-strings because
3113 * none of these fields which aren't addresses contain quoted-strings.
3114 * We do keep track of escaped parens inside of comments and comment
3115 * nesting.
3117 p = d+7000;
3118 for(q = (char *)s; *q; q++){
3119 switch(*q){
3120 case LPAREN:
3121 if(in_comment++ == 0)
3122 start_of_comment = q;
3124 break;
3126 case RPAREN:
3127 if(--in_comment == 0){
3128 /* encode the comment, excluding the outer parens */
3129 if(p-d < dlen-1)
3130 *p++ = LPAREN;
3132 *q = '\0';
3133 r = rfc1522_encode(d+14000, dlen-14000,
3134 (unsigned char *)start_of_comment+1,
3135 charset);
3136 if(r != start_of_comment+1)
3137 value = d+7000; /* some encoding was done */
3139 start_of_comment = NULL;
3140 if(r)
3141 sstrncpy(&p, r, dlen-1-(p-d));
3143 *q = RPAREN;
3144 if(p-d < dlen-1)
3145 *p++ = *q;
3147 else if(in_comment < 0){
3148 in_comment = 0;
3149 if(p-d < dlen-1)
3150 *p++ = *q;
3153 break;
3155 case BSLASH:
3156 if(!in_comment && *(q+1)){
3157 if(p-d < dlen-2){
3158 *p++ = *q++;
3159 *p++ = *q;
3163 break;
3165 default:
3166 if(!in_comment && p-d < dlen-1)
3167 *p++ = *q;
3169 break;
3173 if(value){
3174 /* Unterminated comment (wasn't really a comment) */
3175 if(start_of_comment)
3176 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3178 *p = '\0';
3183 * We have to check if there is anything that needs to be encoded that
3184 * wasn't in a comment. If there is, we'd better just start over and
3185 * encode the whole thing. So, if no encoding has been done within
3186 * comments, or if encoding is needed both within and outside of
3187 * comments, then we encode the whole thing. Otherwise, go with
3188 * the version that has only comments encoded.
3190 if(!value || rfc1522_encode(d, dlen,
3191 (unsigned char *)value, charset) != value)
3192 return(rfc1522_encode(d, dlen, s, charset));
3193 else{
3194 strncpy(d, value, dlen-1);
3195 d[dlen-1] = '\0';
3196 return(d);
3202 * pine_address_line - write a header field containing addresses,
3203 * one by one (so there's no buffer limit), and
3204 * wrapping where necessary.
3205 * Note: we use c-client functions to properly build the text string,
3206 * but have to screw around with pointers to fool c-client functions
3207 * into not blatting all the text into a single buffer. Yeah, I know.
3210 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3211 soutr_t f, void *s, int writehdr, int localcopy)
3213 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3214 size_t alloced = 0, sz;
3215 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3216 char *converted, *cs;
3217 ADDRESS *atmp;
3218 int i, count;
3219 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3220 RFC822BUFFER rbuf;
3221 static char comma[] = ", ";
3222 static char end_group[] = ";";
3223 #define no_comma (&comma[1])
3225 if(!alist) /* nothing in field! */
3226 return(1);
3228 if(!alist->host && alist->mailbox){ /* c-client group convention */
3229 in_group++;
3230 was_start_of_group++;
3231 /* encode mailbox of group */
3232 mtmp = alist->mailbox;
3233 if(mtmp){
3234 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3235 buftmp[sizeof(buftmp)-1] = '\0';
3236 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3237 if(converted){
3238 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3239 (unsigned char *) converted, cs));
3240 if(converted && converted != buftmp)
3241 fs_give((void **) &converted);
3243 else{
3244 failed++;
3245 goto bail_out;
3249 else
3250 mtmp = NULL;
3252 ptmp = alist->personal; /* remember personal name */
3253 /* make sure personal name is encoded */
3254 if(ptmp){
3255 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3256 buftmp[sizeof(buftmp)-1] = '\0';
3257 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3258 if(converted){
3259 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3260 (unsigned char *) converted, cs));
3261 if(converted && converted != buftmp)
3262 fs_give((void **) &converted);
3264 else{
3265 failed++;
3266 goto bail_out;
3270 atmp = alist->next;
3271 alist->next = NULL; /* digest only first address! */
3273 /* use automatic buffer unless it isn't big enough */
3274 if((alloced = est_size(alist)) > sizeof(tmp)){
3275 tmpptr = (char *)fs_get(alloced);
3276 sz = alloced;
3278 else{
3279 tmpptr = tmp;
3280 sz = sizeof(tmp);
3283 rbuf.f = dummy_soutr;
3284 rbuf.s = NULL;
3285 rbuf.beg = tmpptr;
3286 rbuf.cur = tmpptr;
3287 rbuf.end = tmpptr+sz-1;
3288 rfc822_output_address_line(&rbuf, field,
3289 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3290 *rbuf.cur = '\0';
3292 alist->next = atmp; /* restore pointer to next addr */
3294 if(alist->personal && alist->personal != ptmp)
3295 fs_give((void **) &alist->personal);
3297 alist->personal = ptmp; /* in case it changed, restore name */
3299 if(mtmp){
3300 if(alist->mailbox && alist->mailbox != mtmp)
3301 fs_give((void **) &alist->mailbox);
3303 alist->mailbox = mtmp;
3306 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3307 count -= 2;
3308 tmpptr[count] = '\0';
3312 * If there is no sending_stream and we are writing the Lcc header,
3313 * then we are piping it to sendmail -t which expects it to be a bcc,
3314 * not lcc.
3316 * When we write it to the fcc or postponed (the lmc.so),
3317 * we want it to be lcc, not bcc, so we put it back.
3319 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3320 fix_lcc = 1;
3322 if(writehdr && f && *tmpptr){
3323 if(fix_lcc)
3324 tmpptr[0] = 'b';
3326 failed = !(*f)(s, tmpptr);
3327 if(fix_lcc)
3328 tmpptr[0] = 'L';
3330 if(failed)
3331 goto bail_out;
3334 if(localcopy && lmc.so &&
3335 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3336 goto bail_out;
3338 for(alist = atmp; alist; alist = alist->next){
3339 delim = comma;
3340 /* account for c-client's representation of group names */
3341 if(in_group){
3342 if(!alist->host){ /* end of group */
3343 in_group = 0;
3344 was_start_of_group = 0;
3346 * Rfc822_write_address no longer writes out the end of group
3347 * unless the whole group address is passed to it, so we do
3348 * it ourselves.
3350 delim = end_group;
3352 else if(!localcopy || !lmc.so || lmc.all_written)
3353 continue;
3355 /* start of new group, print phrase below */
3356 else if(!alist->host && alist->mailbox){
3357 in_group++;
3358 was_start_of_group++;
3361 /* no comma before first address in group syntax */
3362 if(was_start_of_group && alist->host){
3363 delim = no_comma;
3364 was_start_of_group = 0;
3367 /* write delimiter */
3368 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3369 || (localcopy && lmc.so && !lmc.all_written
3370 && !so_puts(lmc.so, delim)))
3371 goto bail_out;
3373 ptmp = alist->personal; /* remember personal name */
3374 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3375 buftmp[sizeof(buftmp)-1] = '\0';
3376 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3377 if(converted){
3378 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3379 (unsigned char *) converted, cs));
3380 if(converted && converted != buftmp)
3381 fs_give((void **) &converted);
3383 else{
3384 failed++;
3385 goto bail_out;
3388 atmp = alist->next;
3389 alist->next = NULL; /* tie off linked list */
3390 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3391 alloced = i;
3392 sz = alloced;
3393 fs_resize((void **)&tmpptr, alloced);
3396 *tmpptr = '\0';
3397 /* make sure we don't write out group end with rfc822_write_address */
3398 if(alist->host || alist->mailbox){
3399 rbuf.f = dummy_soutr;
3400 rbuf.s = NULL;
3401 rbuf.beg = tmpptr;
3402 rbuf.cur = tmpptr;
3403 rbuf.end = tmpptr+sz-1;
3404 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3405 *rbuf.cur = '\0';
3408 alist->next = atmp; /* restore next pointer */
3410 if(alist->personal && alist->personal != ptmp)
3411 fs_give((void **) &alist->personal);
3413 alist->personal = ptmp; /* in case it changed, restore name */
3416 * BUG
3417 * With group syntax addresses we no longer have two identical
3418 * streams of output. Instead, for the fcc/postpone copy we include
3419 * all of the addresses inside the :; of the group, and for the
3420 * mail we're sending we don't include them. That means we aren't
3421 * correctly keeping track of the column to wrap in, below. That is,
3422 * we are keeping track of the fcc copy but we aren't keeping track
3423 * of the regular copy. It could result in too long or too short
3424 * lines. Should almost never come up since group addresses are almost
3425 * never followed by other addresses in the same header, and even
3426 * when they are, you have to go out of your way to get the headers
3427 * messed up.
3429 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3430 count = i + 4;
3431 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3432 || (localcopy && lmc.so && !lmc.all_written &&
3433 !so_puts(lmc.so, "\015\012 ")))
3434 goto bail_out;
3436 else
3437 count += i + 2;
3439 if(((!in_group || was_start_of_group)
3440 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3441 || (localcopy && lmc.so && !lmc.all_written
3442 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3443 goto bail_out;
3446 bail_out:
3447 if(tmpptr && tmpptr != tmp)
3448 fs_give((void **)&tmpptr);
3450 if(failed)
3451 return(0);
3453 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3454 && ((localcopy && lmc.so
3455 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3460 * mutated pine version of c-client's rfc822_header() function.
3461 * changed to call pine-wrapped header and address functions
3462 * so we don't have to limit the header size to a fixed buffer.
3463 * This function also calls pine's body_header write function
3464 * because encoding is delayed until output_body() is called.
3466 long
3467 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3469 PINEFIELD *pf;
3470 int j;
3472 if(header->env->remail){ /* if remailing */
3473 long i = strlen (header->env->remail);
3474 if(i > 4 && header->env->remail[i-4] == '\015')
3475 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3477 if((f && !(*f)(s, header->env->remail))
3478 || (lmc.so && !lmc.all_written
3479 && !so_puts(lmc.so, header->env->remail)))
3480 return(0L); /* start with remail header */
3483 j = 0;
3484 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3485 switch(pf->type){
3487 * Warning: This is confusing. The 2nd to last argument used to
3488 * be just pf->writehdr. We want Bcc lines to be written out
3489 * if we are handing off to a sendmail temp file but not if we
3490 * are talking smtp, so bcc's writehdr is set to 0 and
3491 * pine_address_line was sending if writehdr OR !sending_stream.
3492 * That works as long as we want to write everything when
3493 * !sending_stream (an mta handoff to sendmail). But then we
3494 * added the undisclosed recipients line which should only get
3495 * written if writehdr is set, and not when we pass to a
3496 * sendmail temp file. So pine_address_line has been changed
3497 * so it bases its decision solely on the writehdr passed to it,
3498 * and the logic that worries about Bcc and sending_stream
3499 * was moved up to the caller (here) to decide when to set it.
3501 * So we have:
3502 * undisclosed recipients:; This will just be written
3503 * if writehdr was set and not
3504 * otherwise, nothing magical.
3505 *** We may want to change this, because sendmail -t doesn't handle
3506 *** the empty group syntax well unless it has been configured to
3507 *** do so. It isn't configured by default, or in any of the
3508 *** sendmail v8 configs. So we may want to not write this line
3509 *** if we're doing an mta_handoff (!sending_stream).
3511 * !sending_stream (which means a handoff to a sendmail -t)
3512 * bcc or lcc both set the arg so they'll get written
3513 * (There is also Lcc hocus pocus in pine_address_line
3514 * which converts the Lcc: to Bcc: for sendmail
3515 * processing.)
3516 * sending_stream (which means an smtp handoff)
3517 * bcc and lcc will never have writehdr set, so
3518 * will never be written (They both do have rcptto set,
3519 * so they both do cause RCPT TO commands.)
3521 * The localcopy is independent of sending_stream and is just
3522 * written if it is set for all of these.
3524 case Address:
3525 if(!pine_address_line(pf->name,
3526 header,
3527 pf->addr ? *pf->addr : NULL,
3530 (!strucmp("bcc",pf->name ? pf->name : "")
3531 || !strucmp("Lcc",pf->name ? pf->name : ""))
3532 ? !sending_stream
3533 : pf->writehdr,
3534 pf->localcopy))
3535 return(0L);
3537 break;
3539 case Fcc:
3540 case FreeText:
3541 case Subject:
3542 if(!pine_header_line(pf->name, header,
3543 pf->text ? *pf->text : NULL,
3544 f, s, pf->writehdr, pf->localcopy))
3545 return(0L);
3547 break;
3549 default:
3550 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3551 pf->name);
3552 break;
3557 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3559 * Add comforting "X-" header line indicating what sort of
3560 * authenticity the receiver can expect...
3562 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3563 NETMBX netmbox;
3564 char sstring[MAILTMPLEN], *label; /* place to write */
3565 MAILSTREAM *m;
3566 int i, anonymous = 1;
3568 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3569 m = ps_global->s_pool.streams[i];
3570 if(m && sp_flagged(m, SP_LOCKED)
3571 && mail_valid_net_parse(m->mailbox, &netmbox)
3572 && !netmbox.anoflag)
3573 anonymous = 0;
3576 if(!anonymous){
3577 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3578 *user = (*netmbox.user)
3579 ? netmbox.user
3580 : cached_user_name(netmbox.mailbox);
3581 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3582 isdigit((unsigned char)last_char) ? "[" : "",
3583 netmbox.host,
3584 isdigit((unsigned char) last_char) ? "]" : "");
3585 sstring[sizeof(sstring)-1] = '\0';
3586 label = "X-X-Sender"; /* Jeez. */
3587 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3588 label += 4;
3590 else{
3591 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3592 sstring[sizeof(sstring)-1] = '\0';
3593 label = "X-Warning";
3596 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3597 return(0L);
3599 #endif
3601 if(body && !header->env->remail){ /* not if remail or no body */
3602 if((f && !(*f)(s, MIME_VER))
3603 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3604 || !pine_write_body_header(body, f, s))
3605 return(0L);
3607 else{ /* write terminating newline */
3608 if((f && !(*f)(s, "\015\012"))
3609 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3610 return(0L);
3613 return(1L);
3618 * pine_rfc822_output - pine's version of c-client call. Necessary here
3619 * since we're not using its structures as intended!
3621 long
3622 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3624 int we_cancel = 0;
3625 long retval;
3627 dprint((4, "-- pine_rfc822_output\n"));
3629 we_cancel = busy_cue(NULL, NULL, 1);
3630 pine_encode_body(body); /* encode body as necessary */
3631 /* build and output RFC822 header, output body */
3632 retval = pine_rfc822_header(header, body, f, s)
3633 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3635 if(we_cancel)
3636 cancel_busy_cue(-1);
3638 return(retval);
3643 * post_rfc822_output - cloak for pine's 822 output routine. Since
3644 * we can't pass opaque envelope thru c-client posting
3645 * logic, we need to wrap the real output inside
3646 * something that c-client knows how to call.
3648 long
3649 post_rfc822_output(char *tmp,
3650 ENVELOPE *env,
3651 struct mail_bodystruct *body,
3652 soutr_t f,
3653 void *s,
3654 long int ok8bit)
3656 return(pine_rfc822_output(send_header, body, f, s));
3661 * posting_characterset- determine what transliteration is reasonable
3662 * for posting the given non-ascii messsage data.
3664 * preferred_charset is the charset the original data was labeled in.
3665 * If we can keep that we do.
3667 * Returns: always returns the preferred character set.
3669 char *
3670 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3672 unsigned long *charsetmap = NULL;
3673 unsigned long validbitmap;
3674 static char *ascii = "US-ASCII";
3675 static char *utf8 = "UTF-8";
3676 int notcjk = 0;
3678 if(!ps_global->post_utf8){
3679 validbitmap = 0;
3681 if(mp == HdrText){
3682 char *text = NULL;
3683 UCS *ucs = NULL, *ucsp;
3685 text = (char *) data;
3687 /* convert text in header to UCS characters */
3688 if(text)
3689 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3691 if(!(ucs && *ucs))
3692 return(ascii);
3695 * After the while loop is done the validbitmap has
3696 * a 1 bit for all the character sets that can
3697 * represent all of the characters of this header.
3699 charsetmap = init_charsetchecker(preferred_charset);
3701 if(!charsetmap)
3702 return(utf8);
3704 validbitmap = ~0;
3705 while((validbitmap & ~0x1) && (*ucsp)){
3706 if(*ucsp > 0xffff){
3707 fs_give((void **) &ucs);
3708 return(utf8);
3711 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3714 fs_give((void **) &ucs);
3716 notcjk = validbitmap & 0x1;
3717 validbitmap &= ~0x1;
3719 if(!validbitmap)
3720 return(utf8);
3722 else{
3723 struct mail_bodystruct *body = NULL;
3724 STORE_S *the_text = NULL;
3725 int outchars;
3726 unsigned char c;
3727 UCS ucs;
3728 CBUF_S cbuf;
3730 cbuf.cbuf[0] = '\0';
3731 cbuf.cbufp = cbuf.cbuf;
3732 cbuf.cbufend = cbuf.cbuf;
3734 body = (struct mail_bodystruct *) data;
3736 if(body && body->type == TYPEMULTIPART)
3737 body = &body->nested.part->body;
3739 if(body && body->type == TYPETEXT)
3740 the_text = (STORE_S *) body->contents.text.data;
3742 if(!the_text)
3743 return(ascii);
3745 so_seek(the_text, 0L, 0); /* rewind */
3747 charsetmap = init_charsetchecker(preferred_charset);
3749 if(!charsetmap)
3750 return(utf8);
3752 validbitmap = ~0;
3755 * Read a stream of UTF-8 characters from the_text
3756 * and convert them to UCS-4 characters for the translatable
3757 * test.
3759 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3760 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3761 /* got a ucs character */
3762 if(ucs > 0xffff)
3763 return(utf8);
3765 validbitmap &= charsetmap[(unsigned long) ucs];
3769 notcjk = validbitmap & 0x1;
3770 validbitmap &= ~0x1;
3772 if(!validbitmap)
3773 return(utf8);
3776 /* user chooses something other than UTF-8 */
3777 if(strucmp(ps_global->posting_charmap, utf8)){
3779 * If we're to post in other than UTF-8, and it can be
3780 * transliterated without losing fidelity, do it.
3781 * Else, use UTF-8.
3784 /* if ascii works, always use that */
3785 if(representable_in_charset(validbitmap, ascii))
3786 return(ascii);
3788 /* does the user's posting character set work? */
3789 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3790 return(ps_global->posting_charmap);
3792 /* this is the charset the message we are replying to was in */
3793 if(preferred_charset
3794 && strucmp(preferred_charset, ascii)
3795 && representable_in_charset(validbitmap, preferred_charset))
3796 return(preferred_charset);
3798 /* else, use UTF-8 */
3801 /* user chooses nothing, going with the default */
3802 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3803 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3804 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3805 char *most_preferred;
3808 * In this case the user didn't specify a posting character set
3809 * and we will choose the most-specific one from our list.
3812 /* ascii is best */
3813 if(representable_in_charset(validbitmap, ascii))
3814 return(ascii);
3816 /* Can we keep the original from the message we're replying to? */
3817 if(preferred_charset
3818 && strucmp(preferred_charset, ascii)
3819 && representable_in_charset(validbitmap, preferred_charset))
3820 return(preferred_charset);
3822 /* choose the best of the rest */
3823 most_preferred = most_preferred_charset(validbitmap);
3824 if(!most_preferred)
3825 return(utf8);
3828 * If the text we're labeling contains something like
3829 * smart quotes but no CJK characters, then instead of
3830 * labeling it as ISO-2022-JP we want to use UTF-8.
3832 if(notcjk){
3833 const CHARSET *cs;
3835 cs = utf8_charset(most_preferred);
3836 if(!cs
3837 || cs->script == SC_CHINESE_SIMPLIFIED
3838 || cs->script == SC_CHINESE_TRADITIONAL
3839 || cs->script == SC_JAPANESE
3840 || cs->script == SC_KOREAN)
3841 return(utf8);
3844 return(most_preferred);
3846 /* user explicitly chooses UTF-8 */
3847 else{
3848 /* if ascii works, always use that */
3849 if(representable_in_charset(validbitmap, ascii))
3850 return(ascii);
3852 /* else, use UTF-8 */
3857 return(utf8);
3861 static char **charsetlist = NULL;
3862 static int items_in_charsetlist = 0;
3863 static unsigned long *charsetmap = NULL;
3865 static char *downgrades[] = {
3866 "US-ASCII",
3867 "ISO-8859-15",
3868 "ISO-8859-1",
3869 "ISO-8859-2",
3870 "VISCII",
3871 "KOI8-R",
3872 "KOI8-U",
3873 "ISO-8859-7",
3874 "ISO-8859-6",
3875 "ISO-8859-8",
3876 "TIS-620",
3877 "ISO-2022-JP",
3878 "GB2312",
3879 "BIG5",
3880 "EUC-KR"
3884 unsigned long *
3885 init_charsetchecker(char *preferred_charset)
3887 int i, count = 0, reset = 0;
3888 char *ascii = "US-ASCII";
3889 char *utf8 = "UTF-8";
3892 * When user doesn't set a posting character set posting_charmap ends up
3893 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3894 * That's where the strange set of if-else's come from.
3897 /* user chooses something other than UTF-8 */
3898 if(strucmp(ps_global->posting_charmap, utf8)){
3899 count++; /* US-ASCII */
3900 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3901 reset++;
3903 /* if posting_charmap is valid, include it in list */
3904 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3905 && strucmp(ps_global->posting_charmap, ascii)
3906 && strucmp(ps_global->posting_charmap, utf8)
3907 && utf8_charset(ps_global->posting_charmap)){
3908 count++;
3909 if(!reset
3910 && (items_in_charsetlist < count
3911 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3912 reset++;
3915 if(preferred_charset && preferred_charset[0]
3916 && strucmp(preferred_charset, ascii)
3917 && strucmp(preferred_charset, utf8)
3918 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3919 count++;
3920 if(!reset
3921 && (items_in_charsetlist < count
3922 || strucmp(charsetlist[count-1], preferred_charset)))
3923 reset++;
3926 if(items_in_charsetlist != count)
3927 reset++;
3929 if(reset){
3930 if(charsetlist)
3931 free_list_array(&charsetlist);
3933 items_in_charsetlist = count;
3934 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3936 i = 0;
3937 charsetlist[i++] = cpystr(ascii);
3939 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3940 && strucmp(ps_global->posting_charmap, ascii)
3941 && strucmp(ps_global->posting_charmap, utf8)
3942 && utf8_charset(ps_global->posting_charmap))
3943 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3945 if(preferred_charset && preferred_charset[0]
3946 && strucmp(preferred_charset, ascii)
3947 && strucmp(preferred_charset, utf8)
3948 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3949 charsetlist[i++] = cpystr(preferred_charset);
3951 charsetlist[i] = NULL;
3954 /* user chooses nothing, going with the default */
3955 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3956 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3957 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3958 int add_preferred = 0;
3960 /* does preferred_charset have to be added to the list? */
3961 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3962 add_preferred = 1;
3963 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3964 if(!strucmp(downgrades[i], preferred_charset))
3965 add_preferred = 0;
3968 if(add_preferred){
3969 /* existing list is right size already */
3970 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3971 /* just check to see if last list item is the preferred_charset */
3972 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3973 /* no, fix it */
3974 reset++;
3975 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3976 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3979 else{
3980 reset++;
3981 if(charsetlist)
3982 free_list_array(&charsetlist);
3984 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3985 items_in_charsetlist = count;
3986 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3987 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3988 charsetlist[i] = cpystr(downgrades[i]);
3990 charsetlist[i++] = cpystr(preferred_charset);
3991 charsetlist[i] = NULL;
3994 else{
3995 /* if list is same size as downgrades, consider it good */
3996 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
3997 reset++;
3999 if(reset){
4000 if(charsetlist)
4001 free_list_array(&charsetlist);
4003 count = sizeof(downgrades)/sizeof(downgrades[0]);
4004 items_in_charsetlist = count;
4005 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4006 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4007 charsetlist[i] = cpystr(downgrades[i]);
4009 charsetlist[i] = NULL;
4013 /* user explicitly chooses UTF-8 */
4014 else{
4015 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4016 count++; /* US-ASCII */
4017 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4018 reset++;
4020 if(items_in_charsetlist != count)
4021 reset++;
4023 if(reset){
4024 if(charsetlist)
4025 free_list_array(&charsetlist);
4027 /* the list is just ascii and nothing else */
4028 items_in_charsetlist = count;
4029 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4031 i = 0;
4032 charsetlist[i++] = cpystr(ascii);
4033 charsetlist[i] = NULL;
4038 if(reset){
4039 if(charsetmap)
4040 fs_give((void **) &charsetmap);
4042 if(charsetlist)
4043 charsetmap = utf8_csvalidmap(charsetlist);
4046 return(charsetmap);
4050 /* total reset */
4051 void
4052 free_charsetchecker(void)
4054 if(charsetlist)
4055 free_list_array(&charsetlist);
4057 items_in_charsetlist = 0;
4059 if(charsetmap)
4060 fs_give((void **) &charsetmap);
4065 representable_in_charset(unsigned long validbitmap, char *charset)
4067 int i, done = 0, ret = 0;
4068 unsigned long j;
4070 if(!(charset && charset[0]))
4071 return ret;
4073 if(!strucmp(charset, "UTF-8"))
4074 return 1;
4076 for(i = 0; !done && i < items_in_charsetlist; i++){
4077 if(!strucmp(charset, charsetlist[i])){
4078 j = 1;
4079 j <<= (i+1);
4080 done++;
4081 if(validbitmap & j)
4082 ret = 1;
4086 return ret;
4090 char *
4091 most_preferred_charset(unsigned long validbitmap)
4093 unsigned long bm;
4094 unsigned long rm;
4095 int index;
4097 if(!(validbitmap && items_in_charsetlist > 0))
4098 return("UTF-8");
4100 /* careful, find_rightmost_bit modifies the bitmap */
4101 bm = validbitmap;
4102 rm = find_rightmost_bit(&bm);
4103 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4105 return(charsetlist[index]);
4110 * Set parameter to new value.
4112 void
4113 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4115 PARAMETER *pm;
4117 if(!param || !(paramname && *paramname))
4118 return;
4120 if(*param == NULL){
4121 pm = (*param) = mail_newbody_parameter();
4122 pm->attribute = cpystr(paramname);
4124 else{
4125 int nomatch;
4127 for(pm = *param;
4128 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4129 pm = pm->next)
4130 ;/* searching for paramname parameter */
4132 if(nomatch){ /* add charset parameter */
4133 pm->next = mail_newbody_parameter();
4134 pm = pm->next;
4135 pm->attribute = cpystr(paramname);
4137 /* else pm is existing paramname parameter */
4140 if(pm){
4141 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4142 if(pm->value)
4143 fs_give((void **) &pm->value);
4145 if(new_value)
4146 pm->value = cpystr(new_value);
4152 /*----------------------------------------------------------------------
4153 Remove the leading digits from SMTP error messages
4154 -----*/
4155 char *
4156 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4158 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4159 (*error == '.' && isdigit((unsigned char)*(error+1))))
4160 error++;
4162 snprintf(outbuf, outbuflen, printstring, error);
4163 outbuf[outbuflen-1] = '\0';
4164 return(outbuf);
4169 * Local globals pine's body output routine needs
4171 static soutr_t l_f;
4172 static TCPSTREAM *l_stream;
4173 static unsigned c_in_buf = 0;
4176 * def to make our pipe write's more friendly
4178 #ifdef PIPE_MAX
4179 #if PIPE_MAX > 20000
4180 #undef PIPE_MAX
4181 #endif
4182 #endif
4184 #ifndef PIPE_MAX
4185 #define PIPE_MAX 1024
4186 #endif
4190 * l_flust_net - empties gf_io terminal function's buffer
4193 l_flush_net(int force)
4195 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4196 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4198 tmp_20k_buf[c_in_buf] = '\0';
4199 if(!force){
4201 * The start of each write is expected to be the start of a
4202 * "record" (i.e., a CRLF terminated line). Make sure that is true
4203 * else we might screw up SMTP dot quoting...
4205 for(p = tmp_20k_buf, lp = NULL;
4206 (p = strstr(p, "\015\012")) != NULL;
4207 lp = (p += 2))
4211 if(!lp && c_in_buf > 2) /* no CRLF! */
4212 for(p = &tmp_20k_buf[c_in_buf] - 2;
4213 p > &tmp_20k_buf[0] && *p == '.';
4214 p--) /* find last non-dot */
4217 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4218 /* snippet remains */
4219 c = *lp;
4220 *lp = '\0';
4224 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4225 || (lmc.so && !lmc.all_written
4226 && !(lmc.text_only && lmc.text_written)
4227 && !so_puts(lmc.so, tmp_20k_buf)))
4228 return(0);
4230 c_in_buf = 0;
4231 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4232 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4233 c_in_buf++, lp++;
4236 return(1);
4241 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4245 l_putc(int c)
4247 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4248 tmp_20k_buf[c_in_buf++] = (char) c;
4250 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4256 * pine_rfc822_output_body - pine's version of c-client call. Again,
4257 * necessary since c-client doesn't know about how
4258 * we're treating attachments
4260 long
4261 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4263 STORE_S *bodyso;
4264 PART *part;
4265 PARAMETER *param;
4266 char *t, *cookie = NIL, *encode_error;
4267 char tmp[MAILTMPLEN];
4268 int add_trailing_crlf;
4269 LOC_2022_JP ljp;
4270 gf_io_t gc;
4272 dprint((4, "-- pine_rfc822_output_body: %d\n",
4273 body ? body->type : 0));
4275 bodyso = (STORE_S *) body->contents.text.data;
4277 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4278 part = body->nested.part; /* first body part */
4279 /* find cookie */
4280 for (param = body->parameter; param && !cookie; param = param->next)
4281 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4282 if (!cookie) cookie = "-"; /* yucky default */
4285 * Output a bit of text before the first multipart delimiter
4286 * to warn unsuspecting users of non-mime-aware ua's that
4287 * they should expect weirdness. We do not add this when signing a
4288 * message, though...
4290 #ifdef SMIME
4291 if(ps_global->smime && !ps_global->smime->do_sign)
4292 #endif
4293 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"))
4294 return(0);
4296 do { /* for each part */
4297 /* build cookie */
4298 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4299 tmp[sizeof(tmp)-1] = '\0';
4300 /* append cookie,mini-hdr,contents */
4301 if((f && !(*f)(s, tmp))
4302 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4303 || !pine_write_body_header(&part->body,f,s)
4304 || !pine_rfc822_output_body (&part->body,f,s))
4305 return(0);
4306 } while ((part = part->next) != NULL); /* until done */
4307 /* output trailing cookie */
4308 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4309 tmp[sizeof(tmp)-1] = '\0';
4310 #ifdef SMIME
4311 if(ps_global->smime && ps_global->smime->do_sign
4312 && strlen(tmp) < sizeof(tmp)-2)
4313 strncat(tmp, "\r\n", 2);
4314 #endif
4315 if(lmc.so && !lmc.all_written){
4316 so_puts(lmc.so, t);
4317 so_puts(lmc.so, "\015\012");
4320 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4323 l_f = f; /* set up for writing chars... */
4324 l_stream = s; /* out other end of pipe... */
4325 gf_filter_init();
4326 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4327 body->size.bytes));
4329 if(bodyso)
4330 gf_set_so_readc(&gc, bodyso);
4331 else
4332 return(1);
4335 * Don't add trailing line if it is ExternalText, which already guarantees
4336 * a trailing newline.
4338 add_trailing_crlf = !(bodyso->src == ExternalText);
4340 so_seek(bodyso, 0L, 0);
4342 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4343 char *charset;
4345 if(body->type == TYPETEXT
4346 && so_attr(bodyso, "edited", NULL)
4347 && (charset = parameter_val(body->parameter, "charset"))){
4348 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4349 if(!strucmp(charset, "iso-2022-jp")){
4350 ljp.report_err = 0;
4351 gf_link_filter(gf_line_test,
4352 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4354 else{
4355 void *table = utf8_rmap(charset);
4357 if(table){
4358 gf_link_filter(gf_convert_utf8_charset,
4359 gf_convert_utf8_charset_opt(table,0));
4361 else{
4362 /* else, just send it? */
4363 set_parameter(&body->parameter, "charset", "UTF-8");
4368 fs_give((void **)&charset);
4372 * Convert text pieces to canonical form
4373 * BEFORE applying any encoding (rfc1341: appendix G)...
4374 * NOTE: almost all filters expect CRLF newlines
4376 if(body->type == TYPETEXT
4377 && body->encoding != ENCBASE64
4378 && !so_attr(bodyso, "rawbody", NULL)){
4379 gf_link_filter(gf_local_nvtnl, NULL);
4382 switch (body->encoding) { /* all else needs filtering */
4383 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4384 gf_link_filter(gf_8bit_qp, NULL);
4385 break;
4387 case ENCBINARY: /* encode binary into BASE64 */
4388 gf_link_filter(gf_binary_b64, NULL);
4389 break;
4391 default: /* otherwise text */
4392 break;
4396 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4397 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4398 _("Encoding Error \"%s\""), encode_error);
4399 display_message('x');
4402 gf_clear_so_readc(bodyso);
4404 if(encode_error || !l_flush_net(TRUE))
4405 return(0);
4407 send_bytes_sent += gf_bytes_piped();
4408 so_release((STORE_S *)body->contents.text.data);
4410 if(lmc.so && !lmc.all_written && lmc.text_only){
4411 if(lmc.text_written){ /* we have some splainin' to do */
4412 char tmp[MAILTMPLEN];
4413 char *name = NULL;
4415 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4416 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4417 return(0);
4420 * BUG: If this name is not ascii it's going to cause trouble.
4422 name = parameter_val(body->parameter, "name");
4423 snprintf(tmp, sizeof(tmp),
4424 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4425 body_type_names(body->type),
4426 body->subtype ? body->subtype : "Unknown",
4427 name ? " (Name=\"" : "",
4428 name ? name : "",
4429 name ? "\")" : "",
4430 comatose(body->size.bytes));
4431 tmp[sizeof(tmp)-1] = '\0';
4432 if(name)
4433 fs_give((void **)&name);
4435 if(!so_puts(lmc.so, tmp))
4436 return(0);
4438 else /* suppress everything after first text part */
4439 lmc.text_written = (body->type == TYPETEXT
4440 && (!body->subtype
4441 || !strucmp(body->subtype, "plain")));
4444 if(add_trailing_crlf)
4445 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4446 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4447 else
4448 return(1);
4451 char *
4452 ToLower(char *s, char *t)
4454 int i;
4456 for(i = 0; s != NULL && s[i] != '\0'; i++)
4457 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4458 t[i] = '\0';
4460 return t;
4464 * pine_write_body_header - another c-client clone. This time
4465 * so the final encoding labels get set
4466 * correctly since it hasn't happened yet,
4467 * and to be paranoid about line lengths.
4469 * Returns: TRUE/nonzero on success, zero on error
4472 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4474 char tmp[MAILTMPLEN];
4475 RFC822BUFFER rbuf;
4476 int i;
4477 unsigned char c;
4478 STRINGLIST *stl;
4479 STORE_S *so;
4480 extern const char *tspecials;
4482 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4483 if(!(so_puts(so, "Content-Type: ")
4484 && so_puts(so, ToLower(body_types[body->type], tmp))
4485 && so_puts(so, "/")
4486 && so_puts(so, ToLower(body->subtype
4487 ? body->subtype
4488 : rfc822_default_subtype (body->type),tmp))))
4489 return(pwbh_finish(0, so));
4491 if(body->parameter){
4492 if(!pine_write_params(body->parameter, so))
4493 return(pwbh_finish(0, so));
4495 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4496 return(pwbh_finish(0, so));
4498 if(!so_puts(so, "\015\012"))
4499 return(pwbh_finish(0, so));
4501 if ((body->encoding /* note: encoding 7BIT never output! */
4502 && !(so_puts(so, "Content-Transfer-Encoding: ")
4503 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4504 ? ENCBASE64
4505 : (body->encoding == ENC8BIT)
4506 ? ENCQUOTEDPRINTABLE
4507 : (body->encoding <= ENCMAX)
4508 ? body->encoding
4509 : ENCOTHER])
4510 && so_puts(so, "\015\012")))
4512 * If requested, strip Content-ID headers that don't look like they
4513 * are needed. Microsoft's Outlook XP has a bug that causes it to
4514 * not show that there is an attachment when there is a Content-ID
4515 * header present on that attachment.
4517 * If user has quell-content-id turned on, don't output content-id
4518 * unless it is of type message/external-body.
4519 * Since this code doesn't look inside messages being forwarded
4520 * type message content-ids will remain as is and type multipart
4521 * alternative will remain as is. We don't create those on our
4522 * own. If we did, we'd have to worry about getting this right.
4524 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4525 || (body->type == TYPEMESSAGE
4526 && body->subtype
4527 && !strucmp(body->subtype, "external-body")))
4528 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4529 && so_puts(so, "\015\012")))
4530 || (body->description
4531 && strlen(body->description) < 5000 /* arbitrary! */
4532 && !pine_write_header_line("Content-Description: ", body->description, so))
4533 || (body->md5
4534 && !(so_puts(so, "Content-MD5: ")
4535 && so_puts(so, body->md5)
4536 && so_puts(so, "\015\012"))))
4537 return(pwbh_finish(0, so));
4539 if ((stl = body->language) != NULL) {
4540 if(!so_puts(so, "Content-Language: "))
4541 return(pwbh_finish(0, so));
4543 do {
4544 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4545 return(pwbh_finish(0, so));
4547 tmp[0] = '\0';
4548 rbuf.f = dummy_soutr;
4549 rbuf.s = NULL;
4550 rbuf.beg = tmp;
4551 rbuf.cur = tmp;
4552 rbuf.end = tmp+sizeof(tmp)-1;
4553 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4554 *rbuf.cur = '\0';
4556 if(!so_puts(so, tmp)
4557 || ((stl = stl->next) && !so_puts(so, ", ")))
4558 return(pwbh_finish(0, so));
4560 while (stl);
4562 if(!so_puts(so, "\015\012"))
4563 return(pwbh_finish(0, so));
4566 if (body->disposition.type) {
4567 if(!(so_puts(so, "Content-Disposition: ")
4568 && so_puts(so, body->disposition.type)))
4569 return(pwbh_finish(0, so));
4571 if(!pine_write_params(body->disposition.parameter, so))
4572 return(pwbh_finish(0, so));
4574 if(!so_puts(so, "\015\012"))
4575 return(pwbh_finish(0, so));
4578 /* copy out of so, a line at a time (or less than a K)
4579 * and send it down the pike
4581 so_seek(so, 0L, 0);
4582 i = 0;
4583 while(so_readc(&c, so))
4584 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4585 tmp[i] = '\0';
4586 if((f && !(*f)(s, tmp))
4587 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4588 return(pwbh_finish(0, so));
4590 i = 0;
4593 /* Finally, write blank line */
4594 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4595 return(pwbh_finish(0, so));
4597 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4600 return(0);
4605 * pine_write_header - convert, encode (if needed) and
4606 * write "header-name: field-body"
4609 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4611 char *cv, *cs, *vp;
4612 int rv;
4614 cs = posting_characterset(val, NULL, HdrText);
4615 cv = utf8_to_charset(val, cs, 0);
4616 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4617 (unsigned char *) cv, cs);
4619 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4621 if(cv && cv != val)
4622 fs_give((void **) &cv);
4625 return(rv);
4630 * pine_write_param - convert, encode and write MIME header-field parameters
4633 pine_write_params(PARAMETER *param, STORE_S *so)
4635 for(; param; param = param->next){
4636 int rv;
4637 char *cv, *cs;
4638 extern const char *tspecials;
4640 cs = posting_characterset(param->value, NULL, HdrText);
4641 cv = utf8_to_charset(param->value, cs, 0);
4642 rv = (so_puts(so, "; ")
4643 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4645 if(cv && cv != param->value)
4646 fs_give((void **) &cv);
4648 if(!rv)
4649 return(0);
4652 return(1);
4658 lmc_body_header_line(char *line, int beginning)
4660 if(lmc.so && !lmc.all_written){
4661 if(beginning && lmc.text_only && lmc.text_written
4662 && (!struncmp(line, "content-type:", 13)
4663 || !struncmp(line, "content-transfer-encoding:", 26)
4664 || !struncmp(line, "content-disposition:", 20))){
4666 * "comment out" the real values since our comment isn't
4667 * likely the same type, disposition nor encoding...
4669 if(!so_puts(lmc.so, "X-"))
4670 return(FALSE);
4673 return(so_puts(lmc.so, line));
4676 return(TRUE);
4681 lmc_body_header_finish(void)
4683 if(lmc.so && !lmc.all_written){
4684 if(lmc.text_only && lmc.text_written
4685 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4686 return(FALSE);
4688 return(so_puts(lmc.so, "\015\012"));
4691 return(TRUE);
4697 pwbh_finish(int rv, STORE_S *so)
4699 if(so)
4700 so_give(&so);
4702 return(rv);
4707 * pine_free_body - c-client call wrapper so the body data pointer we
4708 * we're using in a way c-client doesn't know about
4709 * gets free'd appropriately.
4711 void
4712 pine_free_body(struct mail_bodystruct **body)
4715 * Preempt c-client's contents.text.data clean up since we've
4716 * usurped it's meaning for our own purposes...
4718 pine_free_body_data (*body);
4720 /* Then let c-client handle the rest... */
4721 mail_free_body(body);
4726 * pine_free_body_data - free pine's interpretations of the body part's
4727 * data pointer.
4729 void
4730 pine_free_body_data(struct mail_bodystruct *body)
4732 if(body){
4733 if(body->type == TYPEMULTIPART){
4734 PART *part = body->nested.part;
4735 do /* for each part */
4736 pine_free_body_data(&part->body);
4737 while ((part = part->next) != NULL); /* until done */
4739 else if(body->contents.text.data)
4740 so_give((STORE_S **) &body->contents.text.data);
4745 long
4746 send_body_size(struct mail_bodystruct *body)
4748 long l = 0L;
4749 PART *part;
4751 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4752 part = body->nested.part; /* first body part */
4753 do /* for each part */
4754 l += send_body_size(&part->body);
4755 while ((part = part->next) != NULL); /* until done */
4756 return(l);
4759 return(l + body->size.bytes);
4764 sent_percent(void)
4766 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4767 / send_bytes_to_send);
4768 return(MIN(i, 100));
4773 * pine_smtp_verbose_out - write
4775 void
4776 pine_smtp_verbose_out(char *s)
4778 #ifdef _WINDOWS
4779 LPTSTR slpt;
4780 #endif
4781 if(verbose_send_output && s){
4782 char *p, last = '\0';
4784 for(p = s; *p; p++)
4785 if(*p == '\015')
4786 *p = ' ';
4787 else
4788 last = *p;
4790 #ifdef _WINDOWS
4792 * The stream is opened in Unicode mode, so we need to fix the
4793 * argument to fputs.
4795 slpt = utf8_to_lptstr((LPSTR) s);
4796 if(slpt){
4797 _fputts(slpt, verbose_send_output);
4798 fs_give((void **) &slpt);
4801 if(last != '\012')
4802 _fputtc(L'\n', verbose_send_output);
4803 #else
4804 fputs(s, verbose_send_output);
4805 if(last != '\012')
4806 fputc('\n', verbose_send_output);
4807 #endif
4814 * pine_header_forbidden - is this name a "forbidden" header?
4816 * name - the header name to check
4817 * We don't allow user to change these.
4820 pine_header_forbidden(char *name)
4822 char **p;
4823 static char *forbidden_headers[] = {
4824 "sender",
4825 "x-sender",
4826 "x-x-sender",
4827 "date",
4828 "received",
4829 "message-id",
4830 "in-reply-to",
4831 "path",
4832 "resent-message-id",
4833 "resent-date",
4834 "resent-from",
4835 "resent-sender",
4836 "resent-to",
4837 "resent-cc",
4838 "resent-reply-to",
4839 "mime-version",
4840 "content-type",
4841 "x-priority",
4842 "user-agent",
4843 "list-help", /* rfc 2369, section 3 */
4844 "list-unsubscribe",
4845 "list-subscribe",
4846 "list-post",
4847 "list-owner",
4848 "list-archive",
4849 NULL
4852 for(p = forbidden_headers; *p; p++)
4853 if(!strucmp(name, *p))
4854 break;
4856 return((*p) ? 1 : 0);
4861 * hdr_is_in_list - is there a custom value for this header?
4863 * hdr - the header name to check
4864 * custom - the list to check in
4865 * Returns 1 if there is a custom value, 0 otherwise.
4868 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4870 PINEFIELD *pf;
4872 for(pf = custom; pf && pf->name; pf = pf->next)
4873 if(strucmp(pf->name, hdr) == 0)
4874 return 1;
4876 return 0;
4881 * count_custom_hdrs_pf - returns number of custom headers in arg
4882 * custom -- the list to be counted
4883 * only_nonstandard -- only count headers which aren't standard pine headers
4886 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4888 int ret = 0;
4890 for(; custom && custom->name; custom = custom->next)
4891 if(!only_nonstandard || !custom->standard)
4892 ret++;
4894 return(ret);
4899 * count_custom_hdrs_list - returns number of custom headers in arg
4902 count_custom_hdrs_list(char **list)
4904 char **p;
4905 char *q = NULL;
4906 char *name;
4907 char *t;
4908 char save;
4909 int ret = 0;
4911 if(list){
4912 for(p = list; (q = *p) != NULL; p++){
4913 if(q[0]){
4914 /* remove leading whitespace */
4915 name = skip_white_space(q);
4917 /* look for colon or space or end */
4918 for(t = name;
4919 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4920 ;/* do nothing */
4922 save = *t;
4923 *t = '\0';
4924 if(!pine_header_forbidden(name))
4925 ret++;
4927 *t = save;
4932 return(ret);
4937 * set_default_hdrval - put the user's default value for this header
4938 * into pf->textbuf.
4939 * setthis - the pinefield to be set
4940 * custom - where to look for the default
4942 CustomType
4943 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4945 PINEFIELD *pf;
4946 CustomType ret = NoMatch;
4948 if(!setthis || !setthis->name){
4949 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4950 return(ret);
4953 setthis->textbuf = NULL;
4955 for(pf = custom; pf && pf->name; pf = pf->next){
4956 if(strucmp(pf->name, setthis->name) != 0)
4957 continue;
4959 ret = pf->cstmtype;
4961 /* turn on editing */
4962 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4963 setthis->canedit = 1;
4965 if(pf->val)
4966 setthis->textbuf = cpystr(pf->val);
4969 if(!setthis->textbuf)
4970 setthis->textbuf = cpystr("");
4972 return(ret);
4977 * pine_header_standard - is this name a "standard" header?
4979 * name - the header name to check
4981 FieldType
4982 pine_header_standard(char *name)
4984 int i;
4986 /* check to see if this is a standard header */
4987 for(i = 0; pf_template[i].name; i++)
4988 if(!strucmp(name, pf_template[i].name))
4989 return(pf_template[i].type);
4991 return(TypeUnknown);
4996 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
4997 * Allocates space for each name and addr ptr.
4998 * Allocates space for default in textbuf, even if empty.
5000 * head - the first PINEFIELD to fill in
5001 * list - the list to parse
5003 void
5004 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
5006 char **p, *q, *t, *name, *value, save;
5007 PINEFIELD *pf;
5009 pf = head;
5011 if(list){
5012 for(p = list; (q = *p) != NULL; p++){
5014 if(q[0]){
5016 /* anything after leading whitespace? */
5017 if(!*(name = skip_white_space(q)))
5018 continue;
5020 /* look for colon or space or end */
5021 for(t = name;
5022 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5023 ;/* do nothing */
5025 /* if there is a space in the field-name, skip it */
5026 if(isspace((unsigned char)*t)){
5027 q_status_message1(SM_ORDER, 3, 3,
5028 _("Space not allowed in header name (%s)"),
5029 name);
5030 continue;
5033 save = *t;
5034 *t = '\0';
5036 /* Don't allow any of the forbidden headers. */
5037 if(pine_header_forbidden(name)){
5038 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5039 _("Not allowed to change header \"%s\""),
5040 name);
5042 *t = save;
5043 continue;
5046 if(pf){
5047 if(pine_header_standard(name) != TypeUnknown)
5048 pf->standard = 1;
5050 pf->name = cpystr(name);
5051 pf->type = FreeText;
5052 pf->cstmtype = cstmtype;
5053 pf->next = pf+1;
5055 #ifdef OLDWAY
5057 * Some mailers apparently break if we change
5058 * user@domain into Fred <user@domain> for
5059 * return-receipt-to,
5060 * so we'll just call this a FreeText field, too.
5063 * For now, all custom headers are FreeText except for
5064 * this one that we happen to know about. We might
5065 * have to add some syntax to the config option so that
5066 * people can tell us their custom header takes addresses.
5068 if(!strucmp(pf->name, "Return-Receipt-to")){
5069 pf->type = Address;
5070 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5071 *pf->addr = (ADDRESS *)NULL;
5073 #endif /* OLDWAY */
5075 *t = save;
5077 /* remove space between name and colon */
5078 value = skip_white_space(t);
5080 /* give them an alloc'd default, even if empty */
5081 pf->textbuf = cpystr((*value == ':')
5082 ? skip_white_space(++value) : "");
5083 if(pf->textbuf && pf->textbuf[0])
5084 pf->val = cpystr(pf->textbuf);
5086 pf++;
5088 else
5089 *t = save;
5094 /* fix last next pointer */
5095 if(head && pf != head)
5096 (pf-1)->next = NULL;
5101 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5102 * setup except that it doesn't have default values.
5103 * Add those defaults if they exist in "list".
5105 * head - the first PINEFIELD to add a default to
5106 * list - the list to get the defaults from
5108 void
5109 add_defaults_from_list(PINEFIELD *head, char **list)
5111 char **p, *q, *t, *name, *value, save;
5112 PINEFIELD *pf;
5114 for(pf = head; pf && list; pf = pf->next){
5115 if(!pf->name)
5116 continue;
5118 for(p = list; (q = *p) != NULL; p++){
5120 if(q[0]){
5122 /* anything after leading whitespace? */
5123 if(!*(name = skip_white_space(q)))
5124 continue;
5126 /* look for colon or space or end */
5127 for(t = name;
5128 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5129 ;/* do nothing */
5131 /* if there is a space in the field-name, skip it */
5132 if(isspace((unsigned char)*t))
5133 continue;
5135 save = *t;
5136 *t = '\0';
5138 if(strucmp(name, pf->name) != 0){
5139 *t = save;
5140 continue;
5143 *t = save;
5146 * Found the right header. See if it has a default
5147 * value set.
5150 /* remove space between name and colon */
5151 value = skip_white_space(t);
5153 if(*value == ':'){
5154 char *defval;
5156 defval = skip_white_space(++value);
5157 if(defval && *defval){
5158 if(pf->val)
5159 fs_give((void **)&pf->val);
5161 pf->val = cpystr(defval);
5165 break; /* on to next pf */
5173 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5174 * fill in the defaults
5175 * Args - list -- The list to parse.
5176 * cstmtype -- Fill the in cstmtype field with this value
5178 PINEFIELD *
5179 parse_custom_hdrs(char **list, CustomType cstmtype)
5181 PINEFIELD *pfields;
5182 int i;
5185 * add one for possible use by fcc
5186 * What is this "possible use"? I don't see it. I don't
5187 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5189 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5190 pfields = (PINEFIELD *)fs_get((size_t) i);
5191 memset(pfields, 0, (size_t) i);
5193 /* set up the custom header pfields */
5194 customized_hdr_setup(pfields, list, cstmtype);
5196 return(pfields);
5201 * Combine the two lists of headers into one list which is allocated here
5202 * and freed by the caller. Eliminate duplicates with values from the role
5203 * taking precedence over values from the default.
5205 PINEFIELD *
5206 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5208 PINEFIELD *pfields, *pf, *pf2;
5209 int max_hdrs, i;
5211 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5212 count_custom_hdrs_pf(dflthdrs,0);
5214 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5215 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5217 pf = pfields;
5218 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5219 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5220 pf->type = pf2->type;
5221 pf->cstmtype = pf2->cstmtype;
5222 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5223 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5224 pf->standard = pf2->standard;
5225 pf->next = pf+1;
5226 pf++;
5229 /* if these aren't already there, add them */
5230 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5231 /* check for already there */
5232 for(i = 0;
5233 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5234 i++)
5237 if(!pfields[i].name){ /* this is a new one */
5238 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5239 pf->type = pf2->type;
5240 pf->cstmtype = pf2->cstmtype;
5241 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5242 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5243 pf->standard = pf2->standard;
5244 pf->next = pf+1;
5245 pf++;
5249 /* fix last next pointer */
5250 if(pf != pfields)
5251 (pf-1)->next = NULL;
5253 return(pfields);
5258 * free_customs - free misc. resources associated with custom header fields
5260 * pf - pointer to first custom field
5262 void
5263 free_customs(PINEFIELD *head)
5265 PINEFIELD *pf;
5267 for(pf = head; pf && pf->name; pf = pf->next){
5269 fs_give((void **)&pf->name);
5271 if(pf->val)
5272 fs_give((void **)&pf->val);
5274 /* only true for FreeText */
5275 if(pf->textbuf)
5276 fs_give((void **)&pf->textbuf);
5278 /* only true for Address */
5279 if(pf->addr && *pf->addr)
5280 mail_free_address(pf->addr);
5283 fs_give((void **)&head);
5288 * encode_whole_header
5290 * Returns 1 if whole value should be encoded
5291 * 0 to encode only within comments
5294 encode_whole_header(char *field, METAENV *header)
5296 int retval = 0;
5297 PINEFIELD *pf;
5299 if(field && (!strucmp(field, "Subject") ||
5300 !strucmp(field, "Comment") ||
5301 !struncmp(field, "X-", 2)))
5302 retval++;
5303 else if(field && *field && header && header->custom){
5304 for(pf = header->custom; pf && pf->name; pf = pf->next){
5306 if(!pf->standard && !strucmp(pf->name, field)){
5307 retval++;
5308 break;
5313 return(retval);
5317 /*----------------------------------------------------------------------
5318 Post news via NNTP or inews
5320 Args: env -- envelope of message to post
5321 body -- body of message to post
5323 Returns: -1 if failed or cancelled, 1 if succeeded
5325 WARNING: This call function has the side effect of writing the message
5326 to the lmc.so object.
5327 ----*/
5329 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5330 void (*pipecb_f)(PIPE_S *, int, void *))
5332 char *error_mess, error_buf[200], **news_servers;
5333 char **servers_to_use;
5334 int we_cancel = 0, server_no = 0, done_posting = 0;
5335 void *orig_822_output;
5336 BODY *bp = NULL;
5338 error_buf[0] = '\0';
5339 we_cancel = busy_cue("Posting news", NULL, 0);
5341 dprint((4, "Posting: [%s]\n",
5342 (header && header->env && header->env->newsgroups)
5343 ? header->env->newsgroups : "?"));
5345 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5346 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5347 && ps_global->VAR_NNTP_SERVER[0][0])){
5348 /*---------- NNTP server defined ----------*/
5349 error_mess = NULL;
5350 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5351 && alt_nntp_servers[0][0])
5352 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5355 * Install our rfc822 output routine
5357 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5358 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5359 (void *)post_rfc822_output);
5361 server_no = 0;
5362 news_servers = (char **)fs_get(2 * sizeof(char *));
5363 news_servers[1] = NULL;
5364 while(!done_posting && servers_to_use[server_no] &&
5365 servers_to_use[server_no][0]){
5366 news_servers[0] = servers_to_use[server_no];
5367 ps_global->noshow_error = 1;
5368 #ifdef DEBUG
5369 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5370 #else
5371 sending_stream = nntp_open(news_servers, 0L);
5372 #endif
5373 ps_global->noshow_error = 0;
5375 if(sending_stream != NULL) {
5376 unsigned short save_encoding, added_encoding;
5379 * Fake that we've got clearance from the transport agent
5380 * for 8bit transport for the benefit of our output routines...
5382 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5383 && (bp = first_text_8bit(body))){
5384 int i;
5386 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5389 if(i > ENCMAX){ /* no empty encoding slots! */
5390 bp = NULL;
5392 else {
5393 added_encoding = i;
5394 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5395 save_encoding = bp->encoding;
5396 bp->encoding = added_encoding;
5401 * Set global header pointer so we can get at it later...
5403 send_header = header;
5404 ps_global->noshow_error = 1;
5405 if(nntp_mail(sending_stream, header->env, body) == 0)
5406 snprintf(error_mess = error_buf, sizeof(error_buf),
5407 _("Error posting message: %s"),
5408 sending_stream->reply);
5409 else{
5410 done_posting = 1;
5411 error_buf[0] = '\0';
5412 error_mess = NULL;
5415 error_buf[sizeof(error_buf)-1] = '\0';
5416 smtp_close(sending_stream);
5417 ps_global->noshow_error = 0;
5418 sending_stream = NULL;
5419 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5420 body_encodings[added_encoding] = NULL;
5421 bp->encoding = save_encoding;
5424 } else {
5425 /*---- Open of NNTP connection failed ------ */
5426 snprintf(error_mess = error_buf, sizeof(error_buf),
5427 _("Error connecting to news server: %s"),
5428 ps_global->c_client_error);
5429 error_buf[sizeof(error_buf)-1] = '\0';
5430 dprint((1, error_buf));
5432 server_no++;
5434 fs_give((void **)&news_servers);
5435 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5436 } else {
5437 /*----- Post via local mechanism -------*/
5438 #ifdef _WINDOWS
5439 snprintf(error_mess = error_buf, sizeof(error_buf),
5440 _("Can't post, NNTP-server must be defined!"));
5441 #else /* UNIX */
5442 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5443 pipecb_f);
5444 #endif
5447 if(we_cancel)
5448 cancel_busy_cue(0);
5450 if(error_mess){
5451 if(lmc.so && !lmc.all_written)
5452 so_give(&lmc.so); /* clean up any fcc data */
5454 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5455 return(-1);
5458 lmc.all_written = 1;
5459 return(1);
5463 /* ----------------------------------------------------------------------
5464 Figure out command to start local SMTP agent
5466 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5468 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5470 ----*/
5471 char *
5472 smtp_command(char *errbuf, size_t errbuflen)
5474 #ifdef _WINDOWS
5475 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5476 && ps_global->VAR_SMTP_SERVER[0][0]))
5477 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5479 errbuf[errbuflen-1] = '\0';
5480 #else /* UNIX */
5481 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5482 char tmp[256];
5484 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (sizeof(tmp)-3)/2, SENDMAIL,
5485 (sizeof(tmp)-3)/2, SENDMAILFLAGS);
5486 return(cpystr(tmp));
5487 # else
5488 strncpy(errbuf, _("No default posting command."), errbuflen);
5489 errbuf[errbuflen-1] = '\0';
5490 # endif
5491 #endif
5493 return(NULL);
5498 #ifndef _WINDOWS
5499 /*----------------------------------------------------------------------
5500 Hand off given message to local posting agent
5502 Args: envelope -- The envelope for the BCC and debugging
5503 header -- The text of the message header
5504 errbuf -- buffer for reporting errors (assumed non-NULL)
5505 len -- Length of errbuf
5507 ----*/
5509 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5510 char *errbuf, size_t len,
5511 void (*bigresult_f) (char *, int),
5512 void (*pipecb_f)(PIPE_S *, int, void *))
5514 #ifdef DF_SENDMAIL_PATH
5515 char cmd_buf[256];
5516 #endif
5517 char *cmd = NULL;
5520 * A bit of complicated policy implemented here.
5521 * There are two posting variables sendmail-path and smtp-server.
5522 * Precedence is in that order.
5523 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5524 * Precedence is in that order.
5525 * Said differently, the order goes something like what's below.
5527 * NOTE: the fixed/command-line/user precendence handling is also
5528 * indicated by what's pointed to by ps_global->VAR_*, but since
5529 * that also includes the global defaults, it's not sufficient.
5532 if(ps_global->FIX_SENDMAIL_PATH
5533 && ps_global->FIX_SENDMAIL_PATH[0]){
5534 cmd = ps_global->FIX_SENDMAIL_PATH;
5536 else if(!(ps_global->FIX_SMTP_SERVER
5537 && ps_global->FIX_SMTP_SERVER[0])){
5538 if(ps_global->COM_SENDMAIL_PATH
5539 && ps_global->COM_SENDMAIL_PATH[0]){
5540 cmd = ps_global->COM_SENDMAIL_PATH;
5542 else if(!(ps_global->COM_SMTP_SERVER
5543 && ps_global->COM_SMTP_SERVER[0])){
5544 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5545 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5546 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5547 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5548 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5549 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5550 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5551 else
5552 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5554 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5555 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5556 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5557 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5558 if(ps_global->GLO_SENDMAIL_PATH
5559 && ps_global->GLO_SENDMAIL_PATH[0]){
5560 cmd = ps_global->GLO_SENDMAIL_PATH;
5562 #ifdef DF_SENDMAIL_PATH
5564 * This defines the default method of posting. So,
5565 * unless we're told otherwise use it...
5567 else if(!(ps_global->GLO_SMTP_SERVER
5568 && ps_global->GLO_SMTP_SERVER[0])){
5569 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5570 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5572 #endif
5577 *errbuf = '\0';
5578 if(cmd){
5579 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5581 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5582 return(1);
5584 else
5585 return(0);
5590 /*----------------------------------------------------------------------
5591 Hand off given message to local posting agent
5593 Args: envelope -- The envelope for the BCC and debugging
5594 header -- The text of the message header
5595 errbuf -- buffer for reporting errors (assumed non-NULL)
5596 errbuflen -- Length of errbuf
5598 Fork off mailer process and pipe the message into it
5599 Called to post news via Inews when NNTP is unavailable
5601 ----*/
5602 char *
5603 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5604 size_t errbuflen,
5605 void (*bigresult_f) (char *, int),
5606 void (*pipecb_f)(PIPE_S *, int, void *))
5608 char *err = NULL;
5609 #ifdef SENDNEWS
5610 char *s;
5611 char tmp[200];
5613 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5614 *s = '\0';
5616 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5617 strncpy(tmp, err, sizeof(tmp)-1);
5618 tmp[sizeof(tmp)-1] = '\0';
5619 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5620 SENDNEWS, tmp);
5623 if(s)
5624 *s = ' '; /* restore the date */
5626 #else /* !SENDNEWS */ /* this is the default case */
5627 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5628 err[errbuflen-1] = '\0';
5629 #endif /* !SENDNEWS */
5630 return(err);
5635 /*----------------------------------------------------------------------
5636 Hand off message to local MTA; it parses recipients from 822 header
5638 Args: header -- struct containing header data
5639 body -- struct containing message body data
5640 cmd -- command to use for handoff (%s says where file should go)
5641 errs -- pointer to buf to hold errors
5643 ----*/
5644 char *
5645 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5646 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5647 void (*pipecb_f)(PIPE_S *, int, void *))
5649 char *result = NULL;
5650 PIPE_S *pipe;
5652 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5654 if((pipe = open_system_pipe(cmd, &result, NULL,
5655 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5656 0, pipecb_f, pipe_report_error)) != NULL){
5657 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5658 (TCPSTREAM *) pipe)){
5659 strncpy(errs, _("Error posting."), errslen-1);
5660 errs[errslen-1] = '\0';
5663 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5664 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5665 if(result && bigresult_f)
5666 (*bigresult_f)(result, CM_BR_ERROR);
5669 else
5670 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5672 if(result){
5673 our_unlink(result);
5674 fs_give((void **)&result);
5677 return(*errs ? errs : NULL);
5682 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5683 * pipes rather than a tcp stream
5685 long
5686 pine_pipe_soutr_nl (void *stream, char *s)
5688 long rv = T;
5689 char *p;
5690 size_t n;
5692 while(*s && rv){
5693 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0)
5694 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5695 if(rv < 0){
5696 if(errno != EINTR){
5697 rv = 0;
5698 break;
5701 else{
5702 s += rv;
5703 n -= rv;
5706 if(p && rv){
5707 s = p + 2; /* write UNIX EOL */
5708 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5709 if(rv < 0 && errno != EINTR){
5710 rv = 0;
5711 break;
5714 else
5715 break;
5718 return(rv);
5720 #endif
5723 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5727 * helpful def's
5729 #define S(X) ((PIPE_S *)(X))
5730 #define GETBUFLEN (4 * MAILTMPLEN)
5733 void *
5734 piped_smtp_open (char *host, char *service, long unsigned int port)
5736 char *postcmd;
5737 void *rv = NULL;
5739 if(strucmp(host, "localhost")){
5740 char tmp[MAILTMPLEN];
5742 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5743 sizeof(tmp)-50, host);
5744 tmp[sizeof(tmp)-1] = '\0';
5745 mm_log(tmp, ERROR);
5747 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5748 rv = open_system_pipe(postcmd, NULL, NULL,
5749 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5750 0, NULL, pipe_report_error);
5751 fs_give((void **) &postcmd);
5753 else
5754 mm_log(ps_global->c_client_error, ERROR);
5756 return(rv);
5760 void *
5761 piped_aopen (NETMBX *mb, char *service, char *user)
5763 return(NULL);
5768 * piped_soutr - Replacement for tcp_soutr that writes one of our
5769 * pipes rather than a tcp stream
5771 long
5772 piped_soutr (void *stream, char *s)
5774 return(piped_sout(stream, s, strlen(s)));
5779 * piped_sout - Replacement for tcp_soutr that writes one of our
5780 * pipes rather than a tcp stream
5782 long
5783 piped_sout (void *stream, char *s, long unsigned int size)
5785 int i, o;
5787 if(S(stream)->out.d < 0)
5788 return(0L);
5790 if((i = (int) size) != 0){
5791 while((o = write(S(stream)->out.d, s, i)) != i)
5792 if(o < 0){
5793 if(errno != EINTR){
5794 piped_abort(stream);
5795 return(0L);
5798 else{
5799 s += o; /* try again, fix up counts */
5800 i -= o;
5804 return(1L);
5809 * piped_getline - Replacement for tcp_getline that reads one
5810 * of our pipes rather than a tcp pipe
5812 * C-client expects that the \r\n will be stripped off.
5814 char *
5815 piped_getline (void *stream)
5817 static int cnt;
5818 static char *ptr;
5819 int n, m;
5820 char *ret, *s, *sp, c = '\0', d;
5822 if(S(stream)->in.d < 0)
5823 return(NULL);
5825 if(!S(stream)->tmp){ /* initialize! */
5826 /* alloc space to collect input (freed in close_system_pipe) */
5827 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5828 memset(S(stream)->tmp, 0, GETBUFLEN);
5829 cnt = -1;
5832 while(cnt < 0){
5833 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5834 if(errno != EINTR){
5835 piped_abort(stream);
5836 return(NULL);
5839 if(cnt == 0){
5840 piped_abort(stream);
5841 return(NULL);
5844 ptr = S(stream)->tmp;
5847 s = ptr;
5848 n = 0;
5849 while(cnt--){
5850 d = *ptr++;
5851 if((c == '\015') && (d == '\012')){
5852 ret = (char *)fs_get (n--);
5853 memcpy(ret, s, n);
5854 ret[n] = '\0';
5855 return(ret);
5858 n++;
5859 c = d;
5861 /* copy partial string from buffer */
5862 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5863 /* get more data */
5864 while(cnt < 0){
5865 memset(S(stream)->tmp, 0, GETBUFLEN);
5866 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5867 if(errno != EINTR){
5868 fs_give((void **) &ret);
5869 piped_abort(stream);
5870 return(NULL);
5873 if(cnt == 0){
5874 if(n > 0)
5875 ret[n-1] = '\0'; /* to try to get error message logged */
5876 else{
5877 piped_abort(stream);
5878 return(NULL);
5882 ptr = S(stream)->tmp;
5885 if(c == '\015' && *ptr == '\012'){
5886 ptr++;
5887 cnt--;
5888 ret[n - 1] = '\0'; /* tie off string with null */
5890 else if ((s = piped_getline(stream)) != NULL) {
5891 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5892 memcpy(ret, sp, n); /* copy first part */
5893 memcpy(ret + n, s, m); /* and second part */
5894 fs_give((void **) &sp); /* flush first part */
5895 fs_give((void **) &s); /* flush second part */
5896 ret[n + m] = '\0'; /* tie off string with null */
5899 return(ret);
5904 * piped_close - Replacement for tcp_close that closes pipes to our
5905 * child rather than a tcp connection
5907 void
5908 piped_close(void *stream)
5911 * Uninstall our hooks into smtp_send since it's being used by
5912 * the nntp driver as well...
5914 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5919 * piped_abort - close down the pipe we were using to post
5921 void
5922 piped_abort(void *stream)
5924 if(S(stream)->in.d >= 0){
5925 close(S(stream)->in.d);
5926 S(stream)->in.d = -1;
5929 if(S(stream)->out.d){
5930 close(S(stream)->out.d);
5931 S(stream)->out.d = -1;
5936 char *
5937 piped_host(void *stream)
5939 return(ps_global->hostname ? ps_global->hostname : "localhost");
5943 unsigned long
5944 piped_port(void *stream)
5946 return(0L);