* Update configure script to remove the search for a linking tcl
[alpine.git] / pith / send.c
blobdc5a500f107e3349a7f18c59bd597c4ea4070a36
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-2018 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 alpine_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 /* these types are transformed to text/plain */
894 && strucmp(b->subtype, "richtext")
895 && strucmp(b->subtype, "enriched")
896 && strucmp(b->subtype, "html"))
897 (*body)->subtype = cpystr(b->subtype);
899 if((charset = parameter_val(b->parameter,"charset")) != NULL){
900 /* let outgoing routines decide on charset */
901 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
902 fs_give((void **) &charset);
903 else{
904 (*body)->parameter = mail_newbody_parameter();
905 (*body)->parameter->attribute = cpystr("charset");
906 if(utf8_charset(charset)){
907 fs_give((void **) &charset);
908 (*body)->parameter->value = cpystr("UTF-8");
910 else
911 (*body)->parameter->value = charset;
915 (*body)->contents.text.data = (void *)so;
916 ps_global->postpone_no_flow = 1;
917 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
918 NULL, NULL, gbpt_flags);
919 ps_global->postpone_no_flow = 0;
922 gf_clear_so_writec(so);
924 /* We have what we want, blast this message... */
925 if((flags & REDRAFT_DEL)
926 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
927 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
928 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
930 else
931 return(redraft_cleanup(streamp, TRUE, flags));
933 return(redraft_cleanup(streamp, FALSE, flags));
937 /*----------------------------------------------------------------------
938 Clear deleted messages from given stream and expunge if necessary
940 Args: stream --
941 problem --
943 ----*/
945 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
947 MAILSTREAM *stream;
949 if(!(streamp && *streamp))
950 return(0);
952 if(!problem && streamp && (stream = *streamp)){
953 if(stream->nmsgs){
954 ps_global->expunge_in_progress = 1;
955 mail_expunge(stream); /* clean out deleted */
956 ps_global->expunge_in_progress = 0;
959 if(!stream->nmsgs){ /* close and delete folder */
960 int do_the_broach = 0;
961 char *mbox = NULL;
963 if(stream){
964 if(stream->original_mailbox && stream->original_mailbox[0])
965 mbox = cpystr(stream->original_mailbox);
966 else if(stream->mailbox && stream->mailbox[0])
967 mbox = cpystr(stream->mailbox);
970 /* if it is current, we have to change folders */
971 if(stream == ps_global->mail_stream)
972 do_the_broach++;
975 * This is the stream to the empty postponed-msgs folder.
976 * We are going to delete the folder in a second. It is
977 * probably preferable to unselect the mailbox and leave
978 * this stream open for re-use instead of actually closing it,
979 * so we do that if possible.
981 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
983 * This does the UNSELECT on the stream. A NULL
984 * return should mean that something went wrong and
985 * a mail_close already happened, so that should have
986 * cleaned things up in the callback.
988 if((stream=mail_open(stream, stream->mailbox,
989 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
990 /* now close it so it is put into the stream cache */
991 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
992 pine_mail_close(stream);
995 else
996 pine_mail_actually_close(stream);
998 *streamp = NULL;
1000 if(do_the_broach){
1001 ps_global->mail_stream = NULL; /* already closed above */
1004 if(mbox && !pine_mail_delete(NULL, mbox))
1005 q_status_message1(SM_ORDER|SM_DING, 3, 3,
1006 /* TRANSLATORS: Arg is a mailbox name */
1007 _("Can't delete %s"), mbox);
1009 if(mbox)
1010 fs_give((void **) &mbox);
1014 return(!problem);
1018 /*----------------------------------------------------------------------
1019 Parse the given header text for any given fields
1021 Args: text -- Text to parse for fcc and attachments refs
1022 fields -- array of field names to look for
1023 values -- array of pointer to save values to, returned NULL if
1024 fields isn't in text.
1026 This function simply looks for the given fields in the given header
1027 text string.
1028 NOTE: newlines are expected CRLF, and we'll ignore continuations
1029 ----*/
1030 void
1031 simple_header_parse(char *text, char **fields, char **values)
1033 int i, n;
1034 char *p, *t;
1036 for(i = 0; fields[i]; i++)
1037 values[i] = NULL; /* clear values array */
1039 /*---- Loop until the end of the header ----*/
1040 for(p = text; *p; ){
1041 for(i = 0; fields[i]; i++) /* find matching field? */
1042 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1043 for(p += n + 1; *p; p++){ /* find start of value */
1044 if(*p == '\015' && *(p+1) == '\012'
1045 && !isspace((unsigned char) *(p+2)))
1046 break;
1048 if(!isspace((unsigned char) *p))
1049 break; /* order here is key... */
1052 if(!values[i]){ /* if we haven't already */
1053 values[i] = fs_get(strlen(text) + 1);
1054 values[i][0] = '\0'; /* alloc space for it */
1057 if(*p && *p != '\015'){ /* non-blank value. */
1058 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1059 while(*p){ /* check for cont'n lines */
1060 if(*p == '\015' && *(p+1) == '\012'){
1061 if(isspace((unsigned char) *(p+2))){
1062 p += 2;
1063 continue;
1065 else
1066 break;
1069 *t++ = *p++;
1072 *t = '\0';
1075 break;
1078 /* Skip to end of line, what ever it was */
1079 for(; *p ; p++)
1080 if(*p == '\015' && *(p+1) == '\012'){
1081 p += 2;
1082 break;
1088 /*----------------------------------------------------------------------
1089 build a fresh REPLY_S from the given string (see pine_send for format)
1091 Args: s -- "X-Reply-UID" header value
1093 Returns: filled in REPLY_S or NULL on parse error
1094 ----*/
1095 REPLY_S *
1096 build_reply_uid(char *s)
1098 char *p, *prefix = NULL, *val, *seq, *mbox;
1099 int i, nseq, forwarded = 0;
1100 REPLY_S *reply = NULL;
1102 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1103 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1104 if(*s == '('){
1105 if(*(p = s + 1) == ')'){
1106 forwarded = 1;
1108 else{
1109 for(; isdigit(*p); p++)
1112 if(*p == ' '){
1113 *p++ = '\0';
1115 if((i = atoi(s+1)) && i < strlen(p)){
1116 prefix = p;
1117 *(p += i) = '\0';
1120 else
1121 return(NULL);
1124 if(*++p == '(' && *++p){
1125 for(seq = p; isdigit(*p); p++)
1128 if(*p == ' '){
1129 *p++ = '\0';
1130 for(val = p; isdigit(*p); p++)
1133 if(*p == ' '){
1134 *p++ = '\0';
1136 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1137 && (p = strchr(p, ')')) && *(mbox = ++p)){
1138 imapuid_t *uidl;
1140 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1141 for(i = 0; i < nseq; i++)
1142 if((p = strchr(seq,',')) != NULL){
1143 *p = '\0';
1144 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1145 seq = ++p;
1146 else
1147 break;
1149 else if((p = strchr(seq, ')')) != NULL){
1150 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1151 i++;
1153 break;
1156 if(i == nseq){
1157 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1158 memset(reply, 0, sizeof(REPLY_S));
1159 reply->uid = 1;
1160 reply->data.uid.validity = strtoul(val, NULL, 10);
1161 if(forwarded)
1162 reply->forwarded = 1;
1163 else
1164 reply->prefix = cpystr(prefix);
1166 reply->mailbox = cpystr(mbox);
1167 uidl[nseq] = 0;
1168 reply->data.uid.msgs = uidl;
1170 else
1171 fs_give((void **) &uidl);
1178 return(reply);
1183 * pine_new_env - allocate a new METAENV and fill it in.
1185 METAENV *
1186 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1188 int cnt, i, stdcnt;
1189 char *p;
1190 PINEFIELD *pfields, *pf, **sending_order;
1191 METAENV *header;
1193 header = (METAENV *) fs_get(sizeof(METAENV));
1195 /* how many fields are there? */
1196 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1199 stdcnt = cnt;
1201 for(pf = custom; pf; pf = pf->next)
1202 cnt++;
1204 /* temporary PINEFIELD array */
1205 i = (cnt + 1) * sizeof(PINEFIELD);
1206 pfields = (PINEFIELD *)fs_get((size_t) i);
1207 memset(pfields, 0, (size_t) i);
1209 i = (cnt + 1) * sizeof(PINEFIELD *);
1210 sending_order = (PINEFIELD **)fs_get((size_t) i);
1211 memset(sending_order, 0, (size_t) i);
1213 header->env = outgoing;
1214 header->local = pfields;
1215 header->custom = custom;
1216 header->sending_order = sending_order;
1218 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1219 # define NN 4
1220 #else
1221 # define NN 3
1222 #endif
1224 /* initialize pfield */
1225 pf = pfields;
1226 for(i=0; i < stdcnt; i++, pf++){
1228 pf->name = cpystr(pf_template[i].name);
1229 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global)){
1230 /* slide string over so it is Sender instead of X-X-Sender */
1231 for(p=pf->name+4; *p != '\0'; p++)
1232 *(p-4) = *p;
1233 *(p-4) = '\0';
1236 pf->type = pf_template[i].type;
1237 pf->canedit = pf_template[i].canedit;
1238 pf->rcptto = pf_template[i].rcptto;
1239 pf->writehdr = pf_template[i].writehdr;
1240 pf->localcopy = pf_template[i].localcopy;
1241 pf->extdata = NULL; /* unused */
1242 pf->next = pf + 1;
1244 switch(pf->type){
1245 case FreeText:
1246 switch(i){
1247 case N_AUTHRCVD:
1248 sending_order[0] = pf;
1249 break;
1251 case N_NEWS:
1252 pf->text = &outgoing->newsgroups;
1253 sending_order[1] = pf;
1254 break;
1256 case N_DATE:
1257 pf->text = (char **) &outgoing->date;
1258 sending_order[2] = pf;
1259 break;
1261 case N_INREPLY:
1262 pf->text = &outgoing->in_reply_to;
1263 sending_order[NN+9] = pf;
1264 break;
1266 case N_MSGID:
1267 pf->text = &outgoing->message_id;
1268 sending_order[NN+10] = pf;
1269 break;
1271 case N_REF: /* won't be used here */
1272 sending_order[NN+11] = pf;
1273 break;
1275 case N_PRIORITY:
1276 sending_order[NN+12] = pf;
1277 break;
1279 case N_USERAGENT:
1280 pf->text = &pf->textbuf;
1281 pf->textbuf = generate_user_agent();
1282 sending_order[NN+13] = pf;
1283 break;
1285 case N_POSTERR: /* won't be used here */
1286 sending_order[NN+14] = pf;
1287 break;
1289 case N_RPLUID: /* won't be used here */
1290 sending_order[NN+15] = pf;
1291 break;
1293 case N_RPLMBOX: /* won't be used here */
1294 sending_order[NN+16] = pf;
1295 break;
1297 case N_SMTP: /* won't be used here */
1298 sending_order[NN+17] = pf;
1299 break;
1301 case N_NNTP: /* won't be used here */
1302 sending_order[NN+18] = pf;
1303 break;
1305 case N_CURPOS: /* won't be used here */
1306 sending_order[NN+19] = pf;
1307 break;
1309 case N_OURREPLYTO: /* won't be used here */
1310 sending_order[NN+20] = pf;
1311 break;
1313 case N_OURHDRS: /* won't be used here */
1314 sending_order[NN+21] = pf;
1315 break;
1317 default:
1318 q_status_message1(SM_ORDER,3,3,
1319 "Internal error: 1)FreeText header %s", comatose(i));
1320 break;
1323 break;
1325 case Attachment:
1326 break;
1328 case Address:
1329 switch(i){
1330 case N_FROM:
1331 sending_order[3] = pf;
1332 pf->addr = &outgoing->from;
1333 break;
1335 case N_TO:
1336 sending_order[NN+2] = pf;
1337 pf->addr = &outgoing->to;
1338 if(tobufpp)
1339 (*tobufpp) = &pf->scratch;
1341 break;
1343 case N_CC:
1344 sending_order[NN+3] = pf;
1345 pf->addr = &outgoing->cc;
1346 break;
1348 case N_BCC:
1349 sending_order[NN+4] = pf;
1350 pf->addr = &outgoing->bcc;
1351 break;
1353 case N_REPLYTO:
1354 sending_order[NN+1] = pf;
1355 pf->addr = &outgoing->reply_to;
1356 break;
1358 case N_LCC: /* won't be used here */
1359 sending_order[NN+7] = pf;
1360 break;
1362 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1363 case N_SENDER:
1364 sending_order[4] = pf;
1365 pf->addr = &outgoing->sender;
1366 break;
1367 #endif
1369 case N_NOBODY: /* won't be used here */
1370 sending_order[NN+5] = pf;
1371 break;
1373 default:
1374 q_status_message1(SM_ORDER,3,3,
1375 "Internal error: Address header %s", comatose(i));
1376 break;
1378 break;
1380 case Fcc:
1381 sending_order[NN+8] = pf;
1382 pf->text = fccp;
1383 break;
1385 case Subject:
1386 sending_order[NN+6] = pf;
1387 pf->text = &outgoing->subject;
1388 break;
1390 default:
1391 q_status_message1(SM_ORDER,3,3,
1392 "Unknown header type %d in pine_new_send", (void *)pf->type);
1393 break;
1397 if(((--pf)->next = custom) != NULL){
1398 i--;
1401 * NOTE: "i" is assumed to now index first custom field in sending
1402 * order.
1404 for(pf = pf->next; pf && pf->name; pf = pf->next){
1405 if(pf->standard)
1406 continue;
1408 pf->canedit = 1;
1409 pf->rcptto = 0;
1410 pf->writehdr = 1;
1411 pf->localcopy = 1;
1413 switch(pf->type){
1414 case Address:
1415 if(pf->addr){ /* better be set */
1416 char *addr = NULL;
1417 BuildTo bldto;
1419 bldto.type = Str;
1420 bldto.arg.str = pf->textbuf;
1422 sending_order[i++] = pf;
1423 /* change default text into an ADDRESS */
1424 /* strip quotes around whole default */
1425 removing_trailing_white_space(pf->textbuf);
1426 (void)removing_double_quotes(pf->textbuf);
1427 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1428 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1429 fs_give((void **)&addr);
1430 if(pf->textbuf)
1431 fs_give((void **)&pf->textbuf);
1434 break;
1436 case FreeText:
1437 sending_order[i++] = pf;
1438 pf->text = &pf->textbuf;
1439 break;
1441 default:
1442 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1443 (void *)pf->type);
1444 break;
1450 return(header);
1454 void
1455 pine_free_env(METAENV **menv)
1457 int cnt;
1460 if((*menv)->local){
1461 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1464 for(; cnt >= 0; cnt--){
1465 if((*menv)->local[cnt].textbuf)
1466 fs_give((void **) &(*menv)->local[cnt].textbuf);
1468 fs_give((void **) &(*menv)->local[cnt].name);
1471 fs_give((void **) &(*menv)->local);
1474 if((*menv)->sending_order)
1475 fs_give((void **) &(*menv)->sending_order);
1477 fs_give((void **) menv);
1481 /*----------------------------------------------------------------------
1482 Check for addresses the user is not permitted to send to, or probably
1483 doesn't want to send to
1485 Returns: 0 if OK
1486 1 if there are only empty groups
1487 -1 if the message shouldn't be sent
1489 Queues a message indicating what happened
1490 ---*/
1492 check_addresses(METAENV *header)
1494 PINEFIELD *pf;
1495 ADDRESS *a;
1496 int send_daemon = 0, rv = CA_EMPTY;
1498 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1499 for(pf = header->local; pf && pf->name; pf = pf->next)
1500 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1501 for(a = *pf->addr; a != NULL; a = a->next){
1502 if(a->host && (a->host[0] == '.'
1503 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1504 && a->host[0] == '@'))){
1505 q_status_message2(SM_ORDER, 4, 7,
1506 /* TRANSLATORS: First arg is the address we can't
1507 send to, second arg is "not in addressbook". */
1508 _("Can't send to address %s: %s"),
1509 a->mailbox,
1510 (a->host[0] == '.')
1511 ? a->host
1512 : _("not in addressbook"));
1513 return(CA_BAD);
1515 else if(ps_global->restricted
1516 && !address_is_us(*pf->addr, ps_global)){
1517 q_status_message(SM_ORDER, 3, 3,
1518 "Restricted demo version of Alpine. You may only send mail to yourself");
1519 return(CA_BAD);
1521 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1522 send_daemon = 1;
1523 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1525 else if(a->mailbox && a->host){
1526 rv = CA_OK;
1530 return(rv);
1535 * If this isn't general enough we can modify it. The value passed in
1536 * is expected to be one of the desc settings from the priorities array,
1537 * like "High". The header value is X-Priority: 2 (High)
1538 * or something similar. If value doesn't match any of the values then
1539 * the actual value is used instead.
1541 PINEFIELD *
1542 set_priority_header(METAENV *header, char *value)
1544 PINEFIELD *pf;
1546 for(pf = header->local; pf && pf->name; pf = pf->next)
1547 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1548 break;
1550 if(pf){
1551 if(pf->textbuf)
1552 fs_give((void **) &pf->textbuf);
1554 if(value){
1555 PRIORITY_S *p;
1557 for(p = priorities; p && p->desc; p++)
1558 if(!strcmp(p->desc, value))
1559 break;
1561 if(p && p->desc){
1562 char buf[100];
1564 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1565 pf->textbuf = cpystr(buf);
1567 else
1568 pf->textbuf = cpystr(value);
1571 return pf;
1575 /*----------------------------------------------------------------------
1576 Set answered flags for messages specified by reply structure
1578 Args: reply --
1580 Returns: with appropriate flags set and index cache entries suitably tweeked
1581 ----*/
1582 void
1583 update_answered_flags(REPLY_S *reply)
1585 char *seq = NULL, *p;
1586 long i, ourstream = 0, we_cancel = 0;
1587 MAILSTREAM *stream = NULL;
1589 /* nothing to flip in a pseudo reply */
1590 if(reply && (reply->msgno || reply->uid)){
1591 int j;
1592 MAILSTREAM *m;
1595 * If an established stream will do, use it, else
1596 * build one unless we have an array of msgno's...
1598 * I was just mimicking what was already here. I don't really
1599 * understand why we use strcmp instead of same_stream_and_mailbox().
1600 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1601 * Hubert 2003-07-09
1603 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1604 m = ps_global->s_pool.streams[j];
1605 if(m && reply->mailbox && m->mailbox
1606 && !strcmp(reply->mailbox, m->mailbox))
1607 stream = m;
1610 if(!stream && reply->msgno)
1611 return;
1614 * This is here only for people who ran pine4.42 and are
1615 * processing postponed mail from 4.42 now. Pine4.42 saved the
1616 * original mailbox name in the canonical name's position in
1617 * the postponed-msgs folder so it won't match the canonical
1618 * name from the stream.
1620 if(!stream && (!reply->origmbox ||
1621 (reply->mailbox &&
1622 !strcmp(reply->origmbox, reply->mailbox))))
1623 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1625 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1626 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1627 if(!stream){
1628 if((stream = pine_mail_open(NULL,
1629 reply->origmbox ? reply->origmbox
1630 : reply->mailbox,
1631 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1632 NULL)) != NULL){
1633 ourstream++;
1635 else{
1636 if(we_cancel)
1637 cancel_busy_cue(0);
1639 return;
1643 if(stream->uid_validity == reply->data.uid.validity){
1644 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1645 if(i){
1646 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1647 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1650 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1651 SIZEOF_20KBUF-(p-tmp_20k_buf));
1652 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1655 if(reply->forwarded){
1657 * $Forwarded is a regular keyword so we only try to
1658 * set it if the stream allows keywords.
1659 * We could mess up if the stream has keywords but just
1660 * isn't allowing anymore and $Forwarded already exists,
1661 * but what are the odds?
1663 if(stream && stream->kwd_create)
1664 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1665 FORWARDED_FLAG,
1666 ST_SET | ((reply->uid) ? ST_UID : 0L));
1668 else
1669 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1670 "\\ANSWERED",
1671 ST_SET | ((reply->uid) ? ST_UID : 0L));
1673 if(seq)
1674 fs_give((void **)&seq);
1677 if(ourstream)
1678 pine_mail_close(stream); /* clean up dangling stream */
1680 if(we_cancel)
1681 cancel_busy_cue(0);
1687 * phone_home_from - make phone home request's from address IMpersonal.
1688 * Doesn't include user's personal name.
1690 ADDRESS *
1691 phone_home_from(void)
1693 ADDRESS *addr = mail_newaddr();
1694 char tmp[64];
1696 /* garble up mailbox name */
1697 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1698 tmp[sizeof(tmp)-1] = '\0';
1699 addr->mailbox = cpystr(tmp);
1700 addr->host = cpystr(ps_global->maildomain);
1701 return(addr);
1706 * one-way-hash a username into an 8-digit decimal number
1708 * Corey Satten, corey@cac.washington.edu, 7/15/98
1710 unsigned int
1711 phone_home_hash(char *s)
1713 unsigned int h;
1715 for (h=0; *s; ++s) {
1716 if (h & 1)
1717 h = (h>>1) | (PH_MAXHASH/2);
1718 else
1719 h = (h>>1);
1721 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1724 return (h);
1728 /*----------------------------------------------------------------------
1729 Call the mailer, SMTP, sendmail or whatever
1731 Args: header -- full header (envelope and local parts) of message to send
1732 body -- The full body of the message including text
1733 alt_smtp_servers --
1734 verbosefile -- non-null means caller wants verbose interaction and the resulting
1735 output file name to be returned
1737 Returns: -1 if failed, 1 if succeeded
1738 ----*/
1740 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1741 int flags, void (*bigresult_f)(char *, int),
1742 void (*pipecb_f)(PIPE_S *, int, void *))
1744 char error_buf[200], *error_mess = NULL, *postcmd;
1745 ADDRESS *a;
1746 ENVELOPE *fake_env = NULL;
1747 int addr_error_count, we_cancel = 0;
1748 long smtp_opts = 0L;
1749 char *verbose_file = NULL;
1750 BODY *bp = NULL;
1751 PINEFIELD *pf;
1752 BODY *origBody = body;
1754 dprint((4, "Sending mail...\n"));
1756 /* Check for any recipients */
1757 for(pf = header->local; pf && pf->name; pf = pf->next)
1758 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1759 break;
1761 if(!pf){
1762 q_status_message(SM_ORDER,3,3,
1763 _("Can't send message. No recipients specified!"));
1764 return(0);
1767 #ifdef SMIME
1768 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1769 int result;
1771 STORE_S *so = lmc.so;
1772 lmc.so = NULL;
1774 result = 1;
1776 if(ps_global->smime->do_sign){
1777 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1778 result = sign_outgoing_message(header, &body, 0, &bp);
1781 /* need to free new body from encrypt if sign fails? */
1782 if(result && ps_global->smime->do_encrypt)
1783 result = encrypt_outgoing_message(header, &body);
1785 lmc.so = so;
1787 if(!result)
1788 return 0;
1790 #endif
1792 /* set up counts and such to keep track sent percentage */
1793 send_bytes_sent = 0;
1794 gf_filter_init(); /* zero piped byte count, 'n */
1795 send_bytes_to_send = send_body_size(body); /* count body bytes */
1796 ps_global->c_client_error[0] = error_buf[0] = '\0';
1797 we_cancel = busy_cue(_("Sending mail"),
1798 send_bytes_to_send ? sent_percent : NULL, 0);
1800 #ifndef _WINDOWS
1802 /* try posting via local "<mta> <-t>" if specified */
1803 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1804 if(error_buf[0])
1805 error_mess = error_buf;
1807 goto done;
1810 #endif
1813 * If the user's asked for it, and we find that the first text
1814 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1816 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1817 smtp_opts |= SOP_8BITMIME;
1819 #ifdef DEBUG
1820 #ifndef DEBUGJOURNAL
1821 if(debug > 5 || (flags & CM_VERBOSE))
1822 #endif
1823 smtp_opts |= SOP_DEBUG;
1824 #endif
1826 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1827 smtp_opts |= SOP_DSN;
1828 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1829 if(flags & CM_DSN_DELAY)
1830 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1831 if(flags & CM_DSN_SUCCESS)
1832 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1835 * If it isn't Never, then we're always going to let them
1836 * know about failures. This means we don't allow for the
1837 * possibility of setting delay or success without failure.
1839 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1841 if(flags & CM_DSN_FULL)
1842 smtp_opts |= SOP_DSN_RETURN_FULL;
1848 * Set global header pointer so post_rfc822_output can get at it when
1849 * it's called back from c-client's sending routine...
1851 send_header = header;
1854 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1855 * The purpose is to give smtp_mail the list for SMTP RCPT when
1856 * there are recipients in pine's METAENV that are outside c-client's
1857 * envelope.
1859 * NOTE: If there aren't any, don't bother. Dealt with it below.
1861 for(pf = header->local; pf && pf->name; pf = pf->next)
1862 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1863 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1864 || *pf->addr == header->env->bcc))
1865 break;
1867 if(pf && pf->name){
1868 ADDRESS **tail;
1870 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1871 memset(fake_env, 0, sizeof(ENVELOPE));
1872 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1873 tail = &(fake_env->to);
1874 for(pf = header->local; pf && pf->name; pf = pf->next)
1875 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1876 *tail = rfc822_cpy_adr(*pf->addr);
1877 while(*tail)
1878 tail = &((*tail)->next);
1883 * Install our rfc822 output routine
1885 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1886 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1889 * Allow for verbose posting
1891 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1892 (void *) pine_smtp_verbose_out);
1895 * We do this because we want mm_log to put the error message into
1896 * c_client_error instead of showing it itself.
1898 ps_global->noshow_error = 1;
1901 * OK, who posts what? We tried an mta_handoff above, but there
1902 * was either none specified or we decided not to use it. So,
1903 * if there's an smtp-server defined anywhere,
1905 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1906 /*---------- SMTP ----------*/
1907 dprint((4, "call_mailer: via TCP (%s)\n",
1908 alt_smtp_servers[0]));
1909 TIME_STAMP("smtp-open start (tcp)", 1);
1910 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1912 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1913 && ps_global->VAR_SMTP_SERVER[0][0]){
1914 /*---------- SMTP ----------*/
1915 dprint((4, "call_mailer: via TCP\n"));
1916 TIME_STAMP("smtp-open start (tcp)", 1);
1917 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1919 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1920 char *cmdlist[2];
1922 /*----- Send via LOCAL SMTP agent ------*/
1923 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1925 TIME_STAMP("smtp-open start (pipe)", 1);
1926 fs_give((void **) &postcmd);
1927 cmdlist[0] = "localhost";
1928 cmdlist[1] = NULL;
1929 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1930 SMTPTCPPORT, smtp_opts);
1931 /* BUG: should provide separate stderr output! */
1934 ps_global->noshow_error = 0;
1936 TIME_STAMP("smtp open", 1);
1937 if(sending_stream){
1938 unsigned short save_encoding, added_encoding;
1940 dprint((1, "Opened SMTP server \"%s\"\n",
1941 net_host(sending_stream->netstream)
1942 ? net_host(sending_stream->netstream) : "?"));
1944 if(flags & CM_VERBOSE){
1945 TIME_STAMP("verbose start", 1);
1946 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1947 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1948 if(!smtp_verbose(sending_stream)){
1949 snprintf(error_mess = error_buf, sizeof(error_buf),
1950 "Mail not sent. VERBOSE mode error%s%.50s.",
1951 (sending_stream && sending_stream->reply)
1952 ? ": ": "",
1953 (sending_stream && sending_stream->reply)
1954 ? sending_stream->reply : "");
1955 error_buf[sizeof(error_buf)-1] = '\0';
1958 else{
1959 our_unlink(verbose_file);
1960 strncpy(error_mess = error_buf,
1961 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1962 error_buf[sizeof(error_buf)-1] = '\0';
1965 else{
1966 strncpy(error_mess = error_buf,
1967 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1968 error_buf[sizeof(error_buf)-1] = '\0';
1971 TIME_STAMP("verbose end", 1);
1975 * Before we actually send data, see if we have to protect
1976 * the first text body part from getting encoded. We protect
1977 * it from getting encoded in "pine_rfc822_output_body" by
1978 * temporarily inventing a synonym for ENC8BIT...
1979 * This works like so:
1980 * Suppose bp->encoding is set to ENC8BIT.
1981 * We change that here to some unused value (added_encoding) and
1982 * set body_encodings[added_encoding] to "8BIT".
1983 * Then post_rfc822_output is called which calls
1984 * pine_rfc822_output_body. Inside that routine
1985 * pine_write_body_header writes out the encoding for the
1986 * part. Normally it would see encoding == ENC8BIT and it would
1987 * change that to QUOTED-PRINTABLE, but since encoding has been
1988 * set to added_encoding it uses body_encodings[added_encoding]
1989 * which is "8BIT" instead. Then the actual body is written by
1990 * pine_write_body_header which does not do the gf_8bit_qp
1991 * filtering because encoding != ENC8BIT (instead it's equal
1992 * to added_encoding).
1994 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1995 && sending_stream->protocol.esmtp.eightbit.want){
1996 int i;
1998 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
2001 if(i > ENCMAX){ /* no empty encoding slots! */
2002 bp = NULL;
2004 else {
2005 added_encoding = i;
2006 body_encodings[added_encoding] = body_encodings[ENC8BIT];
2007 save_encoding = bp->encoding;
2008 bp->encoding = added_encoding;
2009 #ifdef SMIME
2010 if(ps_global->smime && ps_global->smime->do_sign
2011 && body->nested.part->next
2012 && body->nested.part->next->body.contents.text.data
2013 && body->nested.part->next->body.mime.text.data){
2014 STORE_S *so;
2016 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
2017 so_give(&so);
2018 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
2019 body->nested.part->next->body.mime.text.data = NULL;
2021 #endif /* SMIME */
2025 if(sending_stream->protocol.esmtp.ok
2026 && sending_stream->protocol.esmtp.dsn.want
2027 && !sending_stream->protocol.esmtp.dsn.ok)
2028 q_status_message(SM_ORDER,3,3,
2029 _("Delivery Status Notification not available from this server."));
2031 TIME_STAMP("smtp start", 1);
2032 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2033 fake_env ? fake_env : header->env, body)){
2035 snprintf(error_buf, sizeof(error_buf),
2036 _("Mail not sent. Sending error%s%s"),
2037 (sending_stream && sending_stream->reply) ? ": ": ".",
2038 (sending_stream && sending_stream->reply)
2039 ? sending_stream->reply : "");
2040 error_buf[sizeof(error_buf)-1] = '\0';
2041 dprint((1, error_buf));
2042 addr_error_count = 0;
2043 if(fake_env){
2044 for(a = fake_env->to; a != NULL; a = a->next)
2045 if(a->error != NULL){
2046 if(addr_error_count++ < MAX_ADDR_ERROR){
2049 * Too complicated to figure out which header line
2050 * has the error in the fake_env case, so just
2051 * leave cursor at default.
2055 if(error_mess) /* previous error? */
2056 q_status_message(SM_ORDER, 4, 7, error_mess);
2058 error_mess = tidy_smtp_mess(a->error,
2059 _("Mail not sent: %.80s"),
2060 error_buf, sizeof(error_buf));
2063 dprint((1, "Send Error: \"%s\"\n",
2064 a->error));
2067 else{
2068 for(pf = header->local; pf && pf->name; pf = pf->next)
2069 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2070 for(a = *pf->addr; a != NULL; a = a->next)
2071 if(a->error != NULL){
2072 if(addr_error_count++ < MAX_ADDR_ERROR){
2074 if(error_mess) /* previous error? */
2075 q_status_message(SM_ORDER, 4, 7, error_mess);
2077 error_mess = tidy_smtp_mess(a->error,
2078 _("Mail not sent: %.80s"),
2079 error_buf, sizeof(error_buf));
2082 dprint((1, "Send Error: \"%s\"\n",
2083 a->error));
2087 if(!error_mess)
2088 error_mess = error_buf;
2091 /* repair modified "body_encodings" array? */
2092 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2093 && sending_stream->protocol.esmtp.eightbit.want){
2094 body_encodings[added_encoding] = NULL;
2095 bp->encoding = save_encoding;
2098 TIME_STAMP("smtp closing", 1);
2099 smtp_close(sending_stream);
2100 sending_stream = NULL;
2101 TIME_STAMP("smtp done", 1);
2103 else if(!error_mess){
2104 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2105 ps_global->c_client_error[0] ? ": " : "",
2106 ps_global->c_client_error);
2107 error_buf[sizeof(error_buf)-1] = '\0';
2110 if(verbose_file){
2111 if(verbose_send_output){
2112 TIME_STAMP("verbose start", 1);
2113 fclose(verbose_send_output);
2114 verbose_send_output = NULL;
2115 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2117 if(bigresult_f)
2118 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2120 TIME_STAMP("verbose end", 1);
2123 fs_give((void **)&verbose_file);
2127 * Restore original 822 emitter...
2129 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2131 if(fake_env)
2132 mail_free_envelope(&fake_env);
2134 done:
2136 #ifdef SMIME
2137 /* Free replacement encrypted body */
2138 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2140 if(body->type == TYPEMULTIPART){
2141 /* Just get rid of first part, it's actually origBody */
2142 void *x = body->nested.part;
2144 body->nested.part = body->nested.part->next;
2146 fs_give(&x);
2149 pine_free_body(&body);
2151 #endif
2153 if(we_cancel)
2154 cancel_busy_cue(0);
2156 TIME_STAMP("call_mailer done", 1);
2157 /*-------- Did message make it ? ----------*/
2158 if(error_mess){
2159 /*---- Error sending mail -----*/
2160 if(lmc.so && !lmc.all_written)
2161 so_give(&lmc.so);
2163 if(error_mess){
2164 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2165 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2168 return(-1);
2170 else{
2171 lmc.all_written = 1;
2172 return(1);
2178 * write_postponed - exported method to write the given message
2179 * to the postponed folder
2182 write_postponed(METAENV *header, struct mail_bodystruct *body)
2184 char **pp, *folder;
2185 int rv = 0, sz;
2186 CONTEXT_S *fcc_cntxt = NULL;
2187 PINEFIELD *pf;
2188 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2190 if(!ps_global->VAR_POSTPONED_FOLDER
2191 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2192 q_status_message(SM_ORDER | SM_DING, 3, 3,
2193 _("No postponed file defined"));
2194 return(-1);
2197 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2199 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2201 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2203 if(lmc.so){
2204 /* BUG: writem sufficient ? */
2205 for(pf = header->local; pf && pf->name; pf = pf->next)
2206 for(pp = writem; *pp; pp++)
2207 if(!strucmp(pf->name, *pp)){
2208 pf->localcopy = 1;
2209 pf->writehdr = 1;
2210 break;
2214 * Work around c-client reply-to bug. C-client will
2215 * return a reply_to in an envelope even if there is
2216 * no reply-to header field. We want to note here whether
2217 * the reply-to is real or not.
2219 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2220 for(pf = header->local; pf; pf = pf->next)
2221 if(!strcmp(pf->name, "Reply-To")){
2222 pf->writehdr = 1;
2223 pf->localcopy = 1;
2224 if(header->env->reply_to)
2225 pf->textbuf = cpystr("Full");
2226 else
2227 pf->textbuf = cpystr("Empty");
2231 * Write the list of custom headers to the
2232 * X-Our-Headers header so that we can recover the
2233 * list in redraft.
2235 sz = 0;
2236 for(pf = header->custom; pf && pf->name; pf = pf->next)
2237 sz += strlen(pf->name) + 1;
2239 if(sz){
2240 int i;
2241 char *pstart, *pend;
2243 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2246 pf->writehdr = 1;
2247 pf->localcopy = 1;
2248 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2249 pf->text = &pf->textbuf;
2250 pf->textbuf[sz] = '\0'; /* tie off overflow */
2251 /* note: "pf" overloaded */
2252 for(pf = header->custom; pf && pf->name; pf = pf->next){
2253 int r = sz - (pend - pstart); /* remaining buffer */
2255 if(r > 0 && r != sz){
2256 r--;
2257 *pend++ = ',';
2260 sstrncpy(&pend, pf->name, r);
2264 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2265 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2266 rv = -1;
2268 so_give(&lmc.so);
2270 else {
2271 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2272 "Can't allocate internal storage: %s ",
2273 error_description(errno));
2274 rv = -1;
2277 fs_give((void **) &folder);
2278 return(rv);
2283 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2285 if(fcc && *fcc){
2286 lmc.all_written = lmc.text_written = 0;
2287 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2288 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2290 else
2291 lmc.so = NULL;
2293 return(TRUE);
2298 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2300 int rv = TRUE;
2302 if(lmc.so){
2303 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2304 char label[50];
2306 strncpy(label, "Fcc", sizeof(label));
2307 label[sizeof(label)-1] = '\0';
2308 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2309 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2310 label[sizeof(label)-1] = '\0';
2313 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2315 else{
2316 rv = FALSE;
2319 so_give(&lmc.so);
2322 return(rv);
2326 /*----------------------------------------------------------------------
2327 Checks to make sure the fcc is available and can be opened
2329 Args: fcc -- the name of the fcc to create. It can't be NULL.
2330 fcc_cntxt -- Returns the context the fcc is in.
2331 force -- supress user option prompt
2333 Returns allocated storage object on success, NULL on failure
2334 ----*/
2335 STORE_S *
2336 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2338 int exists, ok = 0;
2340 ps_global->mm_log_error = 0;
2343 * check for fcc's existance...
2345 TIME_STAMP("open_fcc start", 1);
2346 if(!is_absolute_path(fcc) && context_isambig(fcc)
2347 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2348 int flip_dot = 0;
2351 * Don't want to preclude a user from Fcc'ing a .name'd folder
2353 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2354 flip_dot = 1;
2355 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2359 * We only want to set the "context" if fcc is an ambiguous
2360 * name. Otherwise, our "relativeness" rules for contexts
2361 * (implemented in context.c) might cause the name to be
2362 * interpreted in the wrong context...
2364 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2365 *fcc_cntxt = ps_global->context_list;
2367 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2368 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2369 if(force
2370 || (pith_opt_save_create_prompt
2371 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2373 ps_global->noshow_error = 1;
2375 if(context_create(*fcc_cntxt, NULL, fcc))
2376 ok++;
2378 ps_global->noshow_error = 0;
2380 else
2381 ok--; /* declined! */
2383 else
2384 ok++; /* found! */
2386 if(flip_dot)
2387 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2389 free_folder_list(*fcc_cntxt);
2391 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2392 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2393 ok++;
2395 else{
2396 if(force
2397 || (pith_opt_save_create_prompt
2398 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2400 ps_global->mm_log_error = 0;
2401 ps_global->noshow_error = 1;
2403 ok = pine_mail_create(NULL, fcc) != 0L;
2405 ps_global->noshow_error = 0;
2407 else
2408 ok--; /* declined! */
2412 TIME_STAMP("open_fcc done.", 1);
2413 if(ok > 0){
2414 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2416 else{
2417 int l1, l2, l3, wid, w;
2418 char *errstr, tmp[MAILTMPLEN];
2419 char *s1, *s2;
2421 if(ok == 0){
2422 if(ps_global->mm_log_error){
2423 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2424 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2426 l1 = strlen(s1);
2427 l2 = strlen(s2);
2428 l3 = strlen(ps_global->c_client_error);
2429 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2430 ? ps_global->ttyo->screen_cols : 80;
2431 w = wid - l1 - l2 - 5;
2433 snprintf(errstr = tmp, sizeof(tmp),
2434 "%.99s\"%.*s%.99s\".%.99s",
2436 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2437 ps_global->c_client_error,
2438 (l3 > w) ? "..." : "",
2439 s2);
2440 tmp[sizeof(tmp)-1] = '\0';
2443 else
2444 errstr = _("Fcc creation error. Message NOT sent or copied.");
2446 else
2447 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2449 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2452 return(NULL);
2456 /*----------------------------------------------------------------------
2457 mail_append() the fcc accumulated in temp_storage to proper destination
2459 Args: fcc -- name of folder
2460 fcc_cntxt -- context for folder
2461 temp_storage -- String of file where Fcc has been accumulated
2463 This copies the string of file to the actual folder, which might be IMAP
2464 or a disk folder. The temp_storage is freed after it is written.
2465 An error message is produced if this fails.
2466 ----*/
2468 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2469 MAILSTREAM *stream, char *label, char *flags)
2471 STRING msg;
2472 CONTEXT_S *cntxt;
2473 int we_cancel = 0;
2475 if(!tmp_storage)
2476 return(0);
2478 TIME_STAMP("write_fcc start.", 1);
2479 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2480 if(label && *label){
2481 char msg_buf[80];
2483 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2484 msg_buf[sizeof(msg_buf)-1] = '\0';
2485 strncat(msg_buf, label, sizeof(msg_buf)-10);
2486 we_cancel = busy_cue(msg_buf, NULL, 0);
2488 else
2489 we_cancel = busy_cue(NULL, NULL, 1);
2491 so_seek(tmp_storage, 0L, 0);
2494 * Before changing this note that these lines depend on the
2495 * definition of FCC_SOURCE.
2497 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2498 strlen((char *)so_text(tmp_storage)));
2500 cntxt = fcc_cntxt;
2502 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2503 cancel_busy_cue(-1);
2504 we_cancel = 0;
2506 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2507 "Write to \"%s\" FAILED!!!", fcc);
2508 dprint((1, "ERROR appending %s in \"%s\"",
2509 fcc ? fcc : "?",
2510 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2511 return(0);
2514 if(we_cancel)
2515 cancel_busy_cue(label ? 0 : -1);
2517 dprint((4, "done.\n"));
2518 TIME_STAMP("write_fcc done.", 1);
2519 return(1);
2524 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2525 * contained.
2527 BODY *
2528 first_text_8bit(struct mail_bodystruct *body)
2530 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2531 body = &body->nested.part->body;
2533 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2534 ? body : NULL);
2539 * Build and return the "From:" address for outbound messages from
2540 * global data...
2542 ADDRESS *
2543 generate_from(void)
2545 ADDRESS *addr = mail_newaddr();
2546 if(ps_global->VAR_PERSONAL_NAME){
2547 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2548 removing_leading_and_trailing_white_space(addr->personal);
2549 if(addr->personal[0] == '\0')
2550 fs_give((void **)&addr->personal);
2553 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2554 addr->host = cpystr(ps_global->maildomain);
2555 removing_leading_and_trailing_white_space(addr->mailbox);
2556 removing_leading_and_trailing_white_space(addr->host);
2557 return(addr);
2562 * set_mime_type_by_grope - sniff the given storage object to determine its
2563 * type, subtype, encoding, and charset
2565 * "Type" and "encoding" must be set before calling this routine.
2566 * If "type" is set to something other than TYPEOTHER on entry,
2567 * then that is the "type" we wish to use. Same for "encoding"
2568 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2569 * figure them out here. If "type" is already set, we also
2570 * leave subtype alone. If not, we figure out subtype here.
2571 * There is a chance that we will upgrade encoding to a "higher"
2572 * level. For example, if it comes in as 7BIT we may change
2573 * that to 8BIT if we find a From_ we want to escape.
2574 * We may also set the charset attribute if the type is TEXT.
2576 * NOTE: this is rather inefficient if the store object is a CharStar
2577 * but the win is all types are handled the same
2579 void
2580 set_mime_type_by_grope(struct mail_bodystruct *body)
2582 #define RBUFSZ (8193)
2583 unsigned char *buf, *p, *bol;
2584 register size_t n;
2585 long max_line = 0L,
2586 eight_bit_chars = 0L,
2587 line_so_far = 0L,
2588 len = 0L;
2589 STORE_S *so = (STORE_S *)body->contents.text.data;
2590 unsigned short new_encoding = ENCOTHER;
2591 int we_cancel = 0;
2592 #ifdef ENCODE_FROMS
2593 short froms = 0, dots = 0,
2594 bmap = 0x1, dmap = 0x1;
2595 #endif
2597 we_cancel = busy_cue(NULL, NULL, 1);
2599 buf = (unsigned char *)fs_get(RBUFSZ);
2600 so_seek(so, 0L, 0);
2602 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2605 buf[n] = '\0';
2607 if(n){ /* check first few bytes to look for magic numbers */
2608 if(body->type == TYPEOTHER){
2609 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2610 body->type = TYPEIMAGE;
2611 body->subtype = cpystr("GIF");
2613 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2614 && buf[2] == 0xFF && buf[3] == 0xE0
2615 && !strncmp((char *)&buf[6], "JFIF", 4)){
2616 body->type = TYPEIMAGE;
2617 body->subtype = cpystr("JPEG");
2619 else if((buf[0] == 'M' && buf[1] == 'M')
2620 || (buf[0] == 'I' && buf[1] == 'I')){
2621 body->type = TYPEIMAGE;
2622 body->subtype = cpystr("TIFF");
2624 else if((buf[0] == '%' && buf[1] == '!')
2625 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2626 body->type = TYPEAPPLICATION;
2627 body->subtype = cpystr("PostScript");
2629 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2630 body->type = TYPEAPPLICATION;
2631 body->subtype = cpystr("PDF");
2633 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2634 body->type = TYPEAUDIO;
2635 body->subtype = cpystr("Basic");
2637 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2638 && buf[2] == 0x16 && buf[3] == 0x00){
2639 body->type = TYPEAPPLICATION;
2640 body->subtype = cpystr("APPLEFILE");
2642 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2643 && buf[2] == 0x03 && buf[3] == 0x04){
2644 body->type = TYPEAPPLICATION;
2645 body->subtype = cpystr("ZIP");
2649 * if type was set above, but no encoding specified, go
2650 * ahead and make it BASE64...
2652 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2653 body->encoding = ENCBINARY;
2656 else{
2657 /* PROBLEM !!! */
2658 if(body->type == TYPEOTHER){
2659 body->type = TYPEAPPLICATION;
2660 body->subtype = cpystr("octet-stream");
2661 if(body->encoding == ENCOTHER)
2662 body->encoding = ENCBINARY;
2666 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2667 #if defined(DOS) || defined(OS2) /* for binary file detection */
2668 int lastchar = '\0';
2669 #define BREAKOUT 300 /* a value that a character can't be */
2670 #endif
2672 p = bol = buf;
2673 len = n;
2674 while (n--){
2675 /* Some people don't like quoted-printable caused by leading Froms */
2676 #ifdef ENCODE_FROMS
2677 Find_Froms(froms, dots, bmap, dmap, *p);
2678 #endif
2679 if(*p == '\n'){
2680 max_line = MAX(max_line, line_so_far + p - bol);
2681 bol = NULL; /* clear beginning of line */
2682 line_so_far = 0L; /* clear line count */
2683 #if defined(DOS) || defined(OS2)
2684 /* LF with no CR!! */
2685 if(lastchar != '\r') /* must be non-text data! */
2686 lastchar = BREAKOUT;
2687 #endif
2689 else if(*p & 0x80){
2690 eight_bit_chars++;
2692 else if(!*p){
2693 /* NULL found. Unless we're told otherwise, must be binary */
2694 if(body->type == TYPEOTHER){
2695 body->type = TYPEAPPLICATION;
2696 body->subtype = cpystr("octet-stream");
2700 * The "TYPETEXT" here handles the case that the NULL
2701 * comes from imported text generated by some external
2702 * editor that permits or inserts NULLS. Otherwise,
2703 * assume it's a binary segment...
2705 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2708 * Since we've already set encoding, count this as a
2709 * hi bit char and continue. The reason is that if this
2710 * is text, there may be a high percentage of encoded
2711 * characters, so base64 may get set below...
2713 if(body->type == TYPETEXT)
2714 eight_bit_chars++;
2715 else
2716 break;
2719 #if defined(DOS) || defined(OS2) /* for binary file detection */
2720 if(lastchar != BREAKOUT)
2721 lastchar = *p;
2722 #endif
2724 /* read another buffer in */
2725 if(n == 0){
2726 if(bol)
2727 line_so_far += p - bol;
2729 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2732 len += n;
2733 p = buf;
2735 else
2736 p++;
2739 * If there's no beginning-of-line pointer, then we must
2740 * have seen an end-of-line. Set bol to the start of the
2741 * new line...
2743 if(!bol)
2744 bol = p;
2746 #if defined(DOS) || defined(OS2) /* for binary file detection */
2747 /* either a lone \r or lone \n indicate binary file */
2748 if(lastchar == '\r' || lastchar == BREAKOUT){
2749 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2750 if(body->type == TYPEOTHER){
2751 body->type = TYPEAPPLICATION;
2752 body->subtype = cpystr("octet-stream");
2755 new_encoding = ENCBINARY;
2756 break;
2759 #endif
2763 /* stash away for later */
2764 so_attr(so, "maxline", long2string(max_line));
2766 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2768 * Since the type or encoding aren't set yet, fall thru a
2769 * series of tests to make sure an adequate type and
2770 * encoding are set...
2773 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2774 if(body->type == TYPEOTHER){
2776 * Since the types not set, then we didn't find a NULL.
2777 * If there's no NULL, then this is likely text. However,
2778 * since we can't be *completely* sure, we set it to
2779 * the generic type.
2781 body->type = TYPEAPPLICATION;
2782 body->subtype = cpystr("octet-stream");
2785 if(new_encoding != ENCBINARY)
2787 * As with NULL handling, if we're told it's text,
2788 * qp-encode it, else it gets base 64...
2790 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2793 if(eight_bit_chars == 0L){
2794 if(body->type == TYPEOTHER)
2795 body->type = TYPETEXT;
2797 if(new_encoding == ENCOTHER)
2798 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2800 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2802 * The 30% threshold is based on qp encoded readability
2803 * on non-MIME UA's.
2805 if(body->type == TYPEOTHER)
2806 body->type = TYPETEXT;
2808 if(new_encoding != ENCBINARY)
2809 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2811 else{
2812 if(body->type == TYPEOTHER){
2813 body->type = TYPEAPPLICATION;
2814 body->subtype = cpystr("octet-stream");
2818 * Apply maximal encoding regardless of previous
2819 * setting. This segment's either not text, or is
2820 * unlikely to be readable with > 30% of the
2821 * text encoded anyway, so we might as well save space...
2823 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2827 #ifdef ENCODE_FROMS
2828 /* If there were From_'s at the beginning of a line or standalone dots */
2829 if((froms || dots) && new_encoding != ENCBINARY)
2830 new_encoding = ENC8BIT;
2831 #endif
2833 /* Set the subtype */
2834 if(body->subtype == NULL)
2835 body->subtype = cpystr(rfc822_default_subtype(body->type));
2837 if(body->encoding == ENCOTHER)
2838 body->encoding = new_encoding;
2840 fs_give((void **)&buf);
2842 if(we_cancel)
2843 cancel_busy_cue(-1);
2848 * Call this to set the charset of an attachment we have
2849 * created. If the attachment contains any non-ascii characters
2850 * then we'll set the charset to the passed in charset, otherwise
2851 * we'll make it us-ascii.
2853 void
2854 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2856 unsigned char c;
2857 int can_be_ascii = 1;
2858 STORE_S *so = (STORE_S *)body->contents.text.data;
2859 int we_cancel = 0;
2861 if(!body || body->type != TYPETEXT)
2862 return;
2864 we_cancel = busy_cue(NULL, NULL, 1);
2866 so_seek(so, 0L, 0);
2868 while(can_be_ascii && so_readc(&c, so))
2869 if(!c || c & 0x80)
2870 can_be_ascii--;
2872 if(can_be_ascii)
2873 set_parameter(&body->parameter, "charset", "US-ASCII");
2874 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2875 set_parameter(&body->parameter, "charset", charset);
2876 else{
2878 * Else we don't know. There are non ascii characters but we either
2879 * don't have a charset to set it to or that charset is just us_ascii,
2880 * which is impossible. So we label it unknown. An alternative would
2881 * have been to strip the high bits instead and label it ascii.
2883 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2886 if(we_cancel)
2887 cancel_busy_cue(-1);
2892 * since encoding happens on the way out the door, this is basically
2893 * just needed to handle TYPEMULTIPART
2895 void
2896 pine_encode_body (struct mail_bodystruct *body)
2898 PART *part;
2900 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2901 if (body) switch (body->type) {
2902 char *freethis;
2904 case TYPEMULTIPART: /* multi-part */
2905 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2906 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2908 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2909 (long) getpid ());
2910 tmp[sizeof(tmp)-1] = '\0';
2911 set_parameter(&body->parameter, "boundary", tmp);
2914 if(freethis)
2915 fs_give((void **) &freethis);
2917 part = body->nested.part; /* encode body parts */
2918 do pine_encode_body (&part->body);
2919 while ((part = part->next) != NULL); /* until done */
2920 break;
2922 case TYPETEXT :
2924 * If the part is text we edited, then it is UTF-8.
2925 * The user may be asking us to send it as something else
2926 * or we may want to downconvert to a more-specific characterset.
2927 * Mark it for conversion here so the right MIME header's written.
2928 * Do conversion pine_rfc822_output_body.
2929 * Attachments are left as is.
2931 if(body->contents.text.data
2932 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2933 char *charset, *posting_charset, *lp;
2935 if(!((charset = parameter_val(body->parameter, "charset"))
2936 && !strucmp(charset, UNKNOWN_CHARSET))
2937 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2939 set_parameter(&body->parameter, "charset", posting_charset);
2942 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2943 * and doesn't use anything but ASCII characters.
2944 * Why is it not ENC7BIT already? Because when we set the encoding
2945 * in set_mime_type_by_grope we were groping through UTF-8 text
2946 * not 2022 text. Not only that, but we didn't know at that point
2947 * that it wouldn't stay UTF-8 when we sent it, which would require
2948 * encoding.
2950 if(!strucmp(posting_charset, "iso-2022-jp")
2951 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2952 && strlen(lp) < 4)
2953 body->encoding = ENC7BIT;
2956 if(charset)
2957 fs_give((void **)&charset);
2960 break;
2962 /* case MESSAGE: */ /* here for documentation */
2963 /* Encapsulated messages are always treated as text objects at this point.
2964 This means that you must replace body->contents.msg with
2965 body->contents.text, which probably involves copying
2966 body->contents.msg.text to body->contents.text */
2967 default: /* all else has some encoding */
2969 * but we'll delay encoding it until the message is on the way
2970 * into the mail slot...
2972 break;
2978 * pine_header_line - simple wrapper around c-client call to contain
2979 * repeated code, and to write fcc if required.
2982 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2983 int writehdr, int localcopy)
2985 int ret = 1;
2986 int flags;
2987 int big = 10000;
2988 char *value, *folded = NULL, *cs;
2989 char *converted;
2991 if(!text)
2992 return 1;
2994 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2996 if(converted){
2997 if(cs && !strucmp(cs, "us-ascii"))
2998 value = converted;
2999 else
3000 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
3001 (unsigned char *) converted, cs,
3002 encode_whole_header(field, header));
3004 if(value && value == converted){ /* no encoding was done, have to fold */
3005 int fold_by, len;
3006 char *actual_field;
3008 len = ((header && header->env && header->env->remail)
3009 ? strlen("ReSent-") : 0) +
3010 (field ? strlen(field) : 0) + 2;
3012 actual_field = (char *)fs_get((len+1) * sizeof(char));
3013 snprintf(actual_field, len+1, "%s%s: ",
3014 (header && header->env && header->env->remail) ? "ReSent-" : "",
3015 field ? field : "");
3016 actual_field[len] = '\0';
3019 * We were folding everything except message-id, but that wasn't
3020 * sufficient. Since 822 only allows folding where linear-white-space
3021 * is allowed we'd need a smarter folder than "fold" to do it. So,
3022 * instead of inventing that smarter folder (which would have to
3023 * know 822 syntax)
3025 * We could just alloc space and copy the actual_field followed by
3026 * the value into it, but since that's what fold does anyway we'll
3027 * waste some cpu time and use fold with a big fold parameter.
3029 * We upped the references folding from 75 to 256 because we were
3030 * encountering longer-than-75 message ids, and to break one line
3031 * in references is to break them all.
3033 * Also, some users are adding long text without spaces to the subject
3034 * line (e.g. URLs) so we up that number too. For the moment this is
3035 * going to 512 (a url that is about 6 lines long.)
3037 flags = FLD_CRLF;
3038 if(field && !strucmp("Subject", field)){
3039 fold_by = 75;
3040 flags |= FLD_NEXTSPC;
3042 else if(field && !strucmp("References", field))
3043 fold_by = 256;
3044 else
3045 fold_by = big;
3047 folded = fold(value, fold_by, big, actual_field, " ", flags);
3049 if(actual_field)
3050 fs_give((void **)&actual_field);
3052 else if(value){ /* encoding was done */
3053 RFC822BUFFER rbuf;
3054 size_t ll;
3057 * rfc1522_encode already inserted continuation lines and did
3058 * the necessary folding so we don't have to do it. Let
3059 * rfc822_header_line add the trailing crlf and the resent- if
3060 * necessary. The 20 could actually be a 12.
3062 ll = strlen(field) + strlen(value) + 20;
3063 folded = (char *) fs_get(ll * sizeof(char));
3064 *folded = '\0';
3065 rbuf.f = dummy_soutr;
3066 rbuf.s = NULL;
3067 rbuf.beg = folded;
3068 rbuf.cur = folded;
3069 rbuf.end = folded+ll-1;
3070 rfc822_output_header_line(&rbuf, field,
3071 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3072 *rbuf.cur = '\0';
3075 if(value && folded){
3076 if(writehdr && f)
3077 ret = (*f)(s, folded);
3079 if(ret && localcopy && lmc.so && !lmc.all_written)
3080 ret = so_puts(lmc.so, folded);
3083 if(folded)
3084 fs_give((void **)&folded);
3086 if(converted && converted != text)
3087 fs_give((void **) &converted);
3089 else
3090 ret = 0;
3092 return(ret);
3097 * Do appropriate encoding of text header lines.
3098 * For some field types (those that consist of 822 *text) we just encode
3099 * the whole thing. For structured fields we encode only within comments
3100 * if possible.
3102 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3103 * s -- Source string.
3104 * charset -- Charset to encode with.
3105 * encode_all -- If set, encode the whole string. If not, try to encode
3106 * only within comments if possible.
3108 * Returns S is returned if no encoding is done. D is returned if encoding
3109 * was needed.
3111 char *
3112 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3114 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3115 int in_comment = 0;
3117 if(!s)
3118 return((char *)s);
3120 if(dlen < SIZEOF_20KBUF)
3121 alpine_panic("bad call to encode_header_value");
3123 if(!encode_all){
3125 * We don't have to worry about keeping track of quoted-strings because
3126 * none of these fields which aren't addresses contain quoted-strings.
3127 * We do keep track of escaped parens inside of comments and comment
3128 * nesting.
3130 p = d+7000;
3131 for(q = (char *)s; *q; q++){
3132 switch(*q){
3133 case LPAREN:
3134 if(in_comment++ == 0)
3135 start_of_comment = q;
3137 break;
3139 case RPAREN:
3140 if(--in_comment == 0){
3141 /* encode the comment, excluding the outer parens */
3142 if(p-d < dlen-1)
3143 *p++ = LPAREN;
3145 *q = '\0';
3146 r = rfc1522_encode(d+14000, dlen-14000,
3147 (unsigned char *)start_of_comment+1,
3148 charset);
3149 if(r != start_of_comment+1)
3150 value = d+7000; /* some encoding was done */
3152 start_of_comment = NULL;
3153 if(r)
3154 sstrncpy(&p, r, dlen-1-(p-d));
3156 *q = RPAREN;
3157 if(p-d < dlen-1)
3158 *p++ = *q;
3160 else if(in_comment < 0){
3161 in_comment = 0;
3162 if(p-d < dlen-1)
3163 *p++ = *q;
3166 break;
3168 case BSLASH:
3169 if(!in_comment && *(q+1)){
3170 if(p-d < dlen-2){
3171 *p++ = *q++;
3172 *p++ = *q;
3176 break;
3178 default:
3179 if(!in_comment && p-d < dlen-1)
3180 *p++ = *q;
3182 break;
3186 if(value){
3187 /* Unterminated comment (wasn't really a comment) */
3188 if(start_of_comment)
3189 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3191 *p = '\0';
3196 * We have to check if there is anything that needs to be encoded that
3197 * wasn't in a comment. If there is, we'd better just start over and
3198 * encode the whole thing. So, if no encoding has been done within
3199 * comments, or if encoding is needed both within and outside of
3200 * comments, then we encode the whole thing. Otherwise, go with
3201 * the version that has only comments encoded.
3203 if(!value || rfc1522_encode(d, dlen,
3204 (unsigned char *)value, charset) != value)
3205 return(rfc1522_encode(d, dlen, s, charset));
3206 else{
3207 strncpy(d, value, dlen-1);
3208 d[dlen-1] = '\0';
3209 return(d);
3215 * pine_address_line - write a header field containing addresses,
3216 * one by one (so there's no buffer limit), and
3217 * wrapping where necessary.
3218 * Note: we use c-client functions to properly build the text string,
3219 * but have to screw around with pointers to fool c-client functions
3220 * into not blatting all the text into a single buffer. Yeah, I know.
3223 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3224 soutr_t f, void *s, int writehdr, int localcopy)
3226 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3227 size_t alloced = 0, sz;
3228 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3229 char *converted, *cs;
3230 ADDRESS *atmp;
3231 int i, count;
3232 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3233 RFC822BUFFER rbuf;
3234 static char comma[] = ", ";
3235 static char end_group[] = ";";
3236 #define no_comma (&comma[1])
3238 if(!alist) /* nothing in field! */
3239 return(1);
3241 if(!alist->host && alist->mailbox){ /* c-client group convention */
3242 in_group++;
3243 was_start_of_group++;
3244 /* encode mailbox of group */
3245 mtmp = alist->mailbox;
3246 if(mtmp){
3247 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3248 buftmp[sizeof(buftmp)-1] = '\0';
3249 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3250 if(converted){
3251 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3252 (unsigned char *) converted, cs));
3253 if(converted && converted != buftmp)
3254 fs_give((void **) &converted);
3256 else{
3257 failed++;
3258 goto bail_out;
3262 else
3263 mtmp = NULL;
3265 ptmp = alist->personal; /* remember personal name */
3266 /* make sure personal name is encoded */
3267 if(ptmp){
3268 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3269 buftmp[sizeof(buftmp)-1] = '\0';
3270 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3271 if(converted){
3272 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3273 (unsigned char *) converted, cs));
3274 if(converted && converted != buftmp)
3275 fs_give((void **) &converted);
3277 else{
3278 failed++;
3279 goto bail_out;
3283 atmp = alist->next;
3284 alist->next = NULL; /* digest only first address! */
3286 /* use automatic buffer unless it isn't big enough */
3287 if((alloced = est_size(alist)) > sizeof(tmp)){
3288 tmpptr = (char *)fs_get(alloced);
3289 sz = alloced;
3291 else{
3292 tmpptr = tmp;
3293 sz = sizeof(tmp);
3296 rbuf.f = dummy_soutr;
3297 rbuf.s = NULL;
3298 rbuf.beg = tmpptr;
3299 rbuf.cur = tmpptr;
3300 rbuf.end = tmpptr+sz-1;
3301 rfc822_output_address_line(&rbuf, field,
3302 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3303 *rbuf.cur = '\0';
3305 alist->next = atmp; /* restore pointer to next addr */
3307 if(alist->personal && alist->personal != ptmp)
3308 fs_give((void **) &alist->personal);
3310 alist->personal = ptmp; /* in case it changed, restore name */
3312 if(mtmp){
3313 if(alist->mailbox && alist->mailbox != mtmp)
3314 fs_give((void **) &alist->mailbox);
3316 alist->mailbox = mtmp;
3319 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3320 count -= 2;
3321 tmpptr[count] = '\0';
3325 * If there is no sending_stream and we are writing the Lcc header,
3326 * then we are piping it to sendmail -t which expects it to be a bcc,
3327 * not lcc.
3329 * When we write it to the fcc or postponed (the lmc.so),
3330 * we want it to be lcc, not bcc, so we put it back.
3332 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3333 fix_lcc = 1;
3335 if(writehdr && f && *tmpptr){
3336 if(fix_lcc)
3337 tmpptr[0] = 'b';
3339 failed = !(*f)(s, tmpptr);
3340 if(fix_lcc)
3341 tmpptr[0] = 'L';
3343 if(failed)
3344 goto bail_out;
3347 if(localcopy && lmc.so &&
3348 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3349 goto bail_out;
3351 for(alist = atmp; alist; alist = alist->next){
3352 delim = comma;
3353 /* account for c-client's representation of group names */
3354 if(in_group){
3355 if(!alist->host){ /* end of group */
3356 in_group = 0;
3357 was_start_of_group = 0;
3359 * Rfc822_write_address no longer writes out the end of group
3360 * unless the whole group address is passed to it, so we do
3361 * it ourselves.
3363 delim = end_group;
3365 else if(!localcopy || !lmc.so || lmc.all_written)
3366 continue;
3368 /* start of new group, print phrase below */
3369 else if(!alist->host && alist->mailbox){
3370 in_group++;
3371 was_start_of_group++;
3374 /* no comma before first address in group syntax */
3375 if(was_start_of_group && alist->host){
3376 delim = no_comma;
3377 was_start_of_group = 0;
3380 /* write delimiter */
3381 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3382 || (localcopy && lmc.so && !lmc.all_written
3383 && !so_puts(lmc.so, delim)))
3384 goto bail_out;
3386 ptmp = alist->personal; /* remember personal name */
3387 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3388 buftmp[sizeof(buftmp)-1] = '\0';
3389 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3390 if(converted){
3391 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3392 (unsigned char *) converted, cs));
3393 if(converted && converted != buftmp)
3394 fs_give((void **) &converted);
3396 else{
3397 failed++;
3398 goto bail_out;
3401 atmp = alist->next;
3402 alist->next = NULL; /* tie off linked list */
3403 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3404 alloced = i;
3405 sz = alloced;
3406 fs_resize((void **)&tmpptr, alloced);
3409 *tmpptr = '\0';
3410 /* make sure we don't write out group end with rfc822_write_address */
3411 if(alist->host || alist->mailbox){
3412 rbuf.f = dummy_soutr;
3413 rbuf.s = NULL;
3414 rbuf.beg = tmpptr;
3415 rbuf.cur = tmpptr;
3416 rbuf.end = tmpptr+sz-1;
3417 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3418 *rbuf.cur = '\0';
3421 alist->next = atmp; /* restore next pointer */
3423 if(alist->personal && alist->personal != ptmp)
3424 fs_give((void **) &alist->personal);
3426 alist->personal = ptmp; /* in case it changed, restore name */
3429 * BUG
3430 * With group syntax addresses we no longer have two identical
3431 * streams of output. Instead, for the fcc/postpone copy we include
3432 * all of the addresses inside the :; of the group, and for the
3433 * mail we're sending we don't include them. That means we aren't
3434 * correctly keeping track of the column to wrap in, below. That is,
3435 * we are keeping track of the fcc copy but we aren't keeping track
3436 * of the regular copy. It could result in too long or too short
3437 * lines. Should almost never come up since group addresses are almost
3438 * never followed by other addresses in the same header, and even
3439 * when they are, you have to go out of your way to get the headers
3440 * messed up.
3442 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3443 count = i + 4;
3444 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3445 || (localcopy && lmc.so && !lmc.all_written &&
3446 !so_puts(lmc.so, "\015\012 ")))
3447 goto bail_out;
3449 else
3450 count += i + 2;
3452 if(((!in_group || was_start_of_group)
3453 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3454 || (localcopy && lmc.so && !lmc.all_written
3455 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3456 goto bail_out;
3459 bail_out:
3460 if(tmpptr && tmpptr != tmp)
3461 fs_give((void **)&tmpptr);
3463 if(failed)
3464 return(0);
3466 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3467 && ((localcopy && lmc.so
3468 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3473 * mutated pine version of c-client's rfc822_header() function.
3474 * changed to call pine-wrapped header and address functions
3475 * so we don't have to limit the header size to a fixed buffer.
3476 * This function also calls pine's body_header write function
3477 * because encoding is delayed until output_body() is called.
3479 long
3480 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3482 PINEFIELD *pf;
3483 int j;
3485 if(header->env->remail){ /* if remailing */
3486 long i = strlen (header->env->remail);
3487 if(i > 4 && header->env->remail[i-4] == '\015')
3488 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3490 if((f && !(*f)(s, header->env->remail))
3491 || (lmc.so && !lmc.all_written
3492 && !so_puts(lmc.so, header->env->remail)))
3493 return(0L); /* start with remail header */
3496 j = 0;
3497 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3498 switch(pf->type){
3500 * Warning: This is confusing. The 2nd to last argument used to
3501 * be just pf->writehdr. We want Bcc lines to be written out
3502 * if we are handing off to a sendmail temp file but not if we
3503 * are talking smtp, so bcc's writehdr is set to 0 and
3504 * pine_address_line was sending if writehdr OR !sending_stream.
3505 * That works as long as we want to write everything when
3506 * !sending_stream (an mta handoff to sendmail). But then we
3507 * added the undisclosed recipients line which should only get
3508 * written if writehdr is set, and not when we pass to a
3509 * sendmail temp file. So pine_address_line has been changed
3510 * so it bases its decision solely on the writehdr passed to it,
3511 * and the logic that worries about Bcc and sending_stream
3512 * was moved up to the caller (here) to decide when to set it.
3514 * So we have:
3515 * undisclosed recipients:; This will just be written
3516 * if writehdr was set and not
3517 * otherwise, nothing magical.
3518 *** We may want to change this, because sendmail -t doesn't handle
3519 *** the empty group syntax well unless it has been configured to
3520 *** do so. It isn't configured by default, or in any of the
3521 *** sendmail v8 configs. So we may want to not write this line
3522 *** if we're doing an mta_handoff (!sending_stream).
3524 * !sending_stream (which means a handoff to a sendmail -t)
3525 * bcc or lcc both set the arg so they'll get written
3526 * (There is also Lcc hocus pocus in pine_address_line
3527 * which converts the Lcc: to Bcc: for sendmail
3528 * processing.)
3529 * sending_stream (which means an smtp handoff)
3530 * bcc and lcc will never have writehdr set, so
3531 * will never be written (They both do have rcptto set,
3532 * so they both do cause RCPT TO commands.)
3534 * The localcopy is independent of sending_stream and is just
3535 * written if it is set for all of these.
3537 case Address:
3538 if(!pine_address_line(pf->name,
3539 header,
3540 pf->addr ? *pf->addr : NULL,
3543 (!strucmp("bcc",pf->name ? pf->name : "")
3544 || !strucmp("Lcc",pf->name ? pf->name : ""))
3545 ? !sending_stream
3546 : pf->writehdr,
3547 pf->localcopy))
3548 return(0L);
3550 break;
3552 case Fcc:
3553 case FreeText:
3554 case Subject:
3555 if(!pine_header_line(pf->name, header,
3556 pf->text ? *pf->text : NULL,
3557 f, s, pf->writehdr, pf->localcopy))
3558 return(0L);
3560 break;
3562 default:
3563 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3564 pf->name);
3565 break;
3570 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3572 * Add comforting "X-" header line indicating what sort of
3573 * authenticity the receiver can expect...
3575 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3576 NETMBX netmbox;
3577 char sstring[MAILTMPLEN], *label; /* place to write */
3578 MAILSTREAM *m;
3579 int i, anonymous = 1;
3581 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3582 m = ps_global->s_pool.streams[i];
3583 if(m && sp_flagged(m, SP_LOCKED)
3584 && mail_valid_net_parse(m->mailbox, &netmbox)
3585 && !netmbox.anoflag)
3586 anonymous = 0;
3589 if(!anonymous){
3590 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3591 *user = (*netmbox.user)
3592 ? netmbox.user
3593 : cached_user_name(netmbox.mailbox);
3594 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3595 isdigit((unsigned char)last_char) ? "[" : "",
3596 netmbox.host,
3597 isdigit((unsigned char) last_char) ? "]" : "");
3598 sstring[sizeof(sstring)-1] = '\0';
3599 label = "X-X-Sender"; /* Jeez. */
3600 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3601 label += 4;
3603 else{
3604 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3605 sstring[sizeof(sstring)-1] = '\0';
3606 label = "X-Warning";
3609 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3610 return(0L);
3612 #endif
3614 if(body && !header->env->remail){ /* not if remail or no body */
3615 if((f && !(*f)(s, MIME_VER))
3616 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3617 || !pine_write_body_header(body, f, s))
3618 return(0L);
3620 else{ /* write terminating newline */
3621 if((f && !(*f)(s, "\015\012"))
3622 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3623 return(0L);
3626 return(1L);
3631 * pine_rfc822_output - pine's version of c-client call. Necessary here
3632 * since we're not using its structures as intended!
3634 long
3635 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3637 int we_cancel = 0;
3638 long retval;
3640 dprint((4, "-- pine_rfc822_output\n"));
3642 we_cancel = busy_cue(NULL, NULL, 1);
3643 pine_encode_body(body); /* encode body as necessary */
3644 /* build and output RFC822 header, output body */
3645 retval = pine_rfc822_header(header, body, f, s)
3646 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3648 if(we_cancel)
3649 cancel_busy_cue(-1);
3651 return(retval);
3656 * post_rfc822_output - cloak for pine's 822 output routine. Since
3657 * we can't pass opaque envelope thru c-client posting
3658 * logic, we need to wrap the real output inside
3659 * something that c-client knows how to call.
3661 long
3662 post_rfc822_output(char *tmp,
3663 ENVELOPE *env,
3664 struct mail_bodystruct *body,
3665 soutr_t f,
3666 void *s,
3667 long int ok8bit)
3669 return(pine_rfc822_output(send_header, body, f, s));
3674 * posting_characterset- determine what transliteration is reasonable
3675 * for posting the given non-ascii messsage data.
3677 * preferred_charset is the charset the original data was labeled in.
3678 * If we can keep that we do.
3680 * Returns: always returns the preferred character set.
3682 char *
3683 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3685 unsigned long *charsetmap = NULL;
3686 unsigned long validbitmap;
3687 static char *ascii = "US-ASCII";
3688 static char *utf8 = "UTF-8";
3689 int notcjk = 0;
3691 if(!ps_global->post_utf8){
3692 validbitmap = 0;
3694 if(mp == HdrText){
3695 char *text = NULL;
3696 UCS *ucs = NULL, *ucsp;
3698 text = (char *) data;
3700 /* convert text in header to UCS characters */
3701 if(text)
3702 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3704 if(!(ucs && *ucs))
3705 return(ascii);
3708 * After the while loop is done the validbitmap has
3709 * a 1 bit for all the character sets that can
3710 * represent all of the characters of this header.
3712 charsetmap = init_charsetchecker(preferred_charset);
3714 if(!charsetmap){
3715 if (ucs != NULL) fs_give((void **) &ucs);
3716 return(utf8);
3719 validbitmap = ~0;
3720 while((validbitmap & ~0x1) && (*ucsp)){
3721 if(*ucsp > 0xffff){
3722 fs_give((void **) &ucs);
3723 return(utf8);
3726 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3729 fs_give((void **) &ucs);
3731 notcjk = validbitmap & 0x1;
3732 validbitmap &= ~0x1;
3734 if(!validbitmap)
3735 return(utf8);
3737 else{
3738 struct mail_bodystruct *body = NULL;
3739 STORE_S *the_text = NULL;
3740 int outchars;
3741 unsigned char c;
3742 UCS ucs;
3743 CBUF_S cbuf;
3745 cbuf.cbuf[0] = '\0';
3746 cbuf.cbufp = cbuf.cbuf;
3747 cbuf.cbufend = cbuf.cbuf;
3749 body = (struct mail_bodystruct *) data;
3751 if(body && body->type == TYPEMULTIPART)
3752 body = &body->nested.part->body;
3754 if(body && body->type == TYPETEXT)
3755 the_text = (STORE_S *) body->contents.text.data;
3757 if(!the_text)
3758 return(ascii);
3760 so_seek(the_text, 0L, 0); /* rewind */
3762 charsetmap = init_charsetchecker(preferred_charset);
3764 if(!charsetmap)
3765 return(utf8);
3767 validbitmap = ~0;
3770 * Read a stream of UTF-8 characters from the_text
3771 * and convert them to UCS-4 characters for the translatable
3772 * test.
3774 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3775 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3776 /* got a ucs character */
3777 if(ucs > 0xffff)
3778 return(utf8);
3780 validbitmap &= charsetmap[(unsigned long) ucs];
3784 notcjk = validbitmap & 0x1;
3785 validbitmap &= ~0x1;
3787 if(!validbitmap)
3788 return(utf8);
3791 /* user chooses something other than UTF-8 */
3792 if(strucmp(ps_global->posting_charmap, utf8)){
3794 * If we're to post in other than UTF-8, and it can be
3795 * transliterated without losing fidelity, do it.
3796 * Else, use UTF-8.
3799 /* if ascii works, always use that */
3800 if(representable_in_charset(validbitmap, ascii))
3801 return(ascii);
3803 /* does the user's posting character set work? */
3804 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3805 return(ps_global->posting_charmap);
3807 /* this is the charset the message we are replying to was in */
3808 if(preferred_charset
3809 && strucmp(preferred_charset, ascii)
3810 && representable_in_charset(validbitmap, preferred_charset))
3811 return(preferred_charset);
3813 /* else, use UTF-8 */
3816 /* user chooses nothing, going with the default */
3817 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3818 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3819 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3820 char *most_preferred;
3823 * In this case the user didn't specify a posting character set
3824 * and we will choose the most-specific one from our list.
3827 /* ascii is best */
3828 if(representable_in_charset(validbitmap, ascii))
3829 return(ascii);
3831 /* Can we keep the original from the message we're replying to? */
3832 if(preferred_charset
3833 && strucmp(preferred_charset, ascii)
3834 && representable_in_charset(validbitmap, preferred_charset))
3835 return(preferred_charset);
3837 /* choose the best of the rest */
3838 most_preferred = most_preferred_charset(validbitmap);
3839 if(!most_preferred)
3840 return(utf8);
3843 * If the text we're labeling contains something like
3844 * smart quotes but no CJK characters, then instead of
3845 * labeling it as ISO-2022-JP we want to use UTF-8.
3847 if(notcjk){
3848 const CHARSET *cs;
3850 cs = utf8_charset(most_preferred);
3851 if(!cs
3852 || cs->script == SC_CHINESE_SIMPLIFIED
3853 || cs->script == SC_CHINESE_TRADITIONAL
3854 || cs->script == SC_JAPANESE
3855 || cs->script == SC_KOREAN)
3856 return(utf8);
3859 return(most_preferred);
3861 /* user explicitly chooses UTF-8 */
3862 else{
3863 /* if ascii works, always use that */
3864 if(representable_in_charset(validbitmap, ascii))
3865 return(ascii);
3867 /* else, use UTF-8 */
3872 return(utf8);
3876 static char **charsetlist = NULL;
3877 static int items_in_charsetlist = 0;
3878 static unsigned long *charsetmap = NULL;
3880 static char *downgrades[] = {
3881 "US-ASCII",
3882 "ISO-8859-15",
3883 "ISO-8859-1",
3884 "ISO-8859-2",
3885 "VISCII",
3886 "KOI8-R",
3887 "KOI8-U",
3888 "ISO-8859-7",
3889 "ISO-8859-6",
3890 "ISO-8859-8",
3891 "TIS-620",
3892 "ISO-2022-JP",
3893 "GB2312",
3894 "BIG5",
3895 "EUC-KR"
3899 unsigned long *
3900 init_charsetchecker(char *preferred_charset)
3902 int i, count = 0, reset = 0;
3903 char *ascii = "US-ASCII";
3904 char *utf8 = "UTF-8";
3907 * When user doesn't set a posting character set posting_charmap ends up
3908 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3909 * That's where the strange set of if-else's come from.
3912 /* user chooses something other than UTF-8 */
3913 if(strucmp(ps_global->posting_charmap, utf8)){
3914 count++; /* US-ASCII */
3915 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3916 reset++;
3918 /* if posting_charmap is valid, include it in list */
3919 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3920 && strucmp(ps_global->posting_charmap, ascii)
3921 && strucmp(ps_global->posting_charmap, utf8)
3922 && utf8_charset(ps_global->posting_charmap)){
3923 count++;
3924 if(!reset
3925 && (items_in_charsetlist < count
3926 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3927 reset++;
3930 if(preferred_charset && preferred_charset[0]
3931 && strucmp(preferred_charset, ascii)
3932 && strucmp(preferred_charset, utf8)
3933 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3934 count++;
3935 if(!reset
3936 && (items_in_charsetlist < count
3937 || strucmp(charsetlist[count-1], preferred_charset)))
3938 reset++;
3941 if(items_in_charsetlist != count)
3942 reset++;
3944 if(reset){
3945 if(charsetlist)
3946 free_list_array(&charsetlist);
3948 items_in_charsetlist = count;
3949 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3951 i = 0;
3952 charsetlist[i++] = cpystr(ascii);
3954 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3955 && strucmp(ps_global->posting_charmap, ascii)
3956 && strucmp(ps_global->posting_charmap, utf8)
3957 && utf8_charset(ps_global->posting_charmap))
3958 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3960 if(preferred_charset && preferred_charset[0]
3961 && strucmp(preferred_charset, ascii)
3962 && strucmp(preferred_charset, utf8)
3963 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3964 charsetlist[i++] = cpystr(preferred_charset);
3966 charsetlist[i] = NULL;
3969 /* user chooses nothing, going with the default */
3970 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3971 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3972 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3973 int add_preferred = 0;
3975 /* does preferred_charset have to be added to the list? */
3976 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3977 add_preferred = 1;
3978 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3979 if(!strucmp(downgrades[i], preferred_charset))
3980 add_preferred = 0;
3983 if(add_preferred){
3984 /* existing list is right size already */
3985 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3986 /* just check to see if last list item is the preferred_charset */
3987 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3988 /* no, fix it */
3989 reset++;
3990 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3991 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3994 else{
3995 reset++;
3996 if(charsetlist)
3997 free_list_array(&charsetlist);
3999 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
4000 items_in_charsetlist = count;
4001 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4002 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4003 charsetlist[i] = cpystr(downgrades[i]);
4005 charsetlist[i++] = cpystr(preferred_charset);
4006 charsetlist[i] = NULL;
4009 else{
4010 /* if list is same size as downgrades, consider it good */
4011 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
4012 reset++;
4014 if(reset){
4015 if(charsetlist)
4016 free_list_array(&charsetlist);
4018 count = sizeof(downgrades)/sizeof(downgrades[0]);
4019 items_in_charsetlist = count;
4020 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4021 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4022 charsetlist[i] = cpystr(downgrades[i]);
4024 charsetlist[i] = NULL;
4028 /* user explicitly chooses UTF-8 */
4029 else{
4030 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4031 count++; /* US-ASCII */
4032 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4033 reset++;
4035 if(items_in_charsetlist != count)
4036 reset++;
4038 if(reset){
4039 if(charsetlist)
4040 free_list_array(&charsetlist);
4042 /* the list is just ascii and nothing else */
4043 items_in_charsetlist = count;
4044 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4046 i = 0;
4047 charsetlist[i++] = cpystr(ascii);
4048 charsetlist[i] = NULL;
4053 if(reset){
4054 if(charsetmap)
4055 fs_give((void **) &charsetmap);
4057 if(charsetlist)
4058 charsetmap = utf8_csvalidmap(charsetlist);
4061 return(charsetmap);
4065 /* total reset */
4066 void
4067 free_charsetchecker(void)
4069 if(charsetlist)
4070 free_list_array(&charsetlist);
4072 items_in_charsetlist = 0;
4074 if(charsetmap)
4075 fs_give((void **) &charsetmap);
4080 representable_in_charset(unsigned long validbitmap, char *charset)
4082 int i, done = 0, ret = 0;
4083 unsigned long j;
4085 if(!(charset && charset[0]))
4086 return ret;
4088 if(!strucmp(charset, "UTF-8"))
4089 return 1;
4091 for(i = 0; !done && i < items_in_charsetlist; i++){
4092 if(!strucmp(charset, charsetlist[i])){
4093 j = 1;
4094 j <<= (i+1);
4095 done++;
4096 if(validbitmap & j)
4097 ret = 1;
4101 return ret;
4105 char *
4106 most_preferred_charset(unsigned long validbitmap)
4108 unsigned long bm;
4109 unsigned long rm;
4110 int index;
4112 if(!(validbitmap && items_in_charsetlist > 0))
4113 return("UTF-8");
4115 /* careful, find_rightmost_bit modifies the bitmap */
4116 bm = validbitmap;
4117 rm = find_rightmost_bit(&bm);
4118 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4120 return(charsetlist[index]);
4125 * Set parameter to new value.
4127 void
4128 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4130 PARAMETER *pm;
4132 if(!param || !(paramname && *paramname))
4133 return;
4135 if(*param == NULL){
4136 pm = (*param) = mail_newbody_parameter();
4137 pm->attribute = cpystr(paramname);
4139 else{
4140 int nomatch;
4142 for(pm = *param;
4143 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4144 pm = pm->next)
4145 ;/* searching for paramname parameter */
4147 if(nomatch){ /* add charset parameter */
4148 pm->next = mail_newbody_parameter();
4149 pm = pm->next;
4150 pm->attribute = cpystr(paramname);
4152 /* else pm is existing paramname parameter */
4155 if(pm){
4156 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4157 if(pm->value)
4158 fs_give((void **) &pm->value);
4160 if(new_value)
4161 pm->value = cpystr(new_value);
4167 /*----------------------------------------------------------------------
4168 Remove the leading digits from SMTP error messages
4169 -----*/
4170 char *
4171 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4173 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4174 (*error == '.' && isdigit((unsigned char)*(error+1))))
4175 error++;
4177 snprintf(outbuf, outbuflen, printstring, error);
4178 outbuf[outbuflen-1] = '\0';
4179 return(outbuf);
4184 * Local globals pine's body output routine needs
4186 static soutr_t l_f;
4187 static TCPSTREAM *l_stream;
4188 static unsigned c_in_buf = 0;
4191 * def to make our pipe write's more friendly
4193 #ifdef PIPE_MAX
4194 #if PIPE_MAX > 20000
4195 #undef PIPE_MAX
4196 #endif
4197 #endif
4199 #ifndef PIPE_MAX
4200 #define PIPE_MAX 1024
4201 #endif
4205 * l_flust_net - empties gf_io terminal function's buffer
4208 l_flush_net(int force)
4210 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4211 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4213 tmp_20k_buf[c_in_buf] = '\0';
4214 if(!force){
4216 * The start of each write is expected to be the start of a
4217 * "record" (i.e., a CRLF terminated line). Make sure that is true
4218 * else we might screw up SMTP dot quoting...
4220 for(p = tmp_20k_buf, lp = NULL;
4221 (p = strstr(p, "\015\012")) != NULL;
4222 lp = (p += 2))
4226 if(!lp && c_in_buf > 2) /* no CRLF! */
4227 for(p = &tmp_20k_buf[c_in_buf] - 2;
4228 p > &tmp_20k_buf[0] && *p == '.';
4229 p--) /* find last non-dot */
4232 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4233 /* snippet remains */
4234 c = *lp;
4235 *lp = '\0';
4239 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4240 || (lmc.so && !lmc.all_written
4241 && !(lmc.text_only && lmc.text_written)
4242 && !so_puts(lmc.so, tmp_20k_buf)))
4243 return(0);
4245 c_in_buf = 0;
4246 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4247 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4248 c_in_buf++, lp++;
4251 return(1);
4256 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4260 l_putc(int c)
4262 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4263 tmp_20k_buf[c_in_buf++] = (char) c;
4265 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4271 * pine_rfc822_output_body - pine's version of c-client call. Again,
4272 * necessary since c-client doesn't know about how
4273 * we're treating attachments
4275 long
4276 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4278 STORE_S *bodyso;
4279 PART *part;
4280 PARAMETER *param;
4281 char *t, *cookie = NIL, *encode_error;
4282 char tmp[MAILTMPLEN];
4283 int add_trailing_crlf;
4284 LOC_2022_JP ljp;
4285 gf_io_t gc;
4287 dprint((4, "-- pine_rfc822_output_body: %d\n",
4288 body ? body->type : 0));
4290 bodyso = (STORE_S *) body->contents.text.data;
4292 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4293 part = body->nested.part; /* first body part */
4294 /* find cookie */
4295 for (param = body->parameter; param && !cookie; param = param->next)
4296 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4297 if (!cookie) cookie = "-"; /* yucky default */
4300 * Output a bit of text before the first multipart delimiter
4301 * to warn unsuspecting users of non-mime-aware ua's that
4302 * they should expect weirdness. We do not add this when signing a
4303 * message, though...
4305 #ifdef SMIME
4306 if(ps_global->smime && !ps_global->smime->do_sign)
4307 #endif
4308 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"))
4309 return(0);
4311 do { /* for each part */
4312 /* build cookie */
4313 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4314 tmp[sizeof(tmp)-1] = '\0';
4315 /* append cookie,mini-hdr,contents */
4316 if((f && !(*f)(s, tmp))
4317 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4318 || !pine_write_body_header(&part->body,f,s)
4319 || !pine_rfc822_output_body (&part->body,f,s))
4320 return(0);
4321 } while ((part = part->next) != NULL); /* until done */
4322 /* output trailing cookie */
4323 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4324 tmp[sizeof(tmp)-1] = '\0';
4325 #ifdef SMIME
4326 if(ps_global->smime && ps_global->smime->do_sign
4327 && strlen(tmp) < sizeof(tmp)-2)
4328 strncat(tmp, "\r\n", 2);
4329 #endif
4330 if(lmc.so && !lmc.all_written){
4331 so_puts(lmc.so, t);
4332 so_puts(lmc.so, "\015\012");
4335 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4338 l_f = f; /* set up for writing chars... */
4339 l_stream = s; /* out other end of pipe... */
4340 gf_filter_init();
4341 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4342 body->size.bytes));
4344 if(bodyso)
4345 gf_set_so_readc(&gc, bodyso);
4346 else
4347 return(1);
4350 * Don't add trailing line if it is ExternalText, which already guarantees
4351 * a trailing newline.
4353 add_trailing_crlf = !(bodyso->src == ExternalText);
4355 so_seek(bodyso, 0L, 0);
4357 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4358 char *charset;
4360 if(body->type == TYPETEXT
4361 && so_attr(bodyso, "edited", NULL)
4362 && (charset = parameter_val(body->parameter, "charset"))){
4363 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4364 if(!strucmp(charset, "iso-2022-jp")){
4365 ljp.report_err = 0;
4366 gf_link_filter(gf_line_test,
4367 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4369 else{
4370 void *table = utf8_rmap(charset);
4372 if(table){
4373 gf_link_filter(gf_convert_utf8_charset,
4374 gf_convert_utf8_charset_opt(table,0));
4376 else{
4377 /* else, just send it? */
4378 set_parameter(&body->parameter, "charset", "UTF-8");
4383 fs_give((void **)&charset);
4387 * Convert text pieces to canonical form
4388 * BEFORE applying any encoding (rfc1341: appendix G)...
4389 * NOTE: almost all filters expect CRLF newlines
4391 if(body->type == TYPETEXT
4392 && body->encoding != ENCBASE64
4393 && !so_attr(bodyso, "rawbody", NULL)){
4394 gf_link_filter(gf_local_nvtnl, NULL);
4397 switch (body->encoding) { /* all else needs filtering */
4398 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4399 gf_link_filter(gf_8bit_qp, NULL);
4400 break;
4402 case ENCBINARY: /* encode binary into BASE64 */
4403 gf_link_filter(gf_binary_b64, NULL);
4404 break;
4406 default: /* otherwise text */
4407 break;
4411 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4412 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4413 _("Encoding Error \"%s\""), encode_error);
4414 display_message('x');
4417 gf_clear_so_readc(bodyso);
4419 if(encode_error || !l_flush_net(TRUE))
4420 return(0);
4422 send_bytes_sent += gf_bytes_piped();
4423 so_release((STORE_S *)body->contents.text.data);
4425 if(lmc.so && !lmc.all_written && lmc.text_only){
4426 if(lmc.text_written){ /* we have some splainin' to do */
4427 char tmp[MAILTMPLEN];
4428 char *name = NULL;
4430 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4431 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4432 return(0);
4435 * BUG: If this name is not ascii it's going to cause trouble.
4437 name = parameter_val(body->parameter, "name");
4438 snprintf(tmp, sizeof(tmp),
4439 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4440 body_type_names(body->type),
4441 body->subtype ? body->subtype : "Unknown",
4442 name ? " (Name=\"" : "",
4443 name ? name : "",
4444 name ? "\")" : "",
4445 comatose(body->size.bytes));
4446 tmp[sizeof(tmp)-1] = '\0';
4447 if(name)
4448 fs_give((void **)&name);
4450 if(!so_puts(lmc.so, tmp))
4451 return(0);
4453 else /* suppress everything after first text part */
4454 lmc.text_written = (body->type == TYPETEXT
4455 && (!body->subtype
4456 || !strucmp(body->subtype, "plain")));
4459 if(add_trailing_crlf)
4460 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4461 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4462 else
4463 return(1);
4466 char *
4467 ToLower(char *s, char *t)
4469 int i;
4471 for(i = 0; s != NULL && s[i] != '\0'; i++)
4472 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4473 t[i] = '\0';
4475 return t;
4479 * pine_write_body_header - another c-client clone. This time
4480 * so the final encoding labels get set
4481 * correctly since it hasn't happened yet,
4482 * and to be paranoid about line lengths.
4484 * Returns: TRUE/nonzero on success, zero on error
4487 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4489 char tmp[MAILTMPLEN];
4490 RFC822BUFFER rbuf;
4491 int i;
4492 unsigned char c;
4493 STRINGLIST *stl;
4494 STORE_S *so;
4495 extern const char *tspecials;
4497 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4498 if(!(so_puts(so, "Content-Type: ")
4499 && so_puts(so, ToLower(body_types[body->type], tmp))
4500 && so_puts(so, "/")
4501 && so_puts(so, ToLower(body->subtype
4502 ? body->subtype
4503 : rfc822_default_subtype (body->type),tmp))))
4504 return(pwbh_finish(0, so));
4506 if(body->parameter){
4507 if(!pine_write_params(body->parameter, so))
4508 return(pwbh_finish(0, so));
4510 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4511 return(pwbh_finish(0, so));
4513 if(!so_puts(so, "\015\012"))
4514 return(pwbh_finish(0, so));
4516 if ((body->encoding /* note: encoding 7BIT never output! */
4517 && !(so_puts(so, "Content-Transfer-Encoding: ")
4518 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4519 ? ENCBASE64
4520 : (body->encoding == ENC8BIT)
4521 ? ENCQUOTEDPRINTABLE
4522 : (body->encoding <= ENCMAX)
4523 ? body->encoding
4524 : ENCOTHER])
4525 && so_puts(so, "\015\012")))
4527 * If requested, strip Content-ID headers that don't look like they
4528 * are needed. Microsoft's Outlook XP has a bug that causes it to
4529 * not show that there is an attachment when there is a Content-ID
4530 * header present on that attachment.
4532 * If user has quell-content-id turned on, don't output content-id
4533 * unless it is of type message/external-body.
4534 * Since this code doesn't look inside messages being forwarded
4535 * type message content-ids will remain as is and type multipart
4536 * alternative will remain as is. We don't create those on our
4537 * own. If we did, we'd have to worry about getting this right.
4539 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4540 || (body->type == TYPEMESSAGE
4541 && body->subtype
4542 && !strucmp(body->subtype, "external-body")))
4543 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4544 && so_puts(so, "\015\012")))
4545 || (body->description
4546 && strlen(body->description) < 5000 /* arbitrary! */
4547 && !pine_write_header_line("Content-Description: ", body->description, so))
4548 || (body->md5
4549 && !(so_puts(so, "Content-MD5: ")
4550 && so_puts(so, body->md5)
4551 && so_puts(so, "\015\012"))))
4552 return(pwbh_finish(0, so));
4554 if ((stl = body->language) != NULL) {
4555 if(!so_puts(so, "Content-Language: "))
4556 return(pwbh_finish(0, so));
4558 do {
4559 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4560 return(pwbh_finish(0, so));
4562 tmp[0] = '\0';
4563 rbuf.f = dummy_soutr;
4564 rbuf.s = NULL;
4565 rbuf.beg = tmp;
4566 rbuf.cur = tmp;
4567 rbuf.end = tmp+sizeof(tmp)-1;
4568 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4569 *rbuf.cur = '\0';
4571 if(!so_puts(so, tmp)
4572 || ((stl = stl->next) && !so_puts(so, ", ")))
4573 return(pwbh_finish(0, so));
4575 while (stl);
4577 if(!so_puts(so, "\015\012"))
4578 return(pwbh_finish(0, so));
4581 if (body->disposition.type) {
4582 if(!(so_puts(so, "Content-Disposition: ")
4583 && so_puts(so, body->disposition.type)))
4584 return(pwbh_finish(0, so));
4586 if(!pine_write_params(body->disposition.parameter, so))
4587 return(pwbh_finish(0, so));
4589 if(!so_puts(so, "\015\012"))
4590 return(pwbh_finish(0, so));
4593 /* copy out of so, a line at a time (or less than a K)
4594 * and send it down the pike
4596 so_seek(so, 0L, 0);
4597 i = 0;
4598 while(so_readc(&c, so))
4599 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4600 tmp[i] = '\0';
4601 if((f && !(*f)(s, tmp))
4602 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4603 return(pwbh_finish(0, so));
4605 i = 0;
4608 /* Finally, write blank line */
4609 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4610 return(pwbh_finish(0, so));
4612 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4615 return(0);
4620 * pine_write_header - convert, encode (if needed) and
4621 * write "header-name: field-body"
4624 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4626 char *cv, *cs, *vp;
4627 int rv;
4629 cs = posting_characterset(val, NULL, HdrText);
4630 cv = utf8_to_charset(val, cs, 0);
4631 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4632 (unsigned char *) cv, cs);
4634 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4636 if(cv && cv != val)
4637 fs_give((void **) &cv);
4640 return(rv);
4645 * pine_write_param - convert, encode and write MIME header-field parameters
4648 pine_write_params(PARAMETER *param, STORE_S *so)
4650 for(; param; param = param->next){
4651 int rv;
4652 char *cv, *cs;
4653 extern const char *tspecials;
4655 cs = posting_characterset(param->value, NULL, HdrText);
4656 cv = utf8_to_charset(param->value, cs, 0);
4657 rv = (so_puts(so, "; ")
4658 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4660 if(cv && cv != param->value)
4661 fs_give((void **) &cv);
4663 if(!rv)
4664 return(0);
4667 return(1);
4673 lmc_body_header_line(char *line, int beginning)
4675 if(lmc.so && !lmc.all_written){
4676 if(beginning && lmc.text_only && lmc.text_written
4677 && (!struncmp(line, "content-type:", 13)
4678 || !struncmp(line, "content-transfer-encoding:", 26)
4679 || !struncmp(line, "content-disposition:", 20))){
4681 * "comment out" the real values since our comment isn't
4682 * likely the same type, disposition nor encoding...
4684 if(!so_puts(lmc.so, "X-"))
4685 return(FALSE);
4688 return(so_puts(lmc.so, line));
4691 return(TRUE);
4696 lmc_body_header_finish(void)
4698 if(lmc.so && !lmc.all_written){
4699 if(lmc.text_only && lmc.text_written
4700 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4701 return(FALSE);
4703 return(so_puts(lmc.so, "\015\012"));
4706 return(TRUE);
4712 pwbh_finish(int rv, STORE_S *so)
4714 if(so)
4715 so_give(&so);
4717 return(rv);
4722 * pine_free_body - c-client call wrapper so the body data pointer we
4723 * we're using in a way c-client doesn't know about
4724 * gets free'd appropriately.
4726 void
4727 pine_free_body(struct mail_bodystruct **body)
4730 * Preempt c-client's contents.text.data clean up since we've
4731 * usurped it's meaning for our own purposes...
4733 pine_free_body_data (*body);
4735 /* Then let c-client handle the rest... */
4736 mail_free_body(body);
4741 * pine_free_body_data - free pine's interpretations of the body part's
4742 * data pointer.
4744 void
4745 pine_free_body_data(struct mail_bodystruct *body)
4747 if(body){
4748 if(body->type == TYPEMULTIPART){
4749 PART *part = body->nested.part;
4750 do /* for each part */
4751 pine_free_body_data(&part->body);
4752 while ((part = part->next) != NULL); /* until done */
4754 else if(body->contents.text.data)
4755 so_give((STORE_S **) &body->contents.text.data);
4760 long
4761 send_body_size(struct mail_bodystruct *body)
4763 long l = 0L;
4764 PART *part;
4766 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4767 part = body->nested.part; /* first body part */
4768 do /* for each part */
4769 l += send_body_size(&part->body);
4770 while ((part = part->next) != NULL); /* until done */
4771 return(l);
4774 return(l + body->size.bytes);
4779 sent_percent(void)
4781 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4782 / send_bytes_to_send);
4783 return(MIN(i, 100));
4788 * pine_smtp_verbose_out - write
4790 void
4791 pine_smtp_verbose_out(char *s)
4793 #ifdef _WINDOWS
4794 LPTSTR slpt;
4795 #endif
4796 if(verbose_send_output && s){
4797 char *p, last = '\0';
4799 for(p = s; *p; p++)
4800 if(*p == '\015')
4801 *p = ' ';
4802 else
4803 last = *p;
4805 #ifdef _WINDOWS
4807 * The stream is opened in Unicode mode, so we need to fix the
4808 * argument to fputs.
4810 slpt = utf8_to_lptstr((LPSTR) s);
4811 if(slpt){
4812 _fputts(slpt, verbose_send_output);
4813 fs_give((void **) &slpt);
4816 if(last != '\012')
4817 _fputtc(L'\n', verbose_send_output);
4818 #else
4819 fputs(s, verbose_send_output);
4820 if(last != '\012')
4821 fputc('\n', verbose_send_output);
4822 #endif
4829 * pine_header_forbidden - is this name a "forbidden" header?
4831 * name - the header name to check
4832 * We don't allow user to change these.
4835 pine_header_forbidden(char *name)
4837 char **p;
4838 static char *forbidden_headers[] = {
4839 "sender",
4840 "x-sender",
4841 "x-x-sender",
4842 "date",
4843 "received",
4844 "message-id",
4845 "in-reply-to",
4846 "path",
4847 "resent-message-id",
4848 "resent-date",
4849 "resent-from",
4850 "resent-sender",
4851 "resent-to",
4852 "resent-cc",
4853 "resent-reply-to",
4854 "mime-version",
4855 "content-type",
4856 "x-priority",
4857 "user-agent",
4858 "list-help", /* rfc 2369, section 3 */
4859 "list-unsubscribe",
4860 "list-subscribe",
4861 "list-post",
4862 "list-owner",
4863 "list-archive",
4864 NULL
4867 for(p = forbidden_headers; *p; p++)
4868 if(!strucmp(name, *p))
4869 break;
4871 return((*p) ? 1 : 0);
4876 * hdr_is_in_list - is there a custom value for this header?
4878 * hdr - the header name to check
4879 * custom - the list to check in
4880 * Returns 1 if there is a custom value, 0 otherwise.
4883 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4885 PINEFIELD *pf;
4887 for(pf = custom; pf && pf->name; pf = pf->next)
4888 if(strucmp(pf->name, hdr) == 0)
4889 return 1;
4891 return 0;
4896 * count_custom_hdrs_pf - returns number of custom headers in arg
4897 * custom -- the list to be counted
4898 * only_nonstandard -- only count headers which aren't standard pine headers
4901 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4903 int ret = 0;
4905 for(; custom && custom->name; custom = custom->next)
4906 if(!only_nonstandard || !custom->standard)
4907 ret++;
4909 return(ret);
4914 * count_custom_hdrs_list - returns number of custom headers in arg
4917 count_custom_hdrs_list(char **list)
4919 char **p;
4920 char *q = NULL;
4921 char *name;
4922 char *t;
4923 char save;
4924 int ret = 0;
4926 if(list){
4927 for(p = list; (q = *p) != NULL; p++){
4928 if(q[0]){
4929 /* remove leading whitespace */
4930 name = skip_white_space(q);
4932 /* look for colon or space or end */
4933 for(t = name;
4934 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4935 ;/* do nothing */
4937 save = *t;
4938 *t = '\0';
4939 if(!pine_header_forbidden(name))
4940 ret++;
4942 *t = save;
4947 return(ret);
4952 * set_default_hdrval - put the user's default value for this header
4953 * into pf->textbuf.
4954 * setthis - the pinefield to be set
4955 * custom - where to look for the default
4957 CustomType
4958 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4960 PINEFIELD *pf;
4961 CustomType ret = NoMatch;
4963 if(!setthis || !setthis->name){
4964 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4965 return(ret);
4968 setthis->textbuf = NULL;
4970 for(pf = custom; pf && pf->name; pf = pf->next){
4971 if(strucmp(pf->name, setthis->name) != 0)
4972 continue;
4974 ret = pf->cstmtype;
4976 /* turn on editing */
4977 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4978 setthis->canedit = 1;
4980 if(pf->val)
4981 setthis->textbuf = cpystr(pf->val);
4984 if(!setthis->textbuf)
4985 setthis->textbuf = cpystr("");
4987 return(ret);
4992 * pine_header_standard - is this name a "standard" header?
4994 * name - the header name to check
4996 FieldType
4997 pine_header_standard(char *name)
4999 int i;
5001 /* check to see if this is a standard header */
5002 for(i = 0; pf_template[i].name; i++)
5003 if(!strucmp(name, pf_template[i].name))
5004 return(pf_template[i].type);
5006 return(TypeUnknown);
5011 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
5012 * Allocates space for each name and addr ptr.
5013 * Allocates space for default in textbuf, even if empty.
5015 * head - the first PINEFIELD to fill in
5016 * list - the list to parse
5018 void
5019 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
5021 char **p, *q, *t, *name, *value, save;
5022 PINEFIELD *pf;
5024 pf = head;
5026 if(list){
5027 for(p = list; (q = *p) != NULL; p++){
5029 if(q[0]){
5031 /* anything after leading whitespace? */
5032 if(!*(name = skip_white_space(q)))
5033 continue;
5035 /* look for colon or space or end */
5036 for(t = name;
5037 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5038 ;/* do nothing */
5040 /* if there is a space in the field-name, skip it */
5041 if(isspace((unsigned char)*t)){
5042 q_status_message1(SM_ORDER, 3, 3,
5043 _("Space not allowed in header name (%s)"),
5044 name);
5045 continue;
5048 save = *t;
5049 *t = '\0';
5051 /* Don't allow any of the forbidden headers. */
5052 if(pine_header_forbidden(name)){
5053 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5054 _("Not allowed to change header \"%s\""),
5055 name);
5057 *t = save;
5058 continue;
5061 if(pf){
5062 if(pine_header_standard(name) != TypeUnknown)
5063 pf->standard = 1;
5065 pf->name = cpystr(name);
5066 pf->type = FreeText;
5067 pf->cstmtype = cstmtype;
5068 pf->next = pf+1;
5070 #ifdef OLDWAY
5072 * Some mailers apparently break if we change
5073 * user@domain into Fred <user@domain> for
5074 * return-receipt-to,
5075 * so we'll just call this a FreeText field, too.
5078 * For now, all custom headers are FreeText except for
5079 * this one that we happen to know about. We might
5080 * have to add some syntax to the config option so that
5081 * people can tell us their custom header takes addresses.
5083 if(!strucmp(pf->name, "Return-Receipt-to")){
5084 pf->type = Address;
5085 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5086 *pf->addr = (ADDRESS *)NULL;
5088 #endif /* OLDWAY */
5090 *t = save;
5092 /* remove space between name and colon */
5093 value = skip_white_space(t);
5095 /* give them an alloc'd default, even if empty */
5096 pf->textbuf = cpystr((*value == ':')
5097 ? skip_white_space(++value) : "");
5098 if(pf->textbuf && pf->textbuf[0])
5099 pf->val = cpystr(pf->textbuf);
5101 pf++;
5103 else
5104 *t = save;
5109 /* fix last next pointer */
5110 if(head && pf != head)
5111 (pf-1)->next = NULL;
5116 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5117 * setup except that it doesn't have default values.
5118 * Add those defaults if they exist in "list".
5120 * head - the first PINEFIELD to add a default to
5121 * list - the list to get the defaults from
5123 void
5124 add_defaults_from_list(PINEFIELD *head, char **list)
5126 char **p, *q, *t, *name, *value, save;
5127 PINEFIELD *pf;
5129 for(pf = head; pf && list; pf = pf->next){
5130 if(!pf->name)
5131 continue;
5133 for(p = list; (q = *p) != NULL; p++){
5135 if(q[0]){
5137 /* anything after leading whitespace? */
5138 if(!*(name = skip_white_space(q)))
5139 continue;
5141 /* look for colon or space or end */
5142 for(t = name;
5143 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5144 ;/* do nothing */
5146 /* if there is a space in the field-name, skip it */
5147 if(isspace((unsigned char)*t))
5148 continue;
5150 save = *t;
5151 *t = '\0';
5153 if(strucmp(name, pf->name) != 0){
5154 *t = save;
5155 continue;
5158 *t = save;
5161 * Found the right header. See if it has a default
5162 * value set.
5165 /* remove space between name and colon */
5166 value = skip_white_space(t);
5168 if(*value == ':'){
5169 char *defval;
5171 defval = skip_white_space(++value);
5172 if(defval && *defval){
5173 if(pf->val)
5174 fs_give((void **)&pf->val);
5176 pf->val = cpystr(defval);
5180 break; /* on to next pf */
5188 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5189 * fill in the defaults
5190 * Args - list -- The list to parse.
5191 * cstmtype -- Fill the in cstmtype field with this value
5193 PINEFIELD *
5194 parse_custom_hdrs(char **list, CustomType cstmtype)
5196 PINEFIELD *pfields;
5197 int i;
5200 * add one for possible use by fcc
5201 * What is this "possible use"? I don't see it. I don't
5202 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5204 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5205 pfields = (PINEFIELD *)fs_get((size_t) i);
5206 memset(pfields, 0, (size_t) i);
5208 /* set up the custom header pfields */
5209 customized_hdr_setup(pfields, list, cstmtype);
5211 return(pfields);
5216 * Combine the two lists of headers into one list which is allocated here
5217 * and freed by the caller. Eliminate duplicates with values from the role
5218 * taking precedence over values from the default.
5220 PINEFIELD *
5221 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5223 PINEFIELD *pfields, *pf, *pf2;
5224 int max_hdrs, i;
5226 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5227 count_custom_hdrs_pf(dflthdrs,0);
5229 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5230 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5232 pf = pfields;
5233 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5234 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5235 pf->type = pf2->type;
5236 pf->cstmtype = pf2->cstmtype;
5237 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5238 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5239 pf->standard = pf2->standard;
5240 pf->next = pf+1;
5241 pf++;
5244 /* if these aren't already there, add them */
5245 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5246 /* check for already there */
5247 for(i = 0;
5248 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5249 i++)
5252 if(!pfields[i].name){ /* this is a new one */
5253 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5254 pf->type = pf2->type;
5255 pf->cstmtype = pf2->cstmtype;
5256 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5257 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5258 pf->standard = pf2->standard;
5259 pf->next = pf+1;
5260 pf++;
5264 /* fix last next pointer */
5265 if(pf != pfields)
5266 (pf-1)->next = NULL;
5268 return(pfields);
5273 * free_customs - free misc. resources associated with custom header fields
5275 * pf - pointer to first custom field
5277 void
5278 free_customs(PINEFIELD *head)
5280 PINEFIELD *pf;
5282 for(pf = head; pf && pf->name; pf = pf->next){
5284 fs_give((void **)&pf->name);
5286 if(pf->val)
5287 fs_give((void **)&pf->val);
5289 /* only true for FreeText */
5290 if(pf->textbuf)
5291 fs_give((void **)&pf->textbuf);
5293 /* only true for Address */
5294 if(pf->addr && *pf->addr)
5295 mail_free_address(pf->addr);
5298 fs_give((void **)&head);
5303 * encode_whole_header
5305 * Returns 1 if whole value should be encoded
5306 * 0 to encode only within comments
5309 encode_whole_header(char *field, METAENV *header)
5311 int retval = 0;
5312 PINEFIELD *pf;
5314 if(field && (!strucmp(field, "Subject") ||
5315 !strucmp(field, "Comment") ||
5316 !struncmp(field, "X-", 2)))
5317 retval++;
5318 else if(field && *field && header && header->custom){
5319 for(pf = header->custom; pf && pf->name; pf = pf->next){
5321 if(!pf->standard && !strucmp(pf->name, field)){
5322 retval++;
5323 break;
5328 return(retval);
5332 /*----------------------------------------------------------------------
5333 Post news via NNTP or inews
5335 Args: env -- envelope of message to post
5336 body -- body of message to post
5338 Returns: -1 if failed or cancelled, 1 if succeeded
5340 WARNING: This call function has the side effect of writing the message
5341 to the lmc.so object.
5342 ----*/
5344 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5345 void (*pipecb_f)(PIPE_S *, int, void *))
5347 char *error_mess, error_buf[200], **news_servers;
5348 char **servers_to_use;
5349 int we_cancel = 0, server_no = 0, done_posting = 0;
5350 void *orig_822_output;
5351 BODY *bp = NULL;
5353 error_buf[0] = '\0';
5354 we_cancel = busy_cue("Posting news", NULL, 0);
5356 dprint((4, "Posting: [%s]\n",
5357 (header && header->env && header->env->newsgroups)
5358 ? header->env->newsgroups : "?"));
5360 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5361 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5362 && ps_global->VAR_NNTP_SERVER[0][0])){
5363 /*---------- NNTP server defined ----------*/
5364 error_mess = NULL;
5365 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5366 && alt_nntp_servers[0][0])
5367 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5370 * Install our rfc822 output routine
5372 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5373 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5374 (void *)post_rfc822_output);
5376 server_no = 0;
5377 news_servers = (char **)fs_get(2 * sizeof(char *));
5378 news_servers[1] = NULL;
5379 while(!done_posting && servers_to_use[server_no] &&
5380 servers_to_use[server_no][0]){
5381 news_servers[0] = servers_to_use[server_no];
5382 ps_global->noshow_error = 1;
5383 #ifdef DEBUG
5384 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5385 #else
5386 sending_stream = nntp_open(news_servers, 0L);
5387 #endif
5388 ps_global->noshow_error = 0;
5390 if(sending_stream != NULL) {
5391 unsigned short save_encoding, added_encoding;
5394 * Fake that we've got clearance from the transport agent
5395 * for 8bit transport for the benefit of our output routines...
5397 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5398 && (bp = first_text_8bit(body))){
5399 int i;
5401 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5404 if(i > ENCMAX){ /* no empty encoding slots! */
5405 bp = NULL;
5407 else {
5408 added_encoding = i;
5409 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5410 save_encoding = bp->encoding;
5411 bp->encoding = added_encoding;
5416 * Set global header pointer so we can get at it later...
5418 send_header = header;
5419 ps_global->noshow_error = 1;
5420 if(nntp_mail(sending_stream, header->env, body) == 0)
5421 snprintf(error_mess = error_buf, sizeof(error_buf),
5422 _("Error posting message: %s"),
5423 sending_stream->reply);
5424 else{
5425 done_posting = 1;
5426 error_buf[0] = '\0';
5427 error_mess = NULL;
5430 error_buf[sizeof(error_buf)-1] = '\0';
5431 smtp_close(sending_stream);
5432 ps_global->noshow_error = 0;
5433 sending_stream = NULL;
5434 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5435 body_encodings[added_encoding] = NULL;
5436 bp->encoding = save_encoding;
5439 } else {
5440 /*---- Open of NNTP connection failed ------ */
5441 snprintf(error_mess = error_buf, sizeof(error_buf),
5442 _("Error connecting to news server: %s"),
5443 ps_global->c_client_error);
5444 error_buf[sizeof(error_buf)-1] = '\0';
5445 dprint((1, error_buf));
5447 server_no++;
5449 fs_give((void **)&news_servers);
5450 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5451 } else {
5452 /*----- Post via local mechanism -------*/
5453 #ifdef _WINDOWS
5454 snprintf(error_mess = error_buf, sizeof(error_buf),
5455 _("Can't post, NNTP-server must be defined!"));
5456 #else /* UNIX */
5457 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5458 pipecb_f);
5459 #endif
5462 if(we_cancel)
5463 cancel_busy_cue(0);
5465 if(error_mess){
5466 if(lmc.so && !lmc.all_written)
5467 so_give(&lmc.so); /* clean up any fcc data */
5469 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5470 return(-1);
5473 lmc.all_written = 1;
5474 return(1);
5478 /* ----------------------------------------------------------------------
5479 Figure out command to start local SMTP agent
5481 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5483 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5485 ----*/
5486 char *
5487 smtp_command(char *errbuf, size_t errbuflen)
5489 #ifdef _WINDOWS
5490 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5491 && ps_global->VAR_SMTP_SERVER[0][0]))
5492 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5494 errbuf[errbuflen-1] = '\0';
5495 #else /* UNIX */
5496 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5497 #define SENDTMPLEN 256
5498 char tmp[SENDTMPLEN];
5499 /* SENDTMPLEN == sizeof(tmp) */
5500 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
5501 (SENDTMPLEN-3)/2, SENDMAILFLAGS);
5502 return(cpystr(tmp));
5503 # else
5504 strncpy(errbuf, _("No default posting command."), errbuflen);
5505 errbuf[errbuflen-1] = '\0';
5506 # endif
5507 #endif
5509 return(NULL);
5514 #ifndef _WINDOWS
5515 /*----------------------------------------------------------------------
5516 Hand off given message to local posting agent
5518 Args: envelope -- The envelope for the BCC and debugging
5519 header -- The text of the message header
5520 errbuf -- buffer for reporting errors (assumed non-NULL)
5521 len -- Length of errbuf
5523 ----*/
5525 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5526 char *errbuf, size_t len,
5527 void (*bigresult_f) (char *, int),
5528 void (*pipecb_f)(PIPE_S *, int, void *))
5530 #ifdef DF_SENDMAIL_PATH
5531 char cmd_buf[256];
5532 #endif
5533 char *cmd = NULL;
5536 * A bit of complicated policy implemented here.
5537 * There are two posting variables sendmail-path and smtp-server.
5538 * Precedence is in that order.
5539 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5540 * Precedence is in that order.
5541 * Said differently, the order goes something like what's below.
5543 * NOTE: the fixed/command-line/user precendence handling is also
5544 * indicated by what's pointed to by ps_global->VAR_*, but since
5545 * that also includes the global defaults, it's not sufficient.
5548 if(ps_global->FIX_SENDMAIL_PATH
5549 && ps_global->FIX_SENDMAIL_PATH[0]){
5550 cmd = ps_global->FIX_SENDMAIL_PATH;
5552 else if(!(ps_global->FIX_SMTP_SERVER
5553 && ps_global->FIX_SMTP_SERVER[0])){
5554 if(ps_global->COM_SENDMAIL_PATH
5555 && ps_global->COM_SENDMAIL_PATH[0]){
5556 cmd = ps_global->COM_SENDMAIL_PATH;
5558 else if(!(ps_global->COM_SMTP_SERVER
5559 && ps_global->COM_SMTP_SERVER[0])){
5560 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5561 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5562 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5563 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5564 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5565 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5566 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5567 else
5568 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5570 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5571 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5572 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5573 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5574 if(ps_global->GLO_SENDMAIL_PATH
5575 && ps_global->GLO_SENDMAIL_PATH[0]){
5576 cmd = ps_global->GLO_SENDMAIL_PATH;
5578 #ifdef DF_SENDMAIL_PATH
5580 * This defines the default method of posting. So,
5581 * unless we're told otherwise use it...
5583 else if(!(ps_global->GLO_SMTP_SERVER
5584 && ps_global->GLO_SMTP_SERVER[0])){
5585 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5586 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5588 #endif
5593 *errbuf = '\0';
5594 if(cmd){
5595 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5597 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5598 return(1);
5600 else
5601 return(0);
5606 /*----------------------------------------------------------------------
5607 Hand off given message to local posting agent
5609 Args: envelope -- The envelope for the BCC and debugging
5610 header -- The text of the message header
5611 errbuf -- buffer for reporting errors (assumed non-NULL)
5612 errbuflen -- Length of errbuf
5614 Fork off mailer process and pipe the message into it
5615 Called to post news via Inews when NNTP is unavailable
5617 ----*/
5618 char *
5619 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5620 size_t errbuflen,
5621 void (*bigresult_f) (char *, int),
5622 void (*pipecb_f)(PIPE_S *, int, void *))
5624 char *err = NULL;
5625 #ifdef SENDNEWS
5626 char *s;
5627 char tmp[200];
5629 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5630 *s = '\0';
5632 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5633 strncpy(tmp, err, sizeof(tmp)-1);
5634 tmp[sizeof(tmp)-1] = '\0';
5635 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5636 SENDNEWS, tmp);
5639 if(s)
5640 *s = ' '; /* restore the date */
5642 #else /* !SENDNEWS */ /* this is the default case */
5643 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5644 err[errbuflen-1] = '\0';
5645 #endif /* !SENDNEWS */
5646 return(err);
5651 /*----------------------------------------------------------------------
5652 Hand off message to local MTA; it parses recipients from 822 header
5654 Args: header -- struct containing header data
5655 body -- struct containing message body data
5656 cmd -- command to use for handoff (%s says where file should go)
5657 errs -- pointer to buf to hold errors
5659 ----*/
5660 char *
5661 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5662 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5663 void (*pipecb_f)(PIPE_S *, int, void *))
5665 char *result = NULL;
5666 PIPE_S *pipe;
5668 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5670 if((pipe = open_system_pipe(cmd, &result, NULL,
5671 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5672 0, pipecb_f, pipe_report_error)) != NULL){
5673 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5674 (TCPSTREAM *) pipe)){
5675 strncpy(errs, _("Error posting."), errslen-1);
5676 errs[errslen-1] = '\0';
5679 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5680 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5681 if(result && bigresult_f)
5682 (*bigresult_f)(result, CM_BR_ERROR);
5685 else
5686 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5688 if(result){
5689 our_unlink(result);
5690 fs_give((void **)&result);
5693 return(*errs ? errs : NULL);
5698 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5699 * pipes rather than a tcp stream
5701 long
5702 pine_pipe_soutr_nl (void *stream, char *s)
5704 long rv = T;
5705 char *p;
5706 size_t n;
5708 while(*s && rv){
5709 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0){
5710 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5711 if(rv < 0){
5712 if(errno != EINTR){
5713 rv = 0;
5714 break;
5717 else{
5718 s += rv;
5719 n -= rv;
5723 if(p && rv){
5724 s = p + 2; /* write UNIX EOL */
5725 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5726 if(rv < 0 && errno != EINTR){
5727 rv = 0;
5728 break;
5731 else
5732 break;
5735 return(rv);
5737 #endif
5740 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5744 * helpful def's
5746 #define S(X) ((PIPE_S *)(X))
5747 #define GETBUFLEN (4 * MAILTMPLEN)
5750 void *
5751 piped_smtp_open (char *host, char *service, long unsigned int port)
5753 char *postcmd;
5754 void *rv = NULL;
5756 if(strucmp(host, "localhost")){
5757 char tmp[MAILTMPLEN];
5758 /* MAILTMPLEN = sizeof(tmp) */
5759 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5760 MAILTMPLEN-50, host);
5761 tmp[sizeof(tmp)-1] = '\0';
5762 mm_log(tmp, ERROR);
5764 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5765 rv = open_system_pipe(postcmd, NULL, NULL,
5766 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5767 0, NULL, pipe_report_error);
5768 fs_give((void **) &postcmd);
5770 else
5771 mm_log(ps_global->c_client_error, ERROR);
5773 return(rv);
5777 void *
5778 piped_aopen (NETMBX *mb, char *service, char *user)
5780 return(NULL);
5785 * piped_soutr - Replacement for tcp_soutr that writes one of our
5786 * pipes rather than a tcp stream
5788 long
5789 piped_soutr (void *stream, char *s)
5791 return(piped_sout(stream, s, strlen(s)));
5796 * piped_sout - Replacement for tcp_soutr that writes one of our
5797 * pipes rather than a tcp stream
5799 long
5800 piped_sout (void *stream, char *s, long unsigned int size)
5802 int i, o;
5804 if(S(stream)->out.d < 0)
5805 return(0L);
5807 if((i = (int) size) != 0){
5808 while((o = write(S(stream)->out.d, s, i)) != i)
5809 if(o < 0){
5810 if(errno != EINTR){
5811 piped_abort(stream);
5812 return(0L);
5815 else{
5816 s += o; /* try again, fix up counts */
5817 i -= o;
5821 return(1L);
5826 * piped_getline - Replacement for tcp_getline that reads one
5827 * of our pipes rather than a tcp pipe
5829 * C-client expects that the \r\n will be stripped off.
5831 char *
5832 piped_getline (void *stream)
5834 static int cnt;
5835 static char *ptr;
5836 int n, m;
5837 char *ret, *s, *sp, c = '\0', d;
5839 if(S(stream)->in.d < 0)
5840 return(NULL);
5842 if(!S(stream)->tmp){ /* initialize! */
5843 /* alloc space to collect input (freed in close_system_pipe) */
5844 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5845 memset(S(stream)->tmp, 0, GETBUFLEN);
5846 cnt = -1;
5849 while(cnt < 0){
5850 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5851 if(errno != EINTR){
5852 piped_abort(stream);
5853 return(NULL);
5856 if(cnt == 0){
5857 piped_abort(stream);
5858 return(NULL);
5861 ptr = S(stream)->tmp;
5864 s = ptr;
5865 n = 0;
5866 while(cnt--){
5867 d = *ptr++;
5868 if((c == '\015') && (d == '\012')){
5869 ret = (char *)fs_get (n--);
5870 memcpy(ret, s, n);
5871 ret[n] = '\0';
5872 return(ret);
5875 n++;
5876 c = d;
5878 /* copy partial string from buffer */
5879 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5880 /* get more data */
5881 while(cnt < 0){
5882 memset(S(stream)->tmp, 0, GETBUFLEN);
5883 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5884 if(errno != EINTR){
5885 fs_give((void **) &ret);
5886 piped_abort(stream);
5887 return(NULL);
5890 if(cnt == 0){
5891 if(n > 0)
5892 ret[n-1] = '\0'; /* to try to get error message logged */
5893 else{
5894 piped_abort(stream);
5895 return(NULL);
5899 ptr = S(stream)->tmp;
5902 if(c == '\015' && *ptr == '\012'){
5903 ptr++;
5904 cnt--;
5905 ret[n - 1] = '\0'; /* tie off string with null */
5907 else if ((s = piped_getline(stream)) != NULL) {
5908 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5909 memcpy(ret, sp, n); /* copy first part */
5910 memcpy(ret + n, s, m); /* and second part */
5911 fs_give((void **) &sp); /* flush first part */
5912 fs_give((void **) &s); /* flush second part */
5913 ret[n + m] = '\0'; /* tie off string with null */
5916 return(ret);
5921 * piped_close - Replacement for tcp_close that closes pipes to our
5922 * child rather than a tcp connection
5924 void
5925 piped_close(void *stream)
5928 * Uninstall our hooks into smtp_send since it's being used by
5929 * the nntp driver as well...
5931 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5936 * piped_abort - close down the pipe we were using to post
5938 void
5939 piped_abort(void *stream)
5941 if(S(stream)->in.d >= 0){
5942 close(S(stream)->in.d);
5943 S(stream)->in.d = -1;
5946 if(S(stream)->out.d){
5947 close(S(stream)->out.d);
5948 S(stream)->out.d = -1;
5953 char *
5954 piped_host(void *stream)
5956 return(ps_global->hostname ? ps_global->hostname : "localhost");
5960 unsigned long
5961 piped_port(void *stream)
5963 return(0L);