* Create help for explaining how encrypted password file support
[alpine.git] / pith / send.c
blob4620d1aa271c9fb2947107fd78fb5c2997e26c3a
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2014 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/send.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/store.h"
24 #include "../pith/mimedesc.h"
25 #include "../pith/context.h"
26 #include "../pith/status.h"
27 #include "../pith/folder.h"
28 #include "../pith/bldaddr.h"
29 #include "../pith/pipe.h"
30 #include "../pith/mailview.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/list.h"
33 #include "../pith/filter.h"
34 #include "../pith/reply.h"
35 #include "../pith/addrstring.h"
36 #include "../pith/rfc2231.h"
37 #include "../pith/stream.h"
38 #include "../pith/util.h"
39 #include "../pith/adrbklib.h"
40 #include "../pith/options.h"
41 #include "../pith/busy.h"
42 #include "../pith/text.h"
43 #include "../pith/imap.h"
44 #include "../pith/ablookup.h"
45 #include "../pith/sort.h"
46 #include "../pith/smime.h"
48 #include "../c-client/smtp.h"
49 #include "../c-client/nntp.h"
52 /* this is used in pine_send and pine_simple_send */
53 /* name::type::canedit::writehdr::localcopy::rcptto */
54 PINEFIELD pf_template[] = {
55 {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
56 {"From", Address, 0, 1, 1, 0},
57 {"Reply-To", Address, 0, 1, 1, 0},
58 {TONAME, Address, 1, 1, 1, 1},
59 {CCNAME, Address, 1, 1, 1, 1},
60 {"bcc", Address, 1, 0, 1, 1},
61 {"Newsgroups", FreeText, 1, 1, 1, 0},
62 {"Fcc", Fcc, 1, 0, 0, 0},
63 {"Lcc", Address, 1, 0, 1, 1},
64 {"Attchmnt", Attachment, 1, 1, 1, 0},
65 {SUBJNAME, Subject, 1, 1, 1, 0},
66 {"References", FreeText, 0, 1, 1, 0},
67 {"Date", FreeText, 0, 1, 1, 0},
68 {"In-Reply-To", FreeText, 0, 1, 1, 0},
69 {"Message-ID", FreeText, 0, 1, 1, 0},
70 {PRIORITYNAME, FreeText, 0, 1, 1, 0},
71 {"User-Agent", FreeText, 0, 1, 1, 0},
72 {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
73 {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
74 {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
75 {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
76 {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
77 {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
78 {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
79 {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
80 {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
81 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
82 {"X-X-Sender", Address, 0, 1, 1, 0},
83 #endif
84 {NULL, FreeText}
88 PRIORITY_S priorities[] = {
89 {1, "Highest"},
90 {2, "High"},
91 {3, "Normal"},
92 {4, "Low"},
93 {5, "Lowest"},
94 {0, NULL}
98 #define ctrl(c) ((c) & 0x1f)
100 /* which message part to test for xliteration */
101 typedef enum {MsgBody, HdrText} MsgPart;
105 * Internal prototypes
107 long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
108 int l_flush_net(int);
109 int l_putc(int);
110 int pine_write_header_line(char *, char *, STORE_S *);
111 int pine_write_params(PARAMETER *, STORE_S *);
112 char *tidy_smtp_mess(char *, char *, char *, size_t);
113 int lmc_body_header_line(char *, int);
114 int lmc_body_header_finish(void);
115 int pwbh_finish(int, STORE_S *);
116 int sent_percent(void);
117 unsigned short *setup_avoid_table(void);
118 #ifndef _WINDOWS
119 int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
120 void (*)(PIPE_S *, int, void *));
121 char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
122 void (*)(PIPE_S *, int, void *));
123 char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
124 void (*)(PIPE_S *, int, void *));
125 long pine_pipe_soutr_nl(void *, char *);
126 #endif
127 char *smtp_command(char *, size_t);
128 void *piped_smtp_open(char *, char *, unsigned long);
129 void *piped_aopen(NETMBX *, char *, char *);
130 long piped_soutr(void *, char *);
131 long piped_sout(void *, char *, unsigned long);
132 char *piped_getline(void *);
133 void piped_close(void *);
134 void piped_abort(void *);
135 char *piped_host(void *);
136 unsigned long piped_port(void *);
137 char *posting_characterset(void *, char *, MsgPart);
138 int body_is_translatable(void *, char *);
139 int text_is_translatable(void *, char *);
140 int dummy_putc(int);
141 unsigned long *init_charsetchecker(char *);
142 int representable_in_charset(unsigned long, char *);
143 char *most_preferred_charset(unsigned long);
146 * Storage object where the FCC (or postponed msg) is to be written.
147 * This is amazingly bogus. Much work was done to put messages
148 * together and encode them as they went to the tmp file for sendmail
149 * or into the SMTP slot (especially for for DOS, to prevent a temporary
150 * file (and needlessly copying the message).
152 * HOWEVER, since there's no piping into c-client routines
153 * (particularly mail_append() which copies the fcc), the fcc will have
154 * to be copied to disk. This global tells pine's copy of the rfc822
155 * output functions where to also write the message bytes for the fcc.
156 * With piping in the c-client we could just have two pipes to shove
157 * down rather than messing with damn copies. FIX THIS!
159 * The function open_fcc, locates the actual folder and creates it if
160 * requested before any mailing or posting is done.
162 struct local_message_copy lmc;
166 * Locally global pointer to stream used for sending/posting.
167 * It's also used to indicate when/if we write the Bcc: field in
168 * the header.
170 static SENDSTREAM *sending_stream = NULL;
173 static struct hooks {
174 void *rfc822_out; /* Message outputter */
175 } sending_hooks;
178 static FILE *verbose_send_output = NULL;
179 static long send_bytes_sent, send_bytes_to_send;
180 static METAENV *send_header = NULL;
183 * Hooks for prompts and confirmations
185 int (*pith_opt_daemon_confirm)(void);
188 static NETDRIVER piped_io = {
189 piped_smtp_open, /* open a connection */
190 piped_aopen, /* open an authenticated connection */
191 piped_getline, /* get a line */
192 NULL, /* get a buffer */
193 piped_soutr, /* output pushed data */
194 piped_sout, /* output string */
195 piped_close, /* close connection */
196 piped_host, /* return host name */
197 piped_host, /* remotehost */
198 piped_port, /* return port number */
199 piped_host /* return local host (NOTE: same as host!) */
204 * Since c-client preallocates, it's necessary here to define a limit
205 * such that we don't blow up in c-client (see rfc822_address_line()).
207 #define MAX_SINGLE_ADDR MAILTMPLEN
209 #define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
213 * Phone home hash controls
215 #define PH_HASHBITS 24
216 #define PH_MAXHASH (1<<(PH_HASHBITS))
220 * postponed_stream - return stream associated with postponed messages
221 * in argument.
224 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
226 MAILSTREAM *stream = NULL;
227 CONTEXT_S *p_cntxt = NULL;
228 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
229 int exists;
231 if(!(streamp && mbox))
232 return(0);
234 *streamp = NULL;
237 * find default context to look for folder...
239 * The "mbox" is assumed to be local if we're given what looks
240 * like an absolute path. This is different from Goto/Save
241 * where we do alot of work to interpret paths relative to the
242 * server. This reason is to support all the pre-4.00 pinerc'
243 * that specified a path and because there's yet to be a way
244 * in c-client to specify otherwise in the face of a remote
245 * context.
247 if(!is_absolute_path(mbox)
248 && !(p_cntxt = default_save_context(ps_global->context_list)))
249 p_cntxt = ps_global->context_list;
251 /* check to see if the folder exists, the user wants to continue
252 * and that we can actually read something in...
254 exists = folder_name_exists(p_cntxt, mbox, &fullname);
255 if(fullname)
256 mbox = fullname;
258 if(exists & FEX_ISFILE){
259 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
260 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
262 * The mbox is relative to the home directory.
263 * Make it absolute so we can compare it to
264 * stream->mailbox.
266 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
267 SIZEOF_20KBUF);
268 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
269 tmp[sizeof(tmp)-1] = '\0';
272 if((stream = ps_global->mail_stream)
273 && !(stream->mailbox
274 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
275 || (*tmp == '{'
276 && same_stream(tmp, stream)
277 && (p = strchr(tmp, '}'))
278 && (q = strchr(stream->mailbox,'}'))
279 && !strcmp(p + 1, q + 1)))))
280 stream = NULL;
282 if(!stream){
283 stream = context_open(p_cntxt, NULL, mbox,
284 SP_USEPOOL|SP_TEMPUSE, NULL);
285 if(stream && !stream->halfopen){
286 if(stream->nmsgs > 0)
287 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
289 if(checknmsgs && stream->nmsgs < 1){
290 pine_mail_close(stream);
291 exists = 0;
292 stream = NULL;
295 else{
296 q_status_message2(SM_ORDER | SM_DING, 3, 3,
297 _("Can't open %s mailbox: %s"), type, mbox);
298 if(stream)
299 pine_mail_close(stream);
301 exists = 0;
302 stream = NULL;
306 else{
307 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
308 q_status_message1(SM_ORDER | SM_DING, 3, 3,
309 _("%s message folder doesn't exist!"), type);
313 if(fullname)
314 fs_give((void **) &fullname);
316 *streamp = stream;
318 return(exists);
323 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
324 struct mail_bodystruct **body, char **fcc, char **lcc,
325 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
326 ACTION_S **role, int flags, STORE_S *so)
328 MAILSTREAM *stream;
329 ENVELOPE *e = NULL;
330 BODY *b;
331 PART *part;
332 PINEFIELD *pf;
333 gf_io_t pc;
334 char *extras, **fields, **values, *p;
335 char *hdrs[2], *h, *charset;
336 char **smtp_servers = NULL, **nntp_servers = NULL;
337 int i, pine_generated = 0, our_replyto = 0;
338 int added_to_role = 0;
339 unsigned gbpt_flags = GBPT_NONE;
340 MESSAGECACHE *mc;
342 if(!(streamp && *streamp))
343 return(redraft_cleanup(streamp, TRUE, flags));
345 stream = *streamp;
347 if(flags & REDRAFT_HTML)
348 gbpt_flags |= GBPT_HTML_OK;
350 /* grok any user-defined or non-c-client headers */
351 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
354 * The custom headers to look for in the suspended message should
355 * have been stored in the X-Our-Headers header. So first we get
356 * that list. If we can't find it (version that stored the
357 * message < 4.30) then we use the global custom list.
359 hdrs[0] = OUR_HDRS_LIST;
360 hdrs[1] = NULL;
361 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
362 int commas = 0;
363 char **list;
364 char *hdrval = NULL;
366 if((hdrval = strindex(h, ':')) != NULL){
367 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
368 hdrval++)
372 /* count elements in list */
373 for(p = hdrval; p && *p; p++)
374 if(*p == ',')
375 commas++;
377 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
379 *custom = parse_custom_hdrs(list, Replace);
380 add_defaults_from_list(*custom,
381 ps_global->VAR_CUSTOM_HDRS);
382 free_list_array(&list);
385 if(*custom && !(*custom)->name){
386 free_customs(*custom);
387 *custom = NULL;
390 fs_give((void **)&h);
393 if(!*custom)
394 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
396 #define INDEX_FCC 0
397 #define INDEX_POSTERR 1
398 #define INDEX_REPLYUID 2
399 #define INDEX_REPLYMBOX 3
400 #define INDEX_SMTP 4
401 #define INDEX_NNTP 5
402 #define INDEX_CURSORPOS 6
403 #define INDEX_OUR_REPLYTO 7
404 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
405 #define FIELD_COUNT 9
407 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
410 * Having these two fields separated isn't the slickest, but
411 * converting the pointer array for fetchheader_lines() to
412 * a list of structures or some such for simple_header_parse()
413 * is too goonie. We could do something like re-use c-client's
414 * PARAMETER struct which is a simple char * pairing, but that
415 * doesn't make sense to pass to fetchheader_lines()...
417 fields = (char **) fs_get((size_t) i * sizeof(char *));
418 values = (char **) fs_get((size_t) i * sizeof(char *));
419 memset(fields, 0, (size_t) i * sizeof(char *));
420 memset(values, 0, (size_t) i * sizeof(char *));
422 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
423 fields[++i] = "X-Post-Error"; /* posting errors too */
424 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
425 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
426 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
427 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
428 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
429 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
430 fields[++i] = "Lcc"; /* Lcc: too... */
431 if(++i != FIELD_COUNT)
432 panic("Fix FIELD_COUNT");
434 for(pf = *custom; pf && pf->name; pf = pf->next)
435 if(!pf->standard)
436 fields[i++] = pf->name; /* assign custom fields */
438 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
439 simple_header_parse(extras, fields, values);
440 fs_give((void **) &extras);
443 * translate RFC 1522 strings,
444 * starting with "Lcc" field
446 for(i = INDEX_LCC; fields[i]; i++)
447 if(values[i]){
448 size_t len;
449 char *bufp, *biggerbuf = NULL;
451 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
452 len++;
453 biggerbuf = (char *)fs_get(len * sizeof(char));
454 bufp = biggerbuf;
456 else{
457 bufp = tmp_20k_buf;
458 len = SIZEOF_20KBUF;
461 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
463 if(p == tmp_20k_buf){
464 fs_give((void **)&values[i]);
465 values[i] = cpystr(p);
468 if(biggerbuf)
469 fs_give((void **)&biggerbuf);
472 for(pf = *custom, i = FIELD_COUNT;
473 pf && pf->name;
474 pf = pf->next){
475 if(pf->standard){
477 * Because the value is already in the envelope.
479 pf->cstmtype = NoMatch;
480 continue;
483 if(values[i]){ /* use this instead of default */
484 if(pf->textbuf)
485 fs_give((void **)&pf->textbuf);
487 pf->textbuf = values[i]; /* freed in pine_send! */
489 else if(pf->textbuf) /* was erased before postpone */
490 fs_give((void **)&pf->textbuf);
492 i++;
495 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
496 pine_generated = 1; /* we put it there? */
499 * Since c-client fills in the reply_to field in the envelope
500 * even if there isn't a Reply-To header in the message we
501 * have to work around that. When we postpone we add
502 * a second header that has value "Empty" if there really
503 * was a Reply-To and it was empty. It has the
504 * value "Full" if we put the Reply-To contents there
505 * intentionally (and it isn't empty).
507 if(values[INDEX_OUR_REPLYTO]){
508 if(values[INDEX_OUR_REPLYTO][0] == 'E')
509 our_replyto = 'E'; /* we put an empty one there */
510 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
511 our_replyto = 'F'; /* we put it there */
513 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
516 if(fcc) /* fcc: special case... */
517 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
518 else if(values[INDEX_FCC])
519 fs_give((void **) &values[INDEX_FCC]);
521 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
522 q_status_message(SM_ORDER|SM_DING, 4, 4,
523 values[INDEX_POSTERR]);
524 fs_give((void **) &values[INDEX_POSTERR]);
527 if(values[INDEX_REPLYUID]){
528 if(reply)
529 *reply = build_reply_uid(values[INDEX_REPLYUID]);
531 fs_give((void **) &values[INDEX_REPLYUID]);
533 if(values[INDEX_REPLYMBOX] && reply && *reply)
534 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
536 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
537 (*reply)->origmbox = cpystr((*reply)->mailbox);
540 if(values[INDEX_REPLYMBOX])
541 fs_give((void **) &values[INDEX_REPLYMBOX]);
543 if(values[INDEX_SMTP]){
544 char *q;
545 size_t cnt = 0;
548 * Turn the space delimited list of smtp servers into
549 * a char ** list.
551 p = values[INDEX_SMTP];
553 if(!*p || isspace((unsigned char) *p))
554 cnt++;
555 } while(*p++);
557 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
558 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
560 cnt = 0;
561 q = p = values[INDEX_SMTP];
563 if(!*p || isspace((unsigned char) *p)){
564 if(*p){
565 *p = '\0';
566 smtp_servers[cnt++] = cpystr(q);
567 *p = ' ';
568 q = p+1;
570 else
571 smtp_servers[cnt++] = cpystr(q);
573 } while(*p++);
575 fs_give((void **) &values[INDEX_SMTP]);
578 if(values[INDEX_NNTP]){
579 char *q;
580 size_t cnt = 0;
583 * Turn the space delimited list of smtp nntp into
584 * a char ** list.
586 p = values[INDEX_NNTP];
588 if(!*p || isspace((unsigned char) *p))
589 cnt++;
590 } while(*p++);
592 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
593 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
595 cnt = 0;
596 q = p = values[INDEX_NNTP];
598 if(!*p || isspace((unsigned char) *p)){
599 if(*p){
600 *p = '\0';
601 nntp_servers[cnt++] = cpystr(q);
602 *p = ' ';
603 q = p+1;
605 else
606 nntp_servers[cnt++] = cpystr(q);
608 } while(*p++);
610 fs_give((void **) &values[INDEX_NNTP]);
613 if(values[INDEX_CURSORPOS]){
615 * The redraft cursor position is written as two fields
616 * separated by a space. First comes the name of the
617 * header field we're in, or just a ":" if we're in the
618 * body. Then comes the offset into that header or into
619 * the body.
621 if(redraft_pos){
622 char *q1, *q2;
624 *redraft_pos
625 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
626 (*redraft_pos)->offset = 0L;
628 q1 = skip_white_space(values[INDEX_CURSORPOS]);
629 if(*q1 && (q2 = strindex(q1, SPACE))){
630 *q2 = '\0';
631 (*redraft_pos)->hdrname = cpystr(q1);
632 q1 = skip_white_space(q2+1);
633 if(*q1)
634 (*redraft_pos)->offset = atol(q1);
636 else
637 (*redraft_pos)->hdrname = cpystr(":");
640 fs_give((void **) &values[INDEX_CURSORPOS]);
643 if(lcc)
644 *lcc = values[INDEX_LCC];
645 else
646 fs_give((void **) &values[INDEX_LCC]);
649 fs_give((void **)&fields);
650 fs_give((void **)&values);
652 *outgoing = copy_envelope(e);
655 * If the postponed message has a From which is different from
656 * the default, it is either because allow-changing-from is on
657 * or because there was a role with a from that allowed it to happen.
658 * If allow-changing-from is not on, put this back in a role
659 * so that it will be allowed again in pine_send.
661 if(role && *role == NULL &&
662 !ps_global->never_allow_changing_from &&
663 *outgoing){
665 * Now check to see if the from is different from default from.
667 ADDRESS *deffrom;
669 deffrom = generate_from();
670 if(!((*outgoing)->from &&
671 address_is_same(deffrom, (*outgoing)->from) &&
672 ((!(deffrom->personal && deffrom->personal[0]) &&
673 !((*outgoing)->from->personal &&
674 (*outgoing)->from->personal[0])) ||
675 (deffrom->personal && (*outgoing)->from->personal &&
676 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
678 *role = (ACTION_S *)fs_get(sizeof(**role));
679 memset((void *)*role, 0, sizeof(**role));
680 if(!(*outgoing)->from)
681 (*outgoing)->from = mail_newaddr();
683 (*role)->from = (*outgoing)->from;
684 (*outgoing)->from = NULL;
685 added_to_role++;
688 mail_free_address(&deffrom);
692 * Look at each empty address and see if the user has specified
693 * a default for that field or not. If they have, that means
694 * they have erased it before postponing, so they won't want
695 * the default to come back. If they haven't specified a default,
696 * then the default should be generated in pine_send. We prevent
697 * the default from being assigned by assigning an empty address
698 * to the variable here.
700 * BUG: We should do this for custom Address headers, too, but
701 * there isn't such a thing yet.
703 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
704 (*outgoing)->to = mail_newaddr();
705 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
706 (*outgoing)->cc = mail_newaddr();
707 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
708 (*outgoing)->bcc = mail_newaddr();
710 if(our_replyto == 'E'){
711 /* user erased reply-to before postponing */
712 if((*outgoing)->reply_to)
713 mail_free_address(&(*outgoing)->reply_to);
716 * If empty is not the normal default, make the outgoing
717 * reply_to be an emtpy address. If it is default, leave it
718 * as NULL and the default will be used.
720 if(hdr_is_in_list("reply-to", *custom)){
721 PINEFIELD pf;
723 pf.name = "reply-to";
724 set_default_hdrval(&pf, *custom);
725 if(pf.textbuf){
726 if(pf.textbuf[0]) /* empty is not default */
727 (*outgoing)->reply_to = mail_newaddr();
729 fs_give((void **)&pf.textbuf);
733 else if(our_replyto == 'F'){
734 int add_to_role = 0;
737 * The reply-to is real. If it is different from the default
738 * reply-to, put it in the role so that it will show up when
739 * the user edits.
741 if(hdr_is_in_list("reply-to", *custom)){
742 PINEFIELD pf;
743 char *str;
745 pf.name = "reply-to";
746 set_default_hdrval(&pf, *custom);
747 if(pf.textbuf && pf.textbuf[0]){
748 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
749 if(!strcmp(str, pf.textbuf)){
750 /* standard value, leave it alone */
753 else /* not standard, put in role */
754 add_to_role++;
756 fs_give((void **)&str);
759 else /* not standard, put in role */
760 add_to_role++;
762 if(pf.textbuf)
763 fs_give((void **)&pf.textbuf);
765 else /* not standard, put in role */
766 add_to_role++;
768 if(add_to_role && role && (*role == NULL || added_to_role)){
769 if(*role == NULL){
770 added_to_role++;
771 *role = (ACTION_S *)fs_get(sizeof(**role));
772 memset((void *)*role, 0, sizeof(**role));
775 (*role)->replyto = (*outgoing)->reply_to;
776 (*outgoing)->reply_to = NULL;
779 else{
780 /* this is a bogus c-client generated replyto */
781 if((*outgoing)->reply_to)
782 mail_free_address(&(*outgoing)->reply_to);
785 if((smtp_servers || nntp_servers)
786 && role && (*role == NULL || added_to_role)){
787 if(*role == NULL){
788 *role = (ACTION_S *)fs_get(sizeof(**role));
789 memset((void *)*role, 0, sizeof(**role));
792 if(smtp_servers)
793 (*role)->smtp = smtp_servers;
794 if(nntp_servers)
795 (*role)->nntp = nntp_servers;
798 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
799 (*outgoing)->subject = cpystr("");
801 if(!pine_generated){
803 * Now, this is interesting. We should have found
804 * the "fcc:" field if pine wrote the message being
805 * redrafted. Hence, we probably can't trust the
806 * "originator" type fields, so we'll blast them and let
807 * them get set later in pine_send. This should allow
808 * folks with custom or edited From's and such to still
809 * use redraft reasonably, without inadvertently sending
810 * messages that appear to be "From" others...
812 if((*outgoing)->from)
813 mail_free_address(&(*outgoing)->from);
816 * Ditto for Reply-To and Sender...
818 if((*outgoing)->reply_to)
819 mail_free_address(&(*outgoing)->reply_to);
821 if((*outgoing)->sender)
822 mail_free_address(&(*outgoing)->sender);
825 if(!pine_generated || !(flags & REDRAFT_DEL)){
828 * Generate a fresh message id for pretty much the same
829 * reason From and such got wacked...
830 * Also, if we're coming from a form letter, we need to
831 * generate a different id each time.
833 if((*outgoing)->message_id)
834 fs_give((void **)&(*outgoing)->message_id);
836 (*outgoing)->message_id = generate_message_id();
839 if(b && b->type != TYPETEXT){
840 if(b->type == TYPEMULTIPART){
841 if(strucmp(b->subtype, "mixed")){
842 q_status_message1(SM_INFO, 3, 4,
843 "Converting Multipart/%s to Multipart/Mixed",
844 b->subtype);
845 fs_give((void **)&b->subtype);
846 b->subtype = cpystr("mixed");
849 else{
850 q_status_message2(SM_ORDER | SM_DING, 3, 4,
851 "Unable to resume type %s/%s message",
852 body_types[b->type], b->subtype);
853 return(redraft_cleanup(streamp, TRUE, flags));
857 gf_set_so_writec(&pc, so);
859 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
860 *body = copy_body(NULL, b);
861 part = (*body)->nested.part;
862 part->body.contents.text.data = (void *)so;
863 set_mime_type_by_grope(&part->body);
864 if(part->body.type != TYPETEXT){
865 q_status_message2(SM_ORDER | SM_DING, 3, 4,
866 "Unable to resume; first part is non-text: %s/%s",
867 body_types[part->body.type],
868 part->body.subtype);
869 return(redraft_cleanup(streamp, TRUE, flags));
872 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
873 /* let outgoing routines decide on charset */
874 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
875 set_parameter(&part->body.parameter, "charset", NULL);
877 fs_give((void **) &charset);
880 ps_global->postpone_no_flow = 1;
882 get_body_part_text(stream, &b->nested.part->body,
883 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
884 ps_global->postpone_no_flow = 0;
886 if(!fetch_contents(stream, cont_msg, NULL, *body))
887 q_status_message(SM_ORDER | SM_DING, 3, 4,
888 _("Error including all message parts"));
890 else{
891 *body = mail_newbody();
892 (*body)->type = TYPETEXT;
893 if(b->subtype)
894 (*body)->subtype = cpystr(b->subtype);
896 if((charset = parameter_val(b->parameter,"charset")) != NULL){
897 /* let outgoing routines decide on charset */
898 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
899 fs_give((void **) &charset);
900 else{
901 (*body)->parameter = mail_newbody_parameter();
902 (*body)->parameter->attribute = cpystr("charset");
903 if(utf8_charset(charset)){
904 fs_give((void **) &charset);
905 (*body)->parameter->value = cpystr("UTF-8");
907 else
908 (*body)->parameter->value = charset;
912 (*body)->contents.text.data = (void *)so;
913 ps_global->postpone_no_flow = 1;
914 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
915 NULL, NULL, gbpt_flags);
916 ps_global->postpone_no_flow = 0;
919 gf_clear_so_writec(so);
921 /* We have what we want, blast this message... */
922 if((flags & REDRAFT_DEL)
923 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
924 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
925 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
927 else
928 return(redraft_cleanup(streamp, TRUE, flags));
930 return(redraft_cleanup(streamp, FALSE, flags));
934 /*----------------------------------------------------------------------
935 Clear deleted messages from given stream and expunge if necessary
937 Args: stream --
938 problem --
940 ----*/
942 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
944 MAILSTREAM *stream;
946 if(!(streamp && *streamp))
947 return(0);
949 if(!problem && streamp && (stream = *streamp)){
950 if(stream->nmsgs){
951 ps_global->expunge_in_progress = 1;
952 mail_expunge(stream); /* clean out deleted */
953 ps_global->expunge_in_progress = 0;
956 if(!stream->nmsgs){ /* close and delete folder */
957 int do_the_broach = 0;
958 char *mbox = NULL;
960 if(stream){
961 if(stream->original_mailbox && stream->original_mailbox[0])
962 mbox = cpystr(stream->original_mailbox);
963 else if(stream->mailbox && stream->mailbox[0])
964 mbox = cpystr(stream->mailbox);
967 /* if it is current, we have to change folders */
968 if(stream == ps_global->mail_stream)
969 do_the_broach++;
972 * This is the stream to the empty postponed-msgs folder.
973 * We are going to delete the folder in a second. It is
974 * probably preferable to unselect the mailbox and leave
975 * this stream open for re-use instead of actually closing it,
976 * so we do that if possible.
978 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
980 * This does the UNSELECT on the stream. A NULL
981 * return should mean that something went wrong and
982 * a mail_close already happened, so that should have
983 * cleaned things up in the callback.
985 if((stream=mail_open(stream, stream->mailbox,
986 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
987 /* now close it so it is put into the stream cache */
988 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
989 pine_mail_close(stream);
992 else
993 pine_mail_actually_close(stream);
995 *streamp = NULL;
997 if(do_the_broach){
998 ps_global->mail_stream = NULL; /* already closed above */
1001 if(mbox && !pine_mail_delete(NULL, mbox))
1002 q_status_message1(SM_ORDER|SM_DING, 3, 3,
1003 /* TRANSLATORS: Arg is a mailbox name */
1004 _("Can't delete %s"), mbox);
1006 if(mbox)
1007 fs_give((void **) &mbox);
1011 return(!problem);
1015 /*----------------------------------------------------------------------
1016 Parse the given header text for any given fields
1018 Args: text -- Text to parse for fcc and attachments refs
1019 fields -- array of field names to look for
1020 values -- array of pointer to save values to, returned NULL if
1021 fields isn't in text.
1023 This function simply looks for the given fields in the given header
1024 text string.
1025 NOTE: newlines are expected CRLF, and we'll ignore continuations
1026 ----*/
1027 void
1028 simple_header_parse(char *text, char **fields, char **values)
1030 int i, n;
1031 char *p, *t;
1033 for(i = 0; fields[i]; i++)
1034 values[i] = NULL; /* clear values array */
1036 /*---- Loop until the end of the header ----*/
1037 for(p = text; *p; ){
1038 for(i = 0; fields[i]; i++) /* find matching field? */
1039 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1040 for(p += n + 1; *p; p++){ /* find start of value */
1041 if(*p == '\015' && *(p+1) == '\012'
1042 && !isspace((unsigned char) *(p+2)))
1043 break;
1045 if(!isspace((unsigned char) *p))
1046 break; /* order here is key... */
1049 if(!values[i]){ /* if we haven't already */
1050 values[i] = fs_get(strlen(text) + 1);
1051 values[i][0] = '\0'; /* alloc space for it */
1054 if(*p && *p != '\015'){ /* non-blank value. */
1055 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1056 while(*p){ /* check for cont'n lines */
1057 if(*p == '\015' && *(p+1) == '\012'){
1058 if(isspace((unsigned char) *(p+2))){
1059 p += 2;
1060 continue;
1062 else
1063 break;
1066 *t++ = *p++;
1069 *t = '\0';
1072 break;
1075 /* Skip to end of line, what ever it was */
1076 for(; *p ; p++)
1077 if(*p == '\015' && *(p+1) == '\012'){
1078 p += 2;
1079 break;
1085 /*----------------------------------------------------------------------
1086 build a fresh REPLY_S from the given string (see pine_send for format)
1088 Args: s -- "X-Reply-UID" header value
1090 Returns: filled in REPLY_S or NULL on parse error
1091 ----*/
1092 REPLY_S *
1093 build_reply_uid(char *s)
1095 char *p, *prefix = NULL, *val, *seq, *mbox;
1096 int i, nseq, forwarded = 0;
1097 REPLY_S *reply = NULL;
1099 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1100 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1101 if(*s == '('){
1102 if(*(p = s + 1) == ')'){
1103 forwarded = 1;
1105 else{
1106 for(; isdigit(*p); p++)
1109 if(*p == ' '){
1110 *p++ = '\0';
1112 if((i = atoi(s+1)) && i < strlen(p)){
1113 prefix = p;
1114 *(p += i) = '\0';
1117 else
1118 return(NULL);
1121 if(*++p == '(' && *++p){
1122 for(seq = p; isdigit(*p); p++)
1125 if(*p == ' '){
1126 *p++ = '\0';
1127 for(val = p; isdigit(*p); p++)
1130 if(*p == ' '){
1131 *p++ = '\0';
1133 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1134 && (p = strchr(p, ')')) && *(mbox = ++p)){
1135 imapuid_t *uidl;
1137 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1138 for(i = 0; i < nseq; i++)
1139 if((p = strchr(seq,',')) != NULL){
1140 *p = '\0';
1141 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1142 seq = ++p;
1143 else
1144 break;
1146 else if((p = strchr(seq, ')')) != NULL){
1147 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1148 i++;
1150 break;
1153 if(i == nseq){
1154 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1155 memset(reply, 0, sizeof(REPLY_S));
1156 reply->uid = 1;
1157 reply->data.uid.validity = strtoul(val, NULL, 10);
1158 if(forwarded)
1159 reply->forwarded = 1;
1160 else
1161 reply->prefix = cpystr(prefix);
1163 reply->mailbox = cpystr(mbox);
1164 uidl[nseq] = 0;
1165 reply->data.uid.msgs = uidl;
1167 else
1168 fs_give((void **) &uidl);
1175 return(reply);
1180 * pine_new_env - allocate a new METAENV and fill it in.
1182 METAENV *
1183 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1185 int cnt, i, stdcnt;
1186 char *p;
1187 PINEFIELD *pfields, *pf, **sending_order;
1188 METAENV *header;
1190 header = (METAENV *) fs_get(sizeof(METAENV));
1192 /* how many fields are there? */
1193 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1196 stdcnt = cnt;
1198 for(pf = custom; pf; pf = pf->next)
1199 cnt++;
1201 /* temporary PINEFIELD array */
1202 i = (cnt + 1) * sizeof(PINEFIELD);
1203 pfields = (PINEFIELD *)fs_get((size_t) i);
1204 memset(pfields, 0, (size_t) i);
1206 i = (cnt + 1) * sizeof(PINEFIELD *);
1207 sending_order = (PINEFIELD **)fs_get((size_t) i);
1208 memset(sending_order, 0, (size_t) i);
1210 header->env = outgoing;
1211 header->local = pfields;
1212 header->custom = custom;
1213 header->sending_order = sending_order;
1215 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1216 # define NN 4
1217 #else
1218 # define NN 3
1219 #endif
1221 /* initialize pfield */
1222 pf = pfields;
1223 for(i=0; i < stdcnt; i++, pf++){
1225 pf->name = cpystr(pf_template[i].name);
1226 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
1227 /* slide string over so it is Sender instead of X-X-Sender */
1228 for(p=pf->name; *(p+1); p++)
1229 *p = *(p+4);
1231 pf->type = pf_template[i].type;
1232 pf->canedit = pf_template[i].canedit;
1233 pf->rcptto = pf_template[i].rcptto;
1234 pf->writehdr = pf_template[i].writehdr;
1235 pf->localcopy = pf_template[i].localcopy;
1236 pf->extdata = NULL; /* unused */
1237 pf->next = pf + 1;
1239 switch(pf->type){
1240 case FreeText:
1241 switch(i){
1242 case N_AUTHRCVD:
1243 sending_order[0] = pf;
1244 break;
1246 case N_NEWS:
1247 pf->text = &outgoing->newsgroups;
1248 sending_order[1] = pf;
1249 break;
1251 case N_DATE:
1252 pf->text = (char **) &outgoing->date;
1253 sending_order[2] = pf;
1254 break;
1256 case N_INREPLY:
1257 pf->text = &outgoing->in_reply_to;
1258 sending_order[NN+9] = pf;
1259 break;
1261 case N_MSGID:
1262 pf->text = &outgoing->message_id;
1263 sending_order[NN+10] = pf;
1264 break;
1266 case N_REF: /* won't be used here */
1267 sending_order[NN+11] = pf;
1268 break;
1270 case N_PRIORITY:
1271 sending_order[NN+12] = pf;
1272 break;
1274 case N_USERAGENT:
1275 pf->text = &pf->textbuf;
1276 pf->textbuf = generate_user_agent();
1277 sending_order[NN+13] = pf;
1278 break;
1280 case N_POSTERR: /* won't be used here */
1281 sending_order[NN+14] = pf;
1282 break;
1284 case N_RPLUID: /* won't be used here */
1285 sending_order[NN+15] = pf;
1286 break;
1288 case N_RPLMBOX: /* won't be used here */
1289 sending_order[NN+16] = pf;
1290 break;
1292 case N_SMTP: /* won't be used here */
1293 sending_order[NN+17] = pf;
1294 break;
1296 case N_NNTP: /* won't be used here */
1297 sending_order[NN+18] = pf;
1298 break;
1300 case N_CURPOS: /* won't be used here */
1301 sending_order[NN+19] = pf;
1302 break;
1304 case N_OURREPLYTO: /* won't be used here */
1305 sending_order[NN+20] = pf;
1306 break;
1308 case N_OURHDRS: /* won't be used here */
1309 sending_order[NN+21] = pf;
1310 break;
1312 default:
1313 q_status_message1(SM_ORDER,3,3,
1314 "Internal error: 1)FreeText header %s", comatose(i));
1315 break;
1318 break;
1320 case Attachment:
1321 break;
1323 case Address:
1324 switch(i){
1325 case N_FROM:
1326 sending_order[3] = pf;
1327 pf->addr = &outgoing->from;
1328 break;
1330 case N_TO:
1331 sending_order[NN+2] = pf;
1332 pf->addr = &outgoing->to;
1333 if(tobufpp)
1334 (*tobufpp) = &pf->scratch;
1336 break;
1338 case N_CC:
1339 sending_order[NN+3] = pf;
1340 pf->addr = &outgoing->cc;
1341 break;
1343 case N_BCC:
1344 sending_order[NN+4] = pf;
1345 pf->addr = &outgoing->bcc;
1346 break;
1348 case N_REPLYTO:
1349 sending_order[NN+1] = pf;
1350 pf->addr = &outgoing->reply_to;
1351 break;
1353 case N_LCC: /* won't be used here */
1354 sending_order[NN+7] = pf;
1355 break;
1357 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1358 case N_SENDER:
1359 sending_order[4] = pf;
1360 pf->addr = &outgoing->sender;
1361 break;
1362 #endif
1364 case N_NOBODY: /* won't be used here */
1365 sending_order[NN+5] = pf;
1366 break;
1368 default:
1369 q_status_message1(SM_ORDER,3,3,
1370 "Internal error: Address header %s", comatose(i));
1371 break;
1373 break;
1375 case Fcc:
1376 sending_order[NN+8] = pf;
1377 pf->text = fccp;
1378 break;
1380 case Subject:
1381 sending_order[NN+6] = pf;
1382 pf->text = &outgoing->subject;
1383 break;
1385 default:
1386 q_status_message1(SM_ORDER,3,3,
1387 "Unknown header type %d in pine_new_send", (void *)pf->type);
1388 break;
1392 if(((--pf)->next = custom) != NULL){
1393 i--;
1396 * NOTE: "i" is assumed to now index first custom field in sending
1397 * order.
1399 for(pf = pf->next; pf && pf->name; pf = pf->next){
1400 if(pf->standard)
1401 continue;
1403 pf->canedit = 1;
1404 pf->rcptto = 0;
1405 pf->writehdr = 1;
1406 pf->localcopy = 1;
1408 switch(pf->type){
1409 case Address:
1410 if(pf->addr){ /* better be set */
1411 char *addr = NULL;
1412 BuildTo bldto;
1414 bldto.type = Str;
1415 bldto.arg.str = pf->textbuf;
1417 sending_order[i++] = pf;
1418 /* change default text into an ADDRESS */
1419 /* strip quotes around whole default */
1420 removing_trailing_white_space(pf->textbuf);
1421 (void)removing_double_quotes(pf->textbuf);
1422 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1423 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1424 fs_give((void **)&addr);
1425 if(pf->textbuf)
1426 fs_give((void **)&pf->textbuf);
1429 break;
1431 case FreeText:
1432 sending_order[i++] = pf;
1433 pf->text = &pf->textbuf;
1434 break;
1436 default:
1437 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1438 (void *)pf->type);
1439 break;
1445 return(header);
1449 void
1450 pine_free_env(METAENV **menv)
1452 int cnt;
1455 if((*menv)->local){
1456 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1459 for(; cnt >= 0; cnt--){
1460 if((*menv)->local[cnt].textbuf)
1461 fs_give((void **) &(*menv)->local[cnt].textbuf);
1463 fs_give((void **) &(*menv)->local[cnt].name);
1466 fs_give((void **) &(*menv)->local);
1469 if((*menv)->sending_order)
1470 fs_give((void **) &(*menv)->sending_order);
1472 fs_give((void **) menv);
1476 /*----------------------------------------------------------------------
1477 Check for addresses the user is not permitted to send to, or probably
1478 doesn't want to send to
1480 Returns: 0 if OK
1481 1 if there are only empty groups
1482 -1 if the message shouldn't be sent
1484 Queues a message indicating what happened
1485 ---*/
1487 check_addresses(METAENV *header)
1489 PINEFIELD *pf;
1490 ADDRESS *a;
1491 int send_daemon = 0, rv = CA_EMPTY;
1493 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1494 for(pf = header->local; pf && pf->name; pf = pf->next)
1495 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1496 for(a = *pf->addr; a != NULL; a = a->next){
1497 if(a->host && (a->host[0] == '.'
1498 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1499 && a->host[0] == '@'))){
1500 q_status_message2(SM_ORDER, 4, 7,
1501 /* TRANSLATORS: First arg is the address we can't
1502 send to, second arg is "not in addressbook". */
1503 _("Can't send to address %s: %s"),
1504 a->mailbox,
1505 (a->host[0] == '.')
1506 ? a->host
1507 : _("not in addressbook"));
1508 return(CA_BAD);
1510 else if(ps_global->restricted
1511 && !address_is_us(*pf->addr, ps_global)){
1512 q_status_message(SM_ORDER, 3, 3,
1513 "Restricted demo version of Alpine. You may only send mail to yourself");
1514 return(CA_BAD);
1516 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1517 send_daemon = 1;
1518 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1520 else if(a->mailbox && a->host){
1521 rv = CA_OK;
1525 return(rv);
1530 * If this isn't general enough we can modify it. The value passed in
1531 * is expected to be one of the desc settings from the priorities array,
1532 * like "High". The header value is X-Priority: 2 (High)
1533 * or something similar. If value doesn't match any of the values then
1534 * the actual value is used instead.
1536 PINEFIELD *
1537 set_priority_header(METAENV *header, char *value)
1539 PINEFIELD *pf;
1541 for(pf = header->local; pf && pf->name; pf = pf->next)
1542 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1543 break;
1545 if(pf){
1546 if(pf->textbuf)
1547 fs_give((void **) &pf->textbuf);
1549 if(value){
1550 PRIORITY_S *p;
1552 for(p = priorities; p && p->desc; p++)
1553 if(!strcmp(p->desc, value))
1554 break;
1556 if(p && p->desc){
1557 char buf[100];
1559 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1560 pf->textbuf = cpystr(buf);
1562 else
1563 pf->textbuf = cpystr(value);
1566 return pf;
1570 /*----------------------------------------------------------------------
1571 Set answered flags for messages specified by reply structure
1573 Args: reply --
1575 Returns: with appropriate flags set and index cache entries suitably tweeked
1576 ----*/
1577 void
1578 update_answered_flags(REPLY_S *reply)
1580 char *seq = NULL, *p;
1581 long i, ourstream = 0, we_cancel = 0;
1582 MAILSTREAM *stream = NULL;
1584 /* nothing to flip in a pseudo reply */
1585 if(reply && (reply->msgno || reply->uid)){
1586 int j;
1587 MAILSTREAM *m;
1590 * If an established stream will do, use it, else
1591 * build one unless we have an array of msgno's...
1593 * I was just mimicking what was already here. I don't really
1594 * understand why we use strcmp instead of same_stream_and_mailbox().
1595 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1596 * Hubert 2003-07-09
1598 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1599 m = ps_global->s_pool.streams[j];
1600 if(m && reply->mailbox && m->mailbox
1601 && !strcmp(reply->mailbox, m->mailbox))
1602 stream = m;
1605 if(!stream && reply->msgno)
1606 return;
1609 * This is here only for people who ran pine4.42 and are
1610 * processing postponed mail from 4.42 now. Pine4.42 saved the
1611 * original mailbox name in the canonical name's position in
1612 * the postponed-msgs folder so it won't match the canonical
1613 * name from the stream.
1615 if(!stream && (!reply->origmbox ||
1616 (reply->mailbox &&
1617 !strcmp(reply->origmbox, reply->mailbox))))
1618 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1620 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1621 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1622 if(!stream){
1623 if((stream = pine_mail_open(NULL,
1624 reply->origmbox ? reply->origmbox
1625 : reply->mailbox,
1626 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1627 NULL)) != NULL){
1628 ourstream++;
1630 else{
1631 if(we_cancel)
1632 cancel_busy_cue(0);
1634 return;
1638 if(stream->uid_validity == reply->data.uid.validity){
1639 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1640 if(i){
1641 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1642 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1645 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1646 SIZEOF_20KBUF-(p-tmp_20k_buf));
1647 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1650 if(reply->forwarded){
1652 * $Forwarded is a regular keyword so we only try to
1653 * set it if the stream allows keywords.
1654 * We could mess up if the stream has keywords but just
1655 * isn't allowing anymore and $Forwarded already exists,
1656 * but what are the odds?
1658 if(stream && stream->kwd_create)
1659 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1660 FORWARDED_FLAG,
1661 ST_SET | ((reply->uid) ? ST_UID : 0L));
1663 else
1664 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1665 "\\ANSWERED",
1666 ST_SET | ((reply->uid) ? ST_UID : 0L));
1668 if(seq)
1669 fs_give((void **)&seq);
1672 if(ourstream)
1673 pine_mail_close(stream); /* clean up dangling stream */
1675 if(we_cancel)
1676 cancel_busy_cue(0);
1682 * phone_home_from - make phone home request's from address IMpersonal.
1683 * Doesn't include user's personal name.
1685 ADDRESS *
1686 phone_home_from(void)
1688 ADDRESS *addr = mail_newaddr();
1689 char tmp[32];
1691 /* garble up mailbox name */
1692 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1693 tmp[sizeof(tmp)-1] = '\0';
1694 addr->mailbox = cpystr(tmp);
1695 addr->host = cpystr(ps_global->maildomain);
1696 return(addr);
1701 * one-way-hash a username into an 8-digit decimal number
1703 * Corey Satten, corey@cac.washington.edu, 7/15/98
1705 unsigned int
1706 phone_home_hash(char *s)
1708 unsigned int h;
1710 for (h=0; *s; ++s) {
1711 if (h & 1)
1712 h = (h>>1) | (PH_MAXHASH/2);
1713 else
1714 h = (h>>1);
1716 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1719 return (h);
1723 /*----------------------------------------------------------------------
1724 Call the mailer, SMTP, sendmail or whatever
1726 Args: header -- full header (envelope and local parts) of message to send
1727 body -- The full body of the message including text
1728 alt_smtp_servers --
1729 verbosefile -- non-null means caller wants verbose interaction and the resulting
1730 output file name to be returned
1732 Returns: -1 if failed, 1 if succeeded
1733 ----*/
1735 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1736 int flags, void (*bigresult_f)(char *, int),
1737 void (*pipecb_f)(PIPE_S *, int, void *))
1739 char error_buf[200], *error_mess = NULL, *postcmd;
1740 ADDRESS *a;
1741 ENVELOPE *fake_env = NULL;
1742 int addr_error_count, we_cancel = 0;
1743 long smtp_opts = 0L;
1744 char *verbose_file = NULL;
1745 BODY *bp = NULL;
1746 PINEFIELD *pf;
1747 BODY *origBody = body;
1749 dprint((4, "Sending mail...\n"));
1751 /* Check for any recipients */
1752 for(pf = header->local; pf && pf->name; pf = pf->next)
1753 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1754 break;
1756 if(!pf){
1757 q_status_message(SM_ORDER,3,3,
1758 _("Can't send message. No recipients specified!"));
1759 return(0);
1762 #ifdef SMIME
1763 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1764 int result;
1766 STORE_S *so = lmc.so;
1767 lmc.so = NULL;
1769 result = 1;
1771 if(ps_global->smime->do_sign)
1772 result = sign_outgoing_message(header, &body, 0);
1774 /* need to free new body from encrypt if sign fails? */
1775 if(result && ps_global->smime->do_encrypt)
1776 result = encrypt_outgoing_message(header, &body);
1778 lmc.so = so;
1780 if(!result)
1781 return 0;
1783 #endif
1785 /* set up counts and such to keep track sent percentage */
1786 send_bytes_sent = 0;
1787 gf_filter_init(); /* zero piped byte count, 'n */
1788 send_bytes_to_send = send_body_size(body); /* count body bytes */
1789 ps_global->c_client_error[0] = error_buf[0] = '\0';
1790 we_cancel = busy_cue(_("Sending mail"),
1791 send_bytes_to_send ? sent_percent : NULL, 0);
1793 #ifndef _WINDOWS
1795 /* try posting via local "<mta> <-t>" if specified */
1796 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1797 if(error_buf[0])
1798 error_mess = error_buf;
1800 goto done;
1803 #endif
1806 * If the user's asked for it, and we find that the first text
1807 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1809 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1810 smtp_opts |= SOP_8BITMIME;
1812 #ifdef DEBUG
1813 #ifndef DEBUGJOURNAL
1814 if(debug > 5 || (flags & CM_VERBOSE))
1815 #endif
1816 smtp_opts |= SOP_DEBUG;
1817 #endif
1819 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1820 smtp_opts |= SOP_DSN;
1821 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1822 if(flags & CM_DSN_DELAY)
1823 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1824 if(flags & CM_DSN_SUCCESS)
1825 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1828 * If it isn't Never, then we're always going to let them
1829 * know about failures. This means we don't allow for the
1830 * possibility of setting delay or success without failure.
1832 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1834 if(flags & CM_DSN_FULL)
1835 smtp_opts |= SOP_DSN_RETURN_FULL;
1841 * Set global header pointer so post_rfc822_output can get at it when
1842 * it's called back from c-client's sending routine...
1844 send_header = header;
1847 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1848 * The purpose is to give smtp_mail the list for SMTP RCPT when
1849 * there are recipients in pine's METAENV that are outside c-client's
1850 * envelope.
1852 * NOTE: If there aren't any, don't bother. Dealt with it below.
1854 for(pf = header->local; pf && pf->name; pf = pf->next)
1855 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1856 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1857 || *pf->addr == header->env->bcc))
1858 break;
1860 if(pf && pf->name){
1861 ADDRESS **tail;
1863 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1864 memset(fake_env, 0, sizeof(ENVELOPE));
1865 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1866 tail = &(fake_env->to);
1867 for(pf = header->local; pf && pf->name; pf = pf->next)
1868 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1869 *tail = rfc822_cpy_adr(*pf->addr);
1870 while(*tail)
1871 tail = &((*tail)->next);
1876 * Install our rfc822 output routine
1878 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1879 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1882 * Allow for verbose posting
1884 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1885 (void *) pine_smtp_verbose_out);
1888 * We do this because we want mm_log to put the error message into
1889 * c_client_error instead of showing it itself.
1891 ps_global->noshow_error = 1;
1894 * OK, who posts what? We tried an mta_handoff above, but there
1895 * was either none specified or we decided not to use it. So,
1896 * if there's an smtp-server defined anywhere,
1898 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1899 /*---------- SMTP ----------*/
1900 dprint((4, "call_mailer: via TCP (%s)\n",
1901 alt_smtp_servers[0]));
1902 TIME_STAMP("smtp-open start (tcp)", 1);
1903 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1905 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1906 && ps_global->VAR_SMTP_SERVER[0][0]){
1907 /*---------- SMTP ----------*/
1908 dprint((4, "call_mailer: via TCP\n"));
1909 TIME_STAMP("smtp-open start (tcp)", 1);
1910 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1912 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1913 char *cmdlist[2];
1915 /*----- Send via LOCAL SMTP agent ------*/
1916 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1918 TIME_STAMP("smtp-open start (pipe)", 1);
1919 fs_give((void **) &postcmd);
1920 cmdlist[0] = "localhost";
1921 cmdlist[1] = NULL;
1922 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1923 SMTPTCPPORT, smtp_opts);
1924 /* BUG: should provide separate stderr output! */
1927 ps_global->noshow_error = 0;
1929 TIME_STAMP("smtp open", 1);
1930 if(sending_stream){
1931 unsigned short save_encoding, added_encoding;
1933 dprint((1, "Opened SMTP server \"%s\"\n",
1934 net_host(sending_stream->netstream)
1935 ? net_host(sending_stream->netstream) : "?"));
1937 if(flags & CM_VERBOSE){
1938 TIME_STAMP("verbose start", 1);
1939 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1940 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1941 if(!smtp_verbose(sending_stream)){
1942 snprintf(error_mess = error_buf, sizeof(error_buf),
1943 "Mail not sent. VERBOSE mode error%s%.50s.",
1944 (sending_stream && sending_stream->reply)
1945 ? ": ": "",
1946 (sending_stream && sending_stream->reply)
1947 ? sending_stream->reply : "");
1948 error_buf[sizeof(error_buf)-1] = '\0';
1951 else{
1952 our_unlink(verbose_file);
1953 strncpy(error_mess = error_buf,
1954 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1955 error_buf[sizeof(error_buf)-1] = '\0';
1958 else{
1959 strncpy(error_mess = error_buf,
1960 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1961 error_buf[sizeof(error_buf)-1] = '\0';
1964 TIME_STAMP("verbose end", 1);
1968 * Before we actually send data, see if we have to protect
1969 * the first text body part from getting encoded. We protect
1970 * it from getting encoded in "pine_rfc822_output_body" by
1971 * temporarily inventing a synonym for ENC8BIT...
1972 * This works like so:
1973 * Suppose bp->encoding is set to ENC8BIT.
1974 * We change that here to some unused value (added_encoding) and
1975 * set body_encodings[added_encoding] to "8BIT".
1976 * Then post_rfc822_output is called which calls
1977 * pine_rfc822_output_body. Inside that routine
1978 * pine_write_body_header writes out the encoding for the
1979 * part. Normally it would see encoding == ENC8BIT and it would
1980 * change that to QUOTED-PRINTABLE, but since encoding has been
1981 * set to added_encoding it uses body_encodings[added_encoding]
1982 * which is "8BIT" instead. Then the actual body is written by
1983 * pine_write_body_header which does not do the gf_8bit_qp
1984 * filtering because encoding != ENC8BIT (instead it's equal
1985 * to added_encoding).
1987 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1988 && sending_stream->protocol.esmtp.eightbit.want){
1989 int i;
1991 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1994 if(i > ENCMAX){ /* no empty encoding slots! */
1995 bp = NULL;
1997 else {
1998 added_encoding = i;
1999 body_encodings[added_encoding] = body_encodings[ENC8BIT];
2000 save_encoding = bp->encoding;
2001 bp->encoding = added_encoding;
2005 if(sending_stream->protocol.esmtp.ok
2006 && sending_stream->protocol.esmtp.dsn.want
2007 && !sending_stream->protocol.esmtp.dsn.ok)
2008 q_status_message(SM_ORDER,3,3,
2009 _("Delivery Status Notification not available from this server."));
2011 TIME_STAMP("smtp start", 1);
2012 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2013 fake_env ? fake_env : header->env, body)){
2015 snprintf(error_buf, sizeof(error_buf),
2016 _("Mail not sent. Sending error%s%s"),
2017 (sending_stream && sending_stream->reply) ? ": ": ".",
2018 (sending_stream && sending_stream->reply)
2019 ? sending_stream->reply : "");
2020 error_buf[sizeof(error_buf)-1] = '\0';
2021 dprint((1, error_buf));
2022 addr_error_count = 0;
2023 if(fake_env){
2024 for(a = fake_env->to; a != NULL; a = a->next)
2025 if(a->error != NULL){
2026 if(addr_error_count++ < MAX_ADDR_ERROR){
2029 * Too complicated to figure out which header line
2030 * has the error in the fake_env case, so just
2031 * leave cursor at default.
2035 if(error_mess) /* previous error? */
2036 q_status_message(SM_ORDER, 4, 7, error_mess);
2038 error_mess = tidy_smtp_mess(a->error,
2039 _("Mail not sent: %.80s"),
2040 error_buf, sizeof(error_buf));
2043 dprint((1, "Send Error: \"%s\"\n",
2044 a->error));
2047 else{
2048 for(pf = header->local; pf && pf->name; pf = pf->next)
2049 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2050 for(a = *pf->addr; a != NULL; a = a->next)
2051 if(a->error != NULL){
2052 if(addr_error_count++ < MAX_ADDR_ERROR){
2054 if(error_mess) /* previous error? */
2055 q_status_message(SM_ORDER, 4, 7, error_mess);
2057 error_mess = tidy_smtp_mess(a->error,
2058 _("Mail not sent: %.80s"),
2059 error_buf, sizeof(error_buf));
2062 dprint((1, "Send Error: \"%s\"\n",
2063 a->error));
2067 if(!error_mess)
2068 error_mess = error_buf;
2071 /* repair modified "body_encodings" array? */
2072 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2073 && sending_stream->protocol.esmtp.eightbit.want){
2074 body_encodings[added_encoding] = NULL;
2075 bp->encoding = save_encoding;
2078 TIME_STAMP("smtp closing", 1);
2079 smtp_close(sending_stream);
2080 sending_stream = NULL;
2081 TIME_STAMP("smtp done", 1);
2083 else if(!error_mess){
2084 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2085 ps_global->c_client_error[0] ? ": " : "",
2086 ps_global->c_client_error);
2087 error_buf[sizeof(error_buf)-1] = '\0';
2090 if(verbose_file){
2091 if(verbose_send_output){
2092 TIME_STAMP("verbose start", 1);
2093 fclose(verbose_send_output);
2094 verbose_send_output = NULL;
2095 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2097 if(bigresult_f)
2098 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2100 TIME_STAMP("verbose end", 1);
2103 fs_give((void **)&verbose_file);
2107 * Restore original 822 emitter...
2109 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2111 if(fake_env)
2112 mail_free_envelope(&fake_env);
2114 done:
2116 #ifdef SMIME
2117 /* Free replacement encrypted body */
2118 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2120 if(body->type == TYPEMULTIPART){
2121 /* Just get rid of first part, it's actually origBody */
2122 void *x = body->nested.part;
2124 body->nested.part = body->nested.part->next;
2126 fs_give(&x);
2129 pine_free_body(&body);
2131 #endif
2133 if(we_cancel)
2134 cancel_busy_cue(0);
2136 TIME_STAMP("call_mailer done", 1);
2137 /*-------- Did message make it ? ----------*/
2138 if(error_mess){
2139 /*---- Error sending mail -----*/
2140 if(lmc.so && !lmc.all_written)
2141 so_give(&lmc.so);
2143 if(error_mess){
2144 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2145 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2148 return(-1);
2150 else{
2151 lmc.all_written = 1;
2152 return(1);
2158 * write_postponed - exported method to write the given message
2159 * to the postponed folder
2162 write_postponed(METAENV *header, struct mail_bodystruct *body)
2164 char **pp, *folder;
2165 int rv = 0, sz;
2166 CONTEXT_S *fcc_cntxt = NULL;
2167 PINEFIELD *pf;
2168 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2170 if(!ps_global->VAR_POSTPONED_FOLDER
2171 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2172 q_status_message(SM_ORDER | SM_DING, 3, 3,
2173 _("No postponed file defined"));
2174 return(-1);
2177 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2179 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2181 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2183 if(lmc.so){
2184 /* BUG: writem sufficient ? */
2185 for(pf = header->local; pf && pf->name; pf = pf->next)
2186 for(pp = writem; *pp; pp++)
2187 if(!strucmp(pf->name, *pp)){
2188 pf->localcopy = 1;
2189 pf->writehdr = 1;
2190 break;
2194 * Work around c-client reply-to bug. C-client will
2195 * return a reply_to in an envelope even if there is
2196 * no reply-to header field. We want to note here whether
2197 * the reply-to is real or not.
2199 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2200 for(pf = header->local; pf; pf = pf->next)
2201 if(!strcmp(pf->name, "Reply-To")){
2202 pf->writehdr = 1;
2203 pf->localcopy = 1;
2204 if(header->env->reply_to)
2205 pf->textbuf = cpystr("Full");
2206 else
2207 pf->textbuf = cpystr("Empty");
2211 * Write the list of custom headers to the
2212 * X-Our-Headers header so that we can recover the
2213 * list in redraft.
2215 sz = 0;
2216 for(pf = header->custom; pf && pf->name; pf = pf->next)
2217 sz += strlen(pf->name) + 1;
2219 if(sz){
2220 int i;
2221 char *pstart, *pend;
2223 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2226 pf->writehdr = 1;
2227 pf->localcopy = 1;
2228 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2229 pf->text = &pf->textbuf;
2230 pf->textbuf[sz] = '\0'; /* tie off overflow */
2231 /* note: "pf" overloaded */
2232 for(pf = header->custom; pf && pf->name; pf = pf->next){
2233 int r = sz - (pend - pstart); /* remaining buffer */
2235 if(r > 0 && r != sz){
2236 r--;
2237 *pend++ = ',';
2240 sstrncpy(&pend, pf->name, r);
2244 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2245 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2246 rv = -1;
2248 so_give(&lmc.so);
2250 else {
2251 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2252 "Can't allocate internal storage: %s ",
2253 error_description(errno));
2254 rv = -1;
2257 fs_give((void **) &folder);
2258 return(rv);
2263 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2265 if(fcc && *fcc){
2266 lmc.all_written = lmc.text_written = 0;
2267 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2268 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2270 else
2271 lmc.so = NULL;
2273 return(TRUE);
2278 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2280 int rv = TRUE;
2282 if(lmc.so){
2283 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2284 char label[50];
2286 strncpy(label, "Fcc", sizeof(label));
2287 label[sizeof(label)-1] = '\0';
2288 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2289 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2290 label[sizeof(label)-1] = '\0';
2293 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2295 else{
2296 rv = FALSE;
2299 so_give(&lmc.so);
2302 return(rv);
2306 /*----------------------------------------------------------------------
2307 Checks to make sure the fcc is available and can be opened
2309 Args: fcc -- the name of the fcc to create. It can't be NULL.
2310 fcc_cntxt -- Returns the context the fcc is in.
2311 force -- supress user option prompt
2313 Returns allocated storage object on success, NULL on failure
2314 ----*/
2315 STORE_S *
2316 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2318 int exists, ok = 0;
2320 ps_global->mm_log_error = 0;
2323 * check for fcc's existance...
2325 TIME_STAMP("open_fcc start", 1);
2326 if(!is_absolute_path(fcc) && context_isambig(fcc)
2327 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2328 int flip_dot = 0;
2331 * Don't want to preclude a user from Fcc'ing a .name'd folder
2333 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2334 flip_dot = 1;
2335 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2339 * We only want to set the "context" if fcc is an ambiguous
2340 * name. Otherwise, our "relativeness" rules for contexts
2341 * (implemented in context.c) might cause the name to be
2342 * interpreted in the wrong context...
2344 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2345 *fcc_cntxt = ps_global->context_list;
2347 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2348 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2349 if(force
2350 || (pith_opt_save_create_prompt
2351 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2353 ps_global->noshow_error = 1;
2355 if(context_create(*fcc_cntxt, NULL, fcc))
2356 ok++;
2358 ps_global->noshow_error = 0;
2360 else
2361 ok--; /* declined! */
2363 else
2364 ok++; /* found! */
2366 if(flip_dot)
2367 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2369 free_folder_list(*fcc_cntxt);
2371 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2372 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2373 ok++;
2375 else{
2376 if(force
2377 || (pith_opt_save_create_prompt
2378 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2380 ps_global->mm_log_error = 0;
2381 ps_global->noshow_error = 1;
2383 ok = pine_mail_create(NULL, fcc) != 0L;
2385 ps_global->noshow_error = 0;
2387 else
2388 ok--; /* declined! */
2392 TIME_STAMP("open_fcc done.", 1);
2393 if(ok > 0){
2394 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2396 else{
2397 int l1, l2, l3, wid, w;
2398 char *errstr, tmp[MAILTMPLEN];
2399 char *s1, *s2;
2401 if(ok == 0){
2402 if(ps_global->mm_log_error){
2403 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2404 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2406 l1 = strlen(s1);
2407 l2 = strlen(s2);
2408 l3 = strlen(ps_global->c_client_error);
2409 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2410 ? ps_global->ttyo->screen_cols : 80;
2411 w = wid - l1 - l2 - 5;
2413 snprintf(errstr = tmp, sizeof(tmp),
2414 "%.99s\"%.*s%.99s\".%.99s",
2416 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2417 ps_global->c_client_error,
2418 (l3 > w) ? "..." : "",
2419 s2);
2420 tmp[sizeof(tmp)-1] = '\0';
2423 else
2424 errstr = _("Fcc creation error. Message NOT sent or copied.");
2426 else
2427 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2429 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2432 return(NULL);
2436 /*----------------------------------------------------------------------
2437 mail_append() the fcc accumulated in temp_storage to proper destination
2439 Args: fcc -- name of folder
2440 fcc_cntxt -- context for folder
2441 temp_storage -- String of file where Fcc has been accumulated
2443 This copies the string of file to the actual folder, which might be IMAP
2444 or a disk folder. The temp_storage is freed after it is written.
2445 An error message is produced if this fails.
2446 ----*/
2448 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2449 MAILSTREAM *stream, char *label, char *flags)
2451 STRING msg;
2452 CONTEXT_S *cntxt;
2453 int we_cancel = 0;
2455 if(!tmp_storage)
2456 return(0);
2458 TIME_STAMP("write_fcc start.", 1);
2459 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2460 if(label && *label){
2461 char msg_buf[80];
2463 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2464 msg_buf[sizeof(msg_buf)-1] = '\0';
2465 strncat(msg_buf, label, sizeof(msg_buf)-10);
2466 we_cancel = busy_cue(msg_buf, NULL, 0);
2468 else
2469 we_cancel = busy_cue(NULL, NULL, 1);
2471 so_seek(tmp_storage, 0L, 0);
2474 * Before changing this note that these lines depend on the
2475 * definition of FCC_SOURCE.
2477 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2478 strlen((char *)so_text(tmp_storage)));
2480 cntxt = fcc_cntxt;
2482 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2483 cancel_busy_cue(-1);
2484 we_cancel = 0;
2486 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2487 "Write to \"%s\" FAILED!!!", fcc);
2488 dprint((1, "ERROR appending %s in \"%s\"",
2489 fcc ? fcc : "?",
2490 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2491 return(0);
2494 if(we_cancel)
2495 cancel_busy_cue(label ? 0 : -1);
2497 dprint((4, "done.\n"));
2498 TIME_STAMP("write_fcc done.", 1);
2499 return(1);
2504 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2505 * contained.
2507 BODY *
2508 first_text_8bit(struct mail_bodystruct *body)
2510 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2511 body = &body->nested.part->body;
2513 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2514 ? body : NULL);
2519 * Build and return the "From:" address for outbound messages from
2520 * global data...
2522 ADDRESS *
2523 generate_from(void)
2525 ADDRESS *addr = mail_newaddr();
2526 if(ps_global->VAR_PERSONAL_NAME){
2527 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2528 removing_leading_and_trailing_white_space(addr->personal);
2529 if(addr->personal[0] == '\0')
2530 fs_give((void **)&addr->personal);
2533 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2534 addr->host = cpystr(ps_global->maildomain);
2535 removing_leading_and_trailing_white_space(addr->mailbox);
2536 removing_leading_and_trailing_white_space(addr->host);
2537 return(addr);
2542 * set_mime_type_by_grope - sniff the given storage object to determine its
2543 * type, subtype, encoding, and charset
2545 * "Type" and "encoding" must be set before calling this routine.
2546 * If "type" is set to something other than TYPEOTHER on entry,
2547 * then that is the "type" we wish to use. Same for "encoding"
2548 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2549 * figure them out here. If "type" is already set, we also
2550 * leave subtype alone. If not, we figure out subtype here.
2551 * There is a chance that we will upgrade encoding to a "higher"
2552 * level. For example, if it comes in as 7BIT we may change
2553 * that to 8BIT if we find a From_ we want to escape.
2554 * We may also set the charset attribute if the type is TEXT.
2556 * NOTE: this is rather inefficient if the store object is a CharStar
2557 * but the win is all types are handled the same
2559 void
2560 set_mime_type_by_grope(struct mail_bodystruct *body)
2562 #define RBUFSZ (8193)
2563 unsigned char *buf, *p, *bol;
2564 register size_t n;
2565 long max_line = 0L,
2566 eight_bit_chars = 0L,
2567 line_so_far = 0L,
2568 len = 0L;
2569 STORE_S *so = (STORE_S *)body->contents.text.data;
2570 unsigned short new_encoding = ENCOTHER;
2571 int we_cancel = 0;
2572 #ifdef ENCODE_FROMS
2573 short froms = 0, dots = 0,
2574 bmap = 0x1, dmap = 0x1;
2575 #endif
2577 we_cancel = busy_cue(NULL, NULL, 1);
2579 buf = (unsigned char *)fs_get(RBUFSZ);
2580 so_seek(so, 0L, 0);
2582 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2585 buf[n] = '\0';
2587 if(n){ /* check first few bytes to look for magic numbers */
2588 if(body->type == TYPEOTHER){
2589 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2590 body->type = TYPEIMAGE;
2591 body->subtype = cpystr("GIF");
2593 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2594 && buf[2] == 0xFF && buf[3] == 0xE0
2595 && !strncmp((char *)&buf[6], "JFIF", 4)){
2596 body->type = TYPEIMAGE;
2597 body->subtype = cpystr("JPEG");
2599 else if((buf[0] == 'M' && buf[1] == 'M')
2600 || (buf[0] == 'I' && buf[1] == 'I')){
2601 body->type = TYPEIMAGE;
2602 body->subtype = cpystr("TIFF");
2604 else if((buf[0] == '%' && buf[1] == '!')
2605 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2606 body->type = TYPEAPPLICATION;
2607 body->subtype = cpystr("PostScript");
2609 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2610 body->type = TYPEAPPLICATION;
2611 body->subtype = cpystr("PDF");
2613 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2614 body->type = TYPEAUDIO;
2615 body->subtype = cpystr("Basic");
2617 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2618 && buf[2] == 0x16 && buf[3] == 0x00){
2619 body->type = TYPEAPPLICATION;
2620 body->subtype = cpystr("APPLEFILE");
2622 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2623 && buf[2] == 0x03 && buf[3] == 0x04){
2624 body->type = TYPEAPPLICATION;
2625 body->subtype = cpystr("ZIP");
2629 * if type was set above, but no encoding specified, go
2630 * ahead and make it BASE64...
2632 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2633 body->encoding = ENCBINARY;
2636 else{
2637 /* PROBLEM !!! */
2638 if(body->type == TYPEOTHER){
2639 body->type = TYPEAPPLICATION;
2640 body->subtype = cpystr("octet-stream");
2641 if(body->encoding == ENCOTHER)
2642 body->encoding = ENCBINARY;
2646 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2647 #if defined(DOS) || defined(OS2) /* for binary file detection */
2648 int lastchar = '\0';
2649 #define BREAKOUT 300 /* a value that a character can't be */
2650 #endif
2652 p = bol = buf;
2653 len = n;
2654 while (n--){
2655 /* Some people don't like quoted-printable caused by leading Froms */
2656 #ifdef ENCODE_FROMS
2657 Find_Froms(froms, dots, bmap, dmap, *p);
2658 #endif
2659 if(*p == '\n'){
2660 max_line = MAX(max_line, line_so_far + p - bol);
2661 bol = NULL; /* clear beginning of line */
2662 line_so_far = 0L; /* clear line count */
2663 #if defined(DOS) || defined(OS2)
2664 /* LF with no CR!! */
2665 if(lastchar != '\r') /* must be non-text data! */
2666 lastchar = BREAKOUT;
2667 #endif
2669 else if(*p & 0x80){
2670 eight_bit_chars++;
2672 else if(!*p){
2673 /* NULL found. Unless we're told otherwise, must be binary */
2674 if(body->type == TYPEOTHER){
2675 body->type = TYPEAPPLICATION;
2676 body->subtype = cpystr("octet-stream");
2680 * The "TYPETEXT" here handles the case that the NULL
2681 * comes from imported text generated by some external
2682 * editor that permits or inserts NULLS. Otherwise,
2683 * assume it's a binary segment...
2685 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2688 * Since we've already set encoding, count this as a
2689 * hi bit char and continue. The reason is that if this
2690 * is text, there may be a high percentage of encoded
2691 * characters, so base64 may get set below...
2693 if(body->type == TYPETEXT)
2694 eight_bit_chars++;
2695 else
2696 break;
2699 #if defined(DOS) || defined(OS2) /* for binary file detection */
2700 if(lastchar != BREAKOUT)
2701 lastchar = *p;
2702 #endif
2704 /* read another buffer in */
2705 if(n == 0){
2706 if(bol)
2707 line_so_far += p - bol;
2709 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2712 len += n;
2713 p = buf;
2715 else
2716 p++;
2719 * If there's no beginning-of-line pointer, then we must
2720 * have seen an end-of-line. Set bol to the start of the
2721 * new line...
2723 if(!bol)
2724 bol = p;
2726 #if defined(DOS) || defined(OS2) /* for binary file detection */
2727 /* either a lone \r or lone \n indicate binary file */
2728 if(lastchar == '\r' || lastchar == BREAKOUT){
2729 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2730 if(body->type == TYPEOTHER){
2731 body->type = TYPEAPPLICATION;
2732 body->subtype = cpystr("octet-stream");
2735 new_encoding = ENCBINARY;
2736 break;
2739 #endif
2743 /* stash away for later */
2744 so_attr(so, "maxline", long2string(max_line));
2746 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2748 * Since the type or encoding aren't set yet, fall thru a
2749 * series of tests to make sure an adequate type and
2750 * encoding are set...
2753 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2754 if(body->type == TYPEOTHER){
2756 * Since the types not set, then we didn't find a NULL.
2757 * If there's no NULL, then this is likely text. However,
2758 * since we can't be *completely* sure, we set it to
2759 * the generic type.
2761 body->type = TYPEAPPLICATION;
2762 body->subtype = cpystr("octet-stream");
2765 if(new_encoding != ENCBINARY)
2767 * As with NULL handling, if we're told it's text,
2768 * qp-encode it, else it gets base 64...
2770 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2773 if(eight_bit_chars == 0L){
2774 if(body->type == TYPEOTHER)
2775 body->type = TYPETEXT;
2777 if(new_encoding == ENCOTHER)
2778 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2780 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2782 * The 30% threshold is based on qp encoded readability
2783 * on non-MIME UA's.
2785 if(body->type == TYPEOTHER)
2786 body->type = TYPETEXT;
2788 if(new_encoding != ENCBINARY)
2789 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2791 else{
2792 if(body->type == TYPEOTHER){
2793 body->type = TYPEAPPLICATION;
2794 body->subtype = cpystr("octet-stream");
2798 * Apply maximal encoding regardless of previous
2799 * setting. This segment's either not text, or is
2800 * unlikely to be readable with > 30% of the
2801 * text encoded anyway, so we might as well save space...
2803 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2807 #ifdef ENCODE_FROMS
2808 /* If there were From_'s at the beginning of a line or standalone dots */
2809 if((froms || dots) && new_encoding != ENCBINARY)
2810 new_encoding = ENC8BIT;
2811 #endif
2813 /* Set the subtype */
2814 if(body->subtype == NULL)
2815 body->subtype = cpystr(rfc822_default_subtype(body->type));
2817 if(body->encoding == ENCOTHER)
2818 body->encoding = new_encoding;
2820 fs_give((void **)&buf);
2822 if(we_cancel)
2823 cancel_busy_cue(-1);
2828 * Call this to set the charset of an attachment we have
2829 * created. If the attachment contains any non-ascii characters
2830 * then we'll set the charset to the passed in charset, otherwise
2831 * we'll make it us-ascii.
2833 void
2834 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2836 unsigned char c;
2837 int can_be_ascii = 1;
2838 STORE_S *so = (STORE_S *)body->contents.text.data;
2839 int we_cancel = 0;
2841 if(!body || body->type != TYPETEXT)
2842 return;
2844 we_cancel = busy_cue(NULL, NULL, 1);
2846 so_seek(so, 0L, 0);
2848 while(can_be_ascii && so_readc(&c, so))
2849 if(!c || c & 0x80)
2850 can_be_ascii--;
2852 if(can_be_ascii)
2853 set_parameter(&body->parameter, "charset", "US-ASCII");
2854 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2855 set_parameter(&body->parameter, "charset", charset);
2856 else{
2858 * Else we don't know. There are non ascii characters but we either
2859 * don't have a charset to set it to or that charset is just us_ascii,
2860 * which is impossible. So we label it unknown. An alternative would
2861 * have been to strip the high bits instead and label it ascii.
2863 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2866 if(we_cancel)
2867 cancel_busy_cue(-1);
2872 * since encoding happens on the way out the door, this is basically
2873 * just needed to handle TYPEMULTIPART
2875 void
2876 pine_encode_body (struct mail_bodystruct *body)
2878 PART *part;
2880 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2881 if (body) switch (body->type) {
2882 char *freethis;
2884 case TYPEMULTIPART: /* multi-part */
2885 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2886 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2888 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2889 (long) getpid ());
2890 tmp[sizeof(tmp)-1] = '\0';
2891 set_parameter(&body->parameter, "BOUNDARY", tmp);
2894 if(freethis)
2895 fs_give((void **) &freethis);
2897 part = body->nested.part; /* encode body parts */
2898 do pine_encode_body (&part->body);
2899 while ((part = part->next) != NULL); /* until done */
2900 break;
2902 case TYPETEXT :
2904 * If the part is text we edited, then it is UTF-8.
2905 * The user may be asking us to send it as something else
2906 * or we may want to downconvert to a more-specific characterset.
2907 * Mark it for conversion here so the right MIME header's written.
2908 * Do conversion pine_rfc822_output_body.
2909 * Attachments are left as is.
2911 if(body->contents.text.data
2912 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2913 char *charset, *posting_charset, *lp;
2915 if(!((charset = parameter_val(body->parameter, "charset"))
2916 && !strucmp(charset, UNKNOWN_CHARSET))
2917 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2919 set_parameter(&body->parameter, "charset", posting_charset);
2922 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2923 * and doesn't use anything but ASCII characters.
2924 * Why is it not ENC7BIT already? Because when we set the encoding
2925 * in set_mime_type_by_grope we were groping through UTF-8 text
2926 * not 2022 text. Not only that, but we didn't know at that point
2927 * that it wouldn't stay UTF-8 when we sent it, which would require
2928 * encoding.
2930 if(!strucmp(posting_charset, "iso-2022-jp")
2931 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2932 && strlen(lp) < 4)
2933 body->encoding = ENC7BIT;
2936 if(charset)
2937 fs_give((void **)&charset);
2940 break;
2942 /* case MESSAGE: */ /* here for documentation */
2943 /* Encapsulated messages are always treated as text objects at this point.
2944 This means that you must replace body->contents.msg with
2945 body->contents.text, which probably involves copying
2946 body->contents.msg.text to body->contents.text */
2947 default: /* all else has some encoding */
2949 * but we'll delay encoding it until the message is on the way
2950 * into the mail slot...
2952 break;
2958 * pine_header_line - simple wrapper around c-client call to contain
2959 * repeated code, and to write fcc if required.
2962 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2963 int writehdr, int localcopy)
2965 int ret = 1;
2966 int big = 10000;
2967 char *value, *folded = NULL, *cs;
2968 char *converted;
2970 if(!text)
2971 return 1;
2973 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2975 if(converted){
2976 if(cs && !strucmp(cs, "us-ascii"))
2977 value = converted;
2978 else
2979 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2980 (unsigned char *) converted, cs,
2981 encode_whole_header(field, header));
2983 if(value && value == converted){ /* no encoding was done, have to fold */
2984 int fold_by, len;
2985 char *actual_field;
2987 len = ((header && header->env && header->env->remail)
2988 ? strlen("ReSent-") : 0) +
2989 (field ? strlen(field) : 0) + 2;
2991 actual_field = (char *)fs_get((len+1) * sizeof(char));
2992 snprintf(actual_field, len+1, "%s%s: ",
2993 (header && header->env && header->env->remail) ? "ReSent-" : "",
2994 field ? field : "");
2995 actual_field[len] = '\0';
2998 * We were folding everything except message-id, but that wasn't
2999 * sufficient. Since 822 only allows folding where linear-white-space
3000 * is allowed we'd need a smarter folder than "fold" to do it. So,
3001 * instead of inventing that smarter folder (which would have to
3002 * know 822 syntax)
3004 * We could just alloc space and copy the actual_field followed by
3005 * the value into it, but since that's what fold does anyway we'll
3006 * waste some cpu time and use fold with a big fold parameter.
3008 * We upped the references folding from 75 to 256 because we were
3009 * encountering longer-than-75 message ids, and to break one line
3010 * in references is to break them all.
3012 if(field && !strucmp("Subject", field))
3013 fold_by = 75;
3014 else if(field && !strucmp("References", field))
3015 fold_by = 256;
3016 else
3017 fold_by = big;
3019 folded = fold(value, fold_by, big, actual_field, " ", FLD_CRLF);
3021 if(actual_field)
3022 fs_give((void **)&actual_field);
3024 else if(value){ /* encoding was done */
3025 RFC822BUFFER rbuf;
3026 size_t ll;
3029 * rfc1522_encode already inserted continuation lines and did
3030 * the necessary folding so we don't have to do it. Let
3031 * rfc822_header_line add the trailing crlf and the resent- if
3032 * necessary. The 20 could actually be a 12.
3034 ll = strlen(field) + strlen(value) + 20;
3035 folded = (char *) fs_get(ll * sizeof(char));
3036 *folded = '\0';
3037 rbuf.f = dummy_soutr;
3038 rbuf.s = NULL;
3039 rbuf.beg = folded;
3040 rbuf.cur = folded;
3041 rbuf.end = folded+ll-1;
3042 rfc822_output_header_line(&rbuf, field,
3043 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3044 *rbuf.cur = '\0';
3047 if(value && folded){
3048 if(writehdr && f)
3049 ret = (*f)(s, folded);
3051 if(ret && localcopy && lmc.so && !lmc.all_written)
3052 ret = so_puts(lmc.so, folded);
3055 if(folded)
3056 fs_give((void **)&folded);
3058 if(converted && converted != text)
3059 fs_give((void **) &converted);
3061 else
3062 ret = 0;
3064 return(ret);
3069 * Do appropriate encoding of text header lines.
3070 * For some field types (those that consist of 822 *text) we just encode
3071 * the whole thing. For structured fields we encode only within comments
3072 * if possible.
3074 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3075 * s -- Source string.
3076 * charset -- Charset to encode with.
3077 * encode_all -- If set, encode the whole string. If not, try to encode
3078 * only within comments if possible.
3080 * Returns S is returned if no encoding is done. D is returned if encoding
3081 * was needed.
3083 char *
3084 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3086 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3087 int in_comment = 0;
3089 if(!s)
3090 return((char *)s);
3092 if(dlen < SIZEOF_20KBUF)
3093 panic("bad call to encode_header_value");
3095 if(!encode_all){
3097 * We don't have to worry about keeping track of quoted-strings because
3098 * none of these fields which aren't addresses contain quoted-strings.
3099 * We do keep track of escaped parens inside of comments and comment
3100 * nesting.
3102 p = d+7000;
3103 for(q = (char *)s; *q; q++){
3104 switch(*q){
3105 case LPAREN:
3106 if(in_comment++ == 0)
3107 start_of_comment = q;
3109 break;
3111 case RPAREN:
3112 if(--in_comment == 0){
3113 /* encode the comment, excluding the outer parens */
3114 if(p-d < dlen-1)
3115 *p++ = LPAREN;
3117 *q = '\0';
3118 r = rfc1522_encode(d+14000, dlen-14000,
3119 (unsigned char *)start_of_comment+1,
3120 charset);
3121 if(r != start_of_comment+1)
3122 value = d+7000; /* some encoding was done */
3124 start_of_comment = NULL;
3125 if(r)
3126 sstrncpy(&p, r, dlen-1-(p-d));
3128 *q = RPAREN;
3129 if(p-d < dlen-1)
3130 *p++ = *q;
3132 else if(in_comment < 0){
3133 in_comment = 0;
3134 if(p-d < dlen-1)
3135 *p++ = *q;
3138 break;
3140 case BSLASH:
3141 if(!in_comment && *(q+1)){
3142 if(p-d < dlen-2){
3143 *p++ = *q++;
3144 *p++ = *q;
3148 break;
3150 default:
3151 if(!in_comment && p-d < dlen-1)
3152 *p++ = *q;
3154 break;
3158 if(value){
3159 /* Unterminated comment (wasn't really a comment) */
3160 if(start_of_comment)
3161 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3163 *p = '\0';
3168 * We have to check if there is anything that needs to be encoded that
3169 * wasn't in a comment. If there is, we'd better just start over and
3170 * encode the whole thing. So, if no encoding has been done within
3171 * comments, or if encoding is needed both within and outside of
3172 * comments, then we encode the whole thing. Otherwise, go with
3173 * the version that has only comments encoded.
3175 if(!value || rfc1522_encode(d, dlen,
3176 (unsigned char *)value, charset) != value)
3177 return(rfc1522_encode(d, dlen, s, charset));
3178 else{
3179 strncpy(d, value, dlen-1);
3180 d[dlen-1] = '\0';
3181 return(d);
3187 * pine_address_line - write a header field containing addresses,
3188 * one by one (so there's no buffer limit), and
3189 * wrapping where necessary.
3190 * Note: we use c-client functions to properly build the text string,
3191 * but have to screw around with pointers to fool c-client functions
3192 * into not blatting all the text into a single buffer. Yeah, I know.
3195 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3196 soutr_t f, void *s, int writehdr, int localcopy)
3198 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3199 size_t alloced = 0, sz;
3200 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3201 char *converted, *cs;
3202 ADDRESS *atmp;
3203 int i, count;
3204 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3205 RFC822BUFFER rbuf;
3206 static char comma[] = ", ";
3207 static char end_group[] = ";";
3208 #define no_comma (&comma[1])
3210 if(!alist) /* nothing in field! */
3211 return(1);
3213 if(!alist->host && alist->mailbox){ /* c-client group convention */
3214 in_group++;
3215 was_start_of_group++;
3216 /* encode mailbox of group */
3217 mtmp = alist->mailbox;
3218 if(mtmp){
3219 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3220 buftmp[sizeof(buftmp)-1] = '\0';
3221 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3222 if(converted){
3223 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3224 (unsigned char *) converted, cs));
3225 if(converted && converted != buftmp)
3226 fs_give((void **) &converted);
3228 else{
3229 failed++;
3230 goto bail_out;
3234 else
3235 mtmp = NULL;
3237 ptmp = alist->personal; /* remember personal name */
3238 /* make sure personal name is encoded */
3239 if(ptmp){
3240 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3241 buftmp[sizeof(buftmp)-1] = '\0';
3242 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3243 if(converted){
3244 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3245 (unsigned char *) converted, cs));
3246 if(converted && converted != buftmp)
3247 fs_give((void **) &converted);
3249 else{
3250 failed++;
3251 goto bail_out;
3255 atmp = alist->next;
3256 alist->next = NULL; /* digest only first address! */
3258 /* use automatic buffer unless it isn't big enough */
3259 if((alloced = est_size(alist)) > sizeof(tmp)){
3260 tmpptr = (char *)fs_get(alloced);
3261 sz = alloced;
3263 else{
3264 tmpptr = tmp;
3265 sz = sizeof(tmp);
3268 rbuf.f = dummy_soutr;
3269 rbuf.s = NULL;
3270 rbuf.beg = tmpptr;
3271 rbuf.cur = tmpptr;
3272 rbuf.end = tmpptr+sz-1;
3273 rfc822_output_address_line(&rbuf, field,
3274 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3275 *rbuf.cur = '\0';
3277 alist->next = atmp; /* restore pointer to next addr */
3279 if(alist->personal && alist->personal != ptmp)
3280 fs_give((void **) &alist->personal);
3282 alist->personal = ptmp; /* in case it changed, restore name */
3284 if(mtmp){
3285 if(alist->mailbox && alist->mailbox != mtmp)
3286 fs_give((void **) &alist->mailbox);
3288 alist->mailbox = mtmp;
3291 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3292 count -= 2;
3293 tmpptr[count] = '\0';
3297 * If there is no sending_stream and we are writing the Lcc header,
3298 * then we are piping it to sendmail -t which expects it to be a bcc,
3299 * not lcc.
3301 * When we write it to the fcc or postponed (the lmc.so),
3302 * we want it to be lcc, not bcc, so we put it back.
3304 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3305 fix_lcc = 1;
3307 if(writehdr && f && *tmpptr){
3308 if(fix_lcc)
3309 tmpptr[0] = 'b';
3311 failed = !(*f)(s, tmpptr);
3312 if(fix_lcc)
3313 tmpptr[0] = 'L';
3315 if(failed)
3316 goto bail_out;
3319 if(localcopy && lmc.so &&
3320 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3321 goto bail_out;
3323 for(alist = atmp; alist; alist = alist->next){
3324 delim = comma;
3325 /* account for c-client's representation of group names */
3326 if(in_group){
3327 if(!alist->host){ /* end of group */
3328 in_group = 0;
3329 was_start_of_group = 0;
3331 * Rfc822_write_address no longer writes out the end of group
3332 * unless the whole group address is passed to it, so we do
3333 * it ourselves.
3335 delim = end_group;
3337 else if(!localcopy || !lmc.so || lmc.all_written)
3338 continue;
3340 /* start of new group, print phrase below */
3341 else if(!alist->host && alist->mailbox){
3342 in_group++;
3343 was_start_of_group++;
3346 /* no comma before first address in group syntax */
3347 if(was_start_of_group && alist->host){
3348 delim = no_comma;
3349 was_start_of_group = 0;
3352 /* write delimiter */
3353 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3354 || (localcopy && lmc.so && !lmc.all_written
3355 && !so_puts(lmc.so, delim)))
3356 goto bail_out;
3358 ptmp = alist->personal; /* remember personal name */
3359 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3360 buftmp[sizeof(buftmp)-1] = '\0';
3361 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3362 if(converted){
3363 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3364 (unsigned char *) converted, cs));
3365 if(converted && converted != buftmp)
3366 fs_give((void **) &converted);
3368 else{
3369 failed++;
3370 goto bail_out;
3373 atmp = alist->next;
3374 alist->next = NULL; /* tie off linked list */
3375 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3376 alloced = i;
3377 sz = alloced;
3378 fs_resize((void **)&tmpptr, alloced);
3381 *tmpptr = '\0';
3382 /* make sure we don't write out group end with rfc822_write_address */
3383 if(alist->host || alist->mailbox){
3384 rbuf.f = dummy_soutr;
3385 rbuf.s = NULL;
3386 rbuf.beg = tmpptr;
3387 rbuf.cur = tmpptr;
3388 rbuf.end = tmpptr+sz-1;
3389 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3390 *rbuf.cur = '\0';
3393 alist->next = atmp; /* restore next pointer */
3395 if(alist->personal && alist->personal != ptmp)
3396 fs_give((void **) &alist->personal);
3398 alist->personal = ptmp; /* in case it changed, restore name */
3401 * BUG
3402 * With group syntax addresses we no longer have two identical
3403 * streams of output. Instead, for the fcc/postpone copy we include
3404 * all of the addresses inside the :; of the group, and for the
3405 * mail we're sending we don't include them. That means we aren't
3406 * correctly keeping track of the column to wrap in, below. That is,
3407 * we are keeping track of the fcc copy but we aren't keeping track
3408 * of the regular copy. It could result in too long or too short
3409 * lines. Should almost never come up since group addresses are almost
3410 * never followed by other addresses in the same header, and even
3411 * when they are, you have to go out of your way to get the headers
3412 * messed up.
3414 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3415 count = i + 4;
3416 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3417 || (localcopy && lmc.so && !lmc.all_written &&
3418 !so_puts(lmc.so, "\015\012 ")))
3419 goto bail_out;
3421 else
3422 count += i + 2;
3424 if(((!in_group || was_start_of_group)
3425 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3426 || (localcopy && lmc.so && !lmc.all_written
3427 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3428 goto bail_out;
3431 bail_out:
3432 if(tmpptr && tmpptr != tmp)
3433 fs_give((void **)&tmpptr);
3435 if(failed)
3436 return(0);
3438 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3439 && ((localcopy && lmc.so
3440 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3445 * mutated pine version of c-client's rfc822_header() function.
3446 * changed to call pine-wrapped header and address functions
3447 * so we don't have to limit the header size to a fixed buffer.
3448 * This function also calls pine's body_header write function
3449 * because encoding is delayed until output_body() is called.
3451 long
3452 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3454 PINEFIELD *pf;
3455 int j;
3457 if(header->env->remail){ /* if remailing */
3458 long i = strlen (header->env->remail);
3459 if(i > 4 && header->env->remail[i-4] == '\015')
3460 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3462 if((f && !(*f)(s, header->env->remail))
3463 || (lmc.so && !lmc.all_written
3464 && !so_puts(lmc.so, header->env->remail)))
3465 return(0L); /* start with remail header */
3468 j = 0;
3469 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3470 switch(pf->type){
3472 * Warning: This is confusing. The 2nd to last argument used to
3473 * be just pf->writehdr. We want Bcc lines to be written out
3474 * if we are handing off to a sendmail temp file but not if we
3475 * are talking smtp, so bcc's writehdr is set to 0 and
3476 * pine_address_line was sending if writehdr OR !sending_stream.
3477 * That works as long as we want to write everything when
3478 * !sending_stream (an mta handoff to sendmail). But then we
3479 * added the undisclosed recipients line which should only get
3480 * written if writehdr is set, and not when we pass to a
3481 * sendmail temp file. So pine_address_line has been changed
3482 * so it bases its decision solely on the writehdr passed to it,
3483 * and the logic that worries about Bcc and sending_stream
3484 * was moved up to the caller (here) to decide when to set it.
3486 * So we have:
3487 * undisclosed recipients:; This will just be written
3488 * if writehdr was set and not
3489 * otherwise, nothing magical.
3490 *** We may want to change this, because sendmail -t doesn't handle
3491 *** the empty group syntax well unless it has been configured to
3492 *** do so. It isn't configured by default, or in any of the
3493 *** sendmail v8 configs. So we may want to not write this line
3494 *** if we're doing an mta_handoff (!sending_stream).
3496 * !sending_stream (which means a handoff to a sendmail -t)
3497 * bcc or lcc both set the arg so they'll get written
3498 * (There is also Lcc hocus pocus in pine_address_line
3499 * which converts the Lcc: to Bcc: for sendmail
3500 * processing.)
3501 * sending_stream (which means an smtp handoff)
3502 * bcc and lcc will never have writehdr set, so
3503 * will never be written (They both do have rcptto set,
3504 * so they both do cause RCPT TO commands.)
3506 * The localcopy is independent of sending_stream and is just
3507 * written if it is set for all of these.
3509 case Address:
3510 if(!pine_address_line(pf->name,
3511 header,
3512 pf->addr ? *pf->addr : NULL,
3515 (!strucmp("bcc",pf->name ? pf->name : "")
3516 || !strucmp("Lcc",pf->name ? pf->name : ""))
3517 ? !sending_stream
3518 : pf->writehdr,
3519 pf->localcopy))
3520 return(0L);
3522 break;
3524 case Fcc:
3525 case FreeText:
3526 case Subject:
3527 if(!pine_header_line(pf->name, header,
3528 pf->text ? *pf->text : NULL,
3529 f, s, pf->writehdr, pf->localcopy))
3530 return(0L);
3532 break;
3534 default:
3535 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3536 pf->name);
3537 break;
3542 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3544 * Add comforting "X-" header line indicating what sort of
3545 * authenticity the receiver can expect...
3547 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3548 NETMBX netmbox;
3549 char sstring[MAILTMPLEN], *label; /* place to write */
3550 MAILSTREAM *m;
3551 int i, anonymous = 1;
3553 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3554 m = ps_global->s_pool.streams[i];
3555 if(m && sp_flagged(m, SP_LOCKED)
3556 && mail_valid_net_parse(m->mailbox, &netmbox)
3557 && !netmbox.anoflag)
3558 anonymous = 0;
3561 if(!anonymous){
3562 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3563 *user = (*netmbox.user)
3564 ? netmbox.user
3565 : cached_user_name(netmbox.mailbox);
3566 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3567 isdigit((unsigned char)last_char) ? "[" : "",
3568 netmbox.host,
3569 isdigit((unsigned char) last_char) ? "]" : "");
3570 sstring[sizeof(sstring)-1] = '\0';
3571 label = "X-X-Sender"; /* Jeez. */
3572 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3573 label += 4;
3575 else{
3576 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3577 sstring[sizeof(sstring)-1] = '\0';
3578 label = "X-Warning";
3581 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3582 return(0L);
3584 #endif
3586 if(body && !header->env->remail){ /* not if remail or no body */
3587 if((f && !(*f)(s, MIME_VER))
3588 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3589 || !pine_write_body_header(body, f, s))
3590 return(0L);
3592 else{ /* write terminating newline */
3593 if((f && !(*f)(s, "\015\012"))
3594 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3595 return(0L);
3598 return(1L);
3603 * pine_rfc822_output - pine's version of c-client call. Necessary here
3604 * since we're not using its structures as intended!
3606 long
3607 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3609 int we_cancel = 0;
3610 long retval;
3612 dprint((4, "-- pine_rfc822_output\n"));
3614 we_cancel = busy_cue(NULL, NULL, 1);
3615 pine_encode_body(body); /* encode body as necessary */
3616 /* build and output RFC822 header, output body */
3617 retval = pine_rfc822_header(header, body, f, s)
3618 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3620 if(we_cancel)
3621 cancel_busy_cue(-1);
3623 return(retval);
3628 * post_rfc822_output - cloak for pine's 822 output routine. Since
3629 * we can't pass opaque envelope thru c-client posting
3630 * logic, we need to wrap the real output inside
3631 * something that c-client knows how to call.
3633 long
3634 post_rfc822_output(char *tmp,
3635 ENVELOPE *env,
3636 struct mail_bodystruct *body,
3637 soutr_t f,
3638 void *s,
3639 long int ok8bit)
3641 return(pine_rfc822_output(send_header, body, f, s));
3646 * posting_characterset- determine what transliteration is reasonable
3647 * for posting the given non-ascii messsage data.
3649 * preferred_charset is the charset the original data was labeled in.
3650 * If we can keep that we do.
3652 * Returns: always returns the preferred character set.
3654 char *
3655 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3657 unsigned long *charsetmap = NULL;
3658 unsigned long validbitmap;
3659 static char *ascii = "US-ASCII";
3660 static char *utf8 = "UTF-8";
3661 int notcjk = 0;
3663 if(!ps_global->post_utf8){
3664 validbitmap = 0;
3666 if(mp == HdrText){
3667 char *text = NULL;
3668 UCS *ucs = NULL, *ucsp;
3670 text = (char *) data;
3672 /* convert text in header to UCS characters */
3673 if(text)
3674 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3676 if(!(ucs && *ucs))
3677 return(ascii);
3680 * After the while loop is done the validbitmap has
3681 * a 1 bit for all the character sets that can
3682 * represent all of the characters of this header.
3684 charsetmap = init_charsetchecker(preferred_charset);
3686 if(!charsetmap)
3687 return(utf8);
3689 validbitmap = ~0;
3690 while((validbitmap & ~0x1) && (*ucsp)){
3691 if(*ucsp > 0xffff){
3692 fs_give((void **) &ucs);
3693 return(utf8);
3696 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3699 fs_give((void **) &ucs);
3701 notcjk = validbitmap & 0x1;
3702 validbitmap &= ~0x1;
3704 if(!validbitmap)
3705 return(utf8);
3707 else{
3708 struct mail_bodystruct *body = NULL;
3709 STORE_S *the_text = NULL;
3710 int outchars;
3711 unsigned char c;
3712 UCS ucs;
3713 CBUF_S cbuf;
3715 cbuf.cbuf[0] = '\0';
3716 cbuf.cbufp = cbuf.cbuf;
3717 cbuf.cbufend = cbuf.cbuf;
3719 body = (struct mail_bodystruct *) data;
3721 if(body && body->type == TYPEMULTIPART)
3722 body = &body->nested.part->body;
3724 if(body && body->type == TYPETEXT)
3725 the_text = (STORE_S *) body->contents.text.data;
3727 if(!the_text)
3728 return(ascii);
3730 so_seek(the_text, 0L, 0); /* rewind */
3732 charsetmap = init_charsetchecker(preferred_charset);
3734 if(!charsetmap)
3735 return(utf8);
3737 validbitmap = ~0;
3740 * Read a stream of UTF-8 characters from the_text
3741 * and convert them to UCS-4 characters for the translatable
3742 * test.
3744 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3745 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3746 /* got a ucs character */
3747 if(ucs > 0xffff)
3748 return(utf8);
3750 validbitmap &= charsetmap[(unsigned long) ucs];
3754 notcjk = validbitmap & 0x1;
3755 validbitmap &= ~0x1;
3757 if(!validbitmap)
3758 return(utf8);
3761 /* user chooses something other than UTF-8 */
3762 if(strucmp(ps_global->posting_charmap, utf8)){
3764 * If we're to post in other than UTF-8, and it can be
3765 * transliterated without losing fidelity, do it.
3766 * Else, use UTF-8.
3769 /* if ascii works, always use that */
3770 if(representable_in_charset(validbitmap, ascii))
3771 return(ascii);
3773 /* does the user's posting character set work? */
3774 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3775 return(ps_global->posting_charmap);
3777 /* this is the charset the message we are replying to was in */
3778 if(preferred_charset
3779 && strucmp(preferred_charset, ascii)
3780 && representable_in_charset(validbitmap, preferred_charset))
3781 return(preferred_charset);
3783 /* else, use UTF-8 */
3786 /* user chooses nothing, going with the default */
3787 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3788 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3789 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3790 char *most_preferred;
3793 * In this case the user didn't specify a posting character set
3794 * and we will choose the most-specific one from our list.
3797 /* ascii is best */
3798 if(representable_in_charset(validbitmap, ascii))
3799 return(ascii);
3801 /* Can we keep the original from the message we're replying to? */
3802 if(preferred_charset
3803 && strucmp(preferred_charset, ascii)
3804 && representable_in_charset(validbitmap, preferred_charset))
3805 return(preferred_charset);
3807 /* choose the best of the rest */
3808 most_preferred = most_preferred_charset(validbitmap);
3809 if(!most_preferred)
3810 return(utf8);
3813 * If the text we're labeling contains something like
3814 * smart quotes but no CJK characters, then instead of
3815 * labeling it as ISO-2022-JP we want to use UTF-8.
3817 if(notcjk){
3818 const CHARSET *cs;
3820 cs = utf8_charset(most_preferred);
3821 if(!cs
3822 || cs->script == SC_CHINESE_SIMPLIFIED
3823 || cs->script == SC_CHINESE_TRADITIONAL
3824 || cs->script == SC_JAPANESE
3825 || cs->script == SC_KOREAN)
3826 return(utf8);
3829 return(most_preferred);
3831 /* user explicitly chooses UTF-8 */
3832 else{
3833 /* if ascii works, always use that */
3834 if(representable_in_charset(validbitmap, ascii))
3835 return(ascii);
3837 /* else, use UTF-8 */
3842 return(utf8);
3846 static char **charsetlist = NULL;
3847 static int items_in_charsetlist = 0;
3848 static unsigned long *charsetmap = NULL;
3850 static char *downgrades[] = {
3851 "US-ASCII",
3852 "ISO-8859-15",
3853 "ISO-8859-1",
3854 "ISO-8859-2",
3855 "VISCII",
3856 "KOI8-R",
3857 "KOI8-U",
3858 "ISO-8859-7",
3859 "ISO-8859-6",
3860 "ISO-8859-8",
3861 "TIS-620",
3862 "ISO-2022-JP",
3863 "GB2312",
3864 "BIG5",
3865 "EUC-KR"
3869 unsigned long *
3870 init_charsetchecker(char *preferred_charset)
3872 int i, count = 0, reset = 0;
3873 char *ascii = "US-ASCII";
3874 char *utf8 = "UTF-8";
3877 * When user doesn't set a posting character set posting_charmap ends up
3878 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3879 * That's where the strange set of if-else's come from.
3882 /* user chooses something other than UTF-8 */
3883 if(strucmp(ps_global->posting_charmap, utf8)){
3884 count++; /* US-ASCII */
3885 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3886 reset++;
3888 /* if posting_charmap is valid, include it in list */
3889 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3890 && strucmp(ps_global->posting_charmap, ascii)
3891 && strucmp(ps_global->posting_charmap, utf8)
3892 && utf8_charset(ps_global->posting_charmap)){
3893 count++;
3894 if(!reset
3895 && (items_in_charsetlist < count
3896 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3897 reset++;
3900 if(preferred_charset && preferred_charset[0]
3901 && strucmp(preferred_charset, ascii)
3902 && strucmp(preferred_charset, utf8)
3903 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3904 count++;
3905 if(!reset
3906 && (items_in_charsetlist < count
3907 || strucmp(charsetlist[count-1], preferred_charset)))
3908 reset++;
3911 if(items_in_charsetlist != count)
3912 reset++;
3914 if(reset){
3915 if(charsetlist)
3916 free_list_array(&charsetlist);
3918 items_in_charsetlist = count;
3919 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3921 i = 0;
3922 charsetlist[i++] = cpystr(ascii);
3924 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3925 && strucmp(ps_global->posting_charmap, ascii)
3926 && strucmp(ps_global->posting_charmap, utf8)
3927 && utf8_charset(ps_global->posting_charmap))
3928 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3930 if(preferred_charset && preferred_charset[0]
3931 && strucmp(preferred_charset, ascii)
3932 && strucmp(preferred_charset, utf8)
3933 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3934 charsetlist[i++] = cpystr(preferred_charset);
3936 charsetlist[i] = NULL;
3939 /* user chooses nothing, going with the default */
3940 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3941 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3942 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3943 int add_preferred = 0;
3945 /* does preferred_charset have to be added to the list? */
3946 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3947 add_preferred = 1;
3948 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3949 if(!strucmp(downgrades[i], preferred_charset))
3950 add_preferred = 0;
3953 if(add_preferred){
3954 /* existing list is right size already */
3955 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3956 /* just check to see if last list item is the preferred_charset */
3957 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3958 /* no, fix it */
3959 reset++;
3960 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3961 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3964 else{
3965 reset++;
3966 if(charsetlist)
3967 free_list_array(&charsetlist);
3969 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3970 items_in_charsetlist = count;
3971 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3972 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3973 charsetlist[i] = cpystr(downgrades[i]);
3975 charsetlist[i++] = cpystr(preferred_charset);
3976 charsetlist[i] = NULL;
3979 else{
3980 /* if list is same size as downgrades, consider it good */
3981 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
3982 reset++;
3984 if(reset){
3985 if(charsetlist)
3986 free_list_array(&charsetlist);
3988 count = sizeof(downgrades)/sizeof(downgrades[0]);
3989 items_in_charsetlist = count;
3990 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3991 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3992 charsetlist[i] = cpystr(downgrades[i]);
3994 charsetlist[i] = NULL;
3998 /* user explicitly chooses UTF-8 */
3999 else{
4000 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4001 count++; /* US-ASCII */
4002 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4003 reset++;
4005 if(items_in_charsetlist != count)
4006 reset++;
4008 if(reset){
4009 if(charsetlist)
4010 free_list_array(&charsetlist);
4012 /* the list is just ascii and nothing else */
4013 items_in_charsetlist = count;
4014 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4016 i = 0;
4017 charsetlist[i++] = cpystr(ascii);
4018 charsetlist[i] = NULL;
4023 if(reset){
4024 if(charsetmap)
4025 fs_give((void **) &charsetmap);
4027 if(charsetlist)
4028 charsetmap = utf8_csvalidmap(charsetlist);
4031 return(charsetmap);
4035 /* total reset */
4036 void
4037 free_charsetchecker(void)
4039 if(charsetlist)
4040 free_list_array(&charsetlist);
4042 items_in_charsetlist = 0;
4044 if(charsetmap)
4045 fs_give((void **) &charsetmap);
4050 representable_in_charset(unsigned long validbitmap, char *charset)
4052 int i, done = 0, ret = 0;
4053 unsigned long j;
4055 if(!(charset && charset[0]))
4056 return ret;
4058 if(!strucmp(charset, "UTF-8"))
4059 return 1;
4061 for(i = 0; !done && i < items_in_charsetlist; i++){
4062 if(!strucmp(charset, charsetlist[i])){
4063 j = 1;
4064 j <<= (i+1);
4065 done++;
4066 if(validbitmap & j)
4067 ret = 1;
4071 return ret;
4075 char *
4076 most_preferred_charset(unsigned long validbitmap)
4078 unsigned long bm;
4079 unsigned long rm;
4080 int index;
4082 if(!(validbitmap && items_in_charsetlist > 0))
4083 return("UTF-8");
4085 /* careful, find_rightmost_bit modifies the bitmap */
4086 bm = validbitmap;
4087 rm = find_rightmost_bit(&bm);
4088 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4090 return(charsetlist[index]);
4095 * Set parameter to new value.
4097 void
4098 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4100 PARAMETER *pm;
4102 if(!param || !(paramname && *paramname))
4103 return;
4105 if(*param == NULL){
4106 pm = (*param) = mail_newbody_parameter();
4107 pm->attribute = cpystr(paramname);
4109 else{
4110 int nomatch;
4112 for(pm = *param;
4113 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4114 pm = pm->next)
4115 ;/* searching for paramname parameter */
4117 if(nomatch){ /* add charset parameter */
4118 pm->next = mail_newbody_parameter();
4119 pm = pm->next;
4120 pm->attribute = cpystr(paramname);
4122 /* else pm is existing paramname parameter */
4125 if(pm){
4126 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4127 if(pm->value)
4128 fs_give((void **) &pm->value);
4130 if(new_value)
4131 pm->value = cpystr(new_value);
4137 /*----------------------------------------------------------------------
4138 Remove the leading digits from SMTP error messages
4139 -----*/
4140 char *
4141 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4143 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4144 (*error == '.' && isdigit((unsigned char)*(error+1))))
4145 error++;
4147 snprintf(outbuf, outbuflen, printstring, error);
4148 outbuf[outbuflen-1] = '\0';
4149 return(outbuf);
4154 * Local globals pine's body output routine needs
4156 static soutr_t l_f;
4157 static TCPSTREAM *l_stream;
4158 static unsigned c_in_buf = 0;
4161 * def to make our pipe write's more friendly
4163 #ifdef PIPE_MAX
4164 #if PIPE_MAX > 20000
4165 #undef PIPE_MAX
4166 #endif
4167 #endif
4169 #ifndef PIPE_MAX
4170 #define PIPE_MAX 1024
4171 #endif
4175 * l_flust_net - empties gf_io terminal function's buffer
4178 l_flush_net(int force)
4180 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4181 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4183 tmp_20k_buf[c_in_buf] = '\0';
4184 if(!force){
4186 * The start of each write is expected to be the start of a
4187 * "record" (i.e., a CRLF terminated line). Make sure that is true
4188 * else we might screw up SMTP dot quoting...
4190 for(p = tmp_20k_buf, lp = NULL;
4191 (p = strstr(p, "\015\012")) != NULL;
4192 lp = (p += 2))
4196 if(!lp && c_in_buf > 2) /* no CRLF! */
4197 for(p = &tmp_20k_buf[c_in_buf] - 2;
4198 p > &tmp_20k_buf[0] && *p == '.';
4199 p--) /* find last non-dot */
4202 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4203 /* snippet remains */
4204 c = *lp;
4205 *lp = '\0';
4209 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4210 || (lmc.so && !lmc.all_written
4211 && !(lmc.text_only && lmc.text_written)
4212 && !so_puts(lmc.so, tmp_20k_buf)))
4213 return(0);
4215 c_in_buf = 0;
4216 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4217 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4218 c_in_buf++, lp++;
4221 return(1);
4226 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4230 l_putc(int c)
4232 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4233 tmp_20k_buf[c_in_buf++] = (char) c;
4235 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4241 * pine_rfc822_output_body - pine's version of c-client call. Again,
4242 * necessary since c-client doesn't know about how
4243 * we're treating attachments
4245 long
4246 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4248 PART *part;
4249 PARAMETER *param;
4250 char *t, *cookie = NIL, *encode_error;
4251 char tmp[MAILTMPLEN];
4252 int add_trailing_crlf;
4253 LOC_2022_JP ljp;
4254 gf_io_t gc;
4256 dprint((4, "-- pine_rfc822_output_body: %d\n",
4257 body ? body->type : 0));
4258 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4259 part = body->nested.part; /* first body part */
4260 /* find cookie */
4261 for (param = body->parameter; param && !cookie; param = param->next)
4262 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4263 if (!cookie) cookie = "-"; /* yucky default */
4266 * Output a bit of text before the first multipart delimiter
4267 * to warn unsuspecting users of non-mime-aware ua's that
4268 * they should expect weirdness. We do not add this when signing a
4269 * message, though...
4271 #ifdef SMIME
4272 if(ps_global->smime && !ps_global->smime->do_sign)
4273 #endif
4274 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"))
4275 return(0);
4277 do { /* for each part */
4278 /* build cookie */
4279 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4280 tmp[sizeof(tmp)-1] = '\0';
4281 /* append cookie,mini-hdr,contents */
4282 if((f && !(*f)(s, tmp))
4283 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4284 || !pine_write_body_header(&part->body,f,s)
4285 || !pine_rfc822_output_body (&part->body,f,s))
4286 return(0);
4287 } while ((part = part->next) != NULL); /* until done */
4288 /* output trailing cookie */
4289 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4290 tmp[sizeof(tmp)-1] = '\0';
4291 #ifdef SMIME
4292 if(ps_global->smime && ps_global->smime->do_sign
4293 && strlen(tmp) < sizeof(tmp)-2)
4294 strncat(tmp, "\r\n", 2);
4295 #endif
4296 if(lmc.so && !lmc.all_written){
4297 so_puts(lmc.so, t);
4298 so_puts(lmc.so, "\015\012");
4301 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4304 l_f = f; /* set up for writing chars... */
4305 l_stream = s; /* out other end of pipe... */
4306 gf_filter_init();
4307 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4308 body->size.bytes));
4310 if(body->contents.text.data)
4311 gf_set_so_readc(&gc, (STORE_S *) body->contents.text.data);
4312 else
4313 return(1);
4316 * Don't add trailing line if it is ExternalText, which already guarantees
4317 * a trailing newline.
4319 add_trailing_crlf = !(((STORE_S *) body->contents.text.data)->src == ExternalText);
4321 so_seek((STORE_S *) body->contents.text.data, 0L, 0);
4323 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4324 char *charset;
4326 if(body->type == TYPETEXT
4327 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)
4328 && (charset = parameter_val(body->parameter, "charset"))){
4329 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4330 if(!strucmp(charset, "iso-2022-jp")){
4331 ljp.report_err = 0;
4332 gf_link_filter(gf_line_test,
4333 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4335 else{
4336 void *table = utf8_rmap(charset);
4338 if(table){
4339 gf_link_filter(gf_convert_utf8_charset,
4340 gf_convert_utf8_charset_opt(table,0));
4342 else{
4343 /* else, just send it? */
4344 set_parameter(&body->parameter, "charset", "UTF-8");
4349 fs_give((void **)&charset);
4353 * Convert text pieces to canonical form
4354 * BEFORE applying any encoding (rfc1341: appendix G)...
4355 * NOTE: almost all filters expect CRLF newlines
4357 if(body->type == TYPETEXT
4358 && body->encoding != ENCBASE64
4359 && !so_attr((STORE_S *) body->contents.text.data, "rawbody", NULL)){
4360 gf_link_filter(gf_local_nvtnl, NULL);
4363 switch (body->encoding) { /* all else needs filtering */
4364 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4365 gf_link_filter(gf_8bit_qp, NULL);
4366 break;
4368 case ENCBINARY: /* encode binary into BASE64 */
4369 gf_link_filter(gf_binary_b64, NULL);
4370 break;
4372 default: /* otherwise text */
4373 break;
4377 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4378 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4379 _("Encoding Error \"%s\""), encode_error);
4380 display_message('x');
4383 gf_clear_so_readc((STORE_S *) body->contents.text.data);
4385 if(encode_error || !l_flush_net(TRUE))
4386 return(0);
4388 send_bytes_sent += gf_bytes_piped();
4389 so_release((STORE_S *)body->contents.text.data);
4391 if(lmc.so && !lmc.all_written && lmc.text_only){
4392 if(lmc.text_written){ /* we have some splainin' to do */
4393 char tmp[MAILTMPLEN];
4394 char *name = NULL;
4396 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4397 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4398 return(0);
4401 * BUG: If this name is not ascii it's going to cause trouble.
4403 name = parameter_val(body->parameter, "name");
4404 snprintf(tmp, sizeof(tmp),
4405 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4406 body_type_names(body->type),
4407 body->subtype ? body->subtype : "Unknown",
4408 name ? " (Name=\"" : "",
4409 name ? name : "",
4410 name ? "\")" : "",
4411 comatose(body->size.bytes));
4412 tmp[sizeof(tmp)-1] = '\0';
4413 if(name)
4414 fs_give((void **)&name);
4416 if(!so_puts(lmc.so, tmp))
4417 return(0);
4419 else /* suppress everything after first text part */
4420 lmc.text_written = (body->type == TYPETEXT
4421 && (!body->subtype
4422 || !strucmp(body->subtype, "plain")));
4425 if(add_trailing_crlf)
4426 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4427 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4428 else
4429 return(1);
4432 char *
4433 ToLower(char *s, char *t)
4435 int i;
4437 for(i = 0; s != NULL && s[i] != '\0'; i++)
4438 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4439 t[i] = '\0';
4441 return t;
4445 * pine_write_body_header - another c-client clone. This time
4446 * so the final encoding labels get set
4447 * correctly since it hasn't happened yet,
4448 * and to be paranoid about line lengths.
4450 * Returns: TRUE/nonzero on success, zero on error
4453 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4455 char tmp[MAILTMPLEN];
4456 RFC822BUFFER rbuf;
4457 int i;
4458 unsigned char c;
4459 STRINGLIST *stl;
4460 STORE_S *so;
4461 extern const char *tspecials;
4463 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4464 if(!(so_puts(so, "Content-Type: ")
4465 && so_puts(so, ToLower(body_types[body->type], tmp))
4466 && so_puts(so, "/")
4467 && so_puts(so, ToLower(body->subtype
4468 ? body->subtype
4469 : rfc822_default_subtype (body->type),tmp))))
4470 return(pwbh_finish(0, so));
4472 if(body->parameter){
4473 if(!pine_write_params(body->parameter, so))
4474 return(pwbh_finish(0, so));
4476 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4477 return(pwbh_finish(0, so));
4479 if(!so_puts(so, "\015\012"))
4480 return(pwbh_finish(0, so));
4482 if ((body->encoding /* note: encoding 7BIT never output! */
4483 && !(so_puts(so, "Content-Transfer-Encoding: ")
4484 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4485 ? ENCBASE64
4486 : (body->encoding == ENC8BIT)
4487 ? ENCQUOTEDPRINTABLE
4488 : (body->encoding <= ENCMAX)
4489 ? body->encoding
4490 : ENCOTHER])
4491 && so_puts(so, "\015\012")))
4493 * If requested, strip Content-ID headers that don't look like they
4494 * are needed. Microsoft's Outlook XP has a bug that causes it to
4495 * not show that there is an attachment when there is a Content-ID
4496 * header present on that attachment.
4498 * If user has quell-content-id turned on, don't output content-id
4499 * unless it is of type message/external-body.
4500 * Since this code doesn't look inside messages being forwarded
4501 * type message content-ids will remain as is and type multipart
4502 * alternative will remain as is. We don't create those on our
4503 * own. If we did, we'd have to worry about getting this right.
4505 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4506 || (body->type == TYPEMESSAGE
4507 && body->subtype
4508 && !strucmp(body->subtype, "external-body")))
4509 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4510 && so_puts(so, "\015\012")))
4511 || (body->description
4512 && strlen(body->description) < 5000 /* arbitrary! */
4513 && !pine_write_header_line("Content-Description: ", body->description, so))
4514 || (body->md5
4515 && !(so_puts(so, "Content-MD5: ")
4516 && so_puts(so, body->md5)
4517 && so_puts(so, "\015\012"))))
4518 return(pwbh_finish(0, so));
4520 if ((stl = body->language) != NULL) {
4521 if(!so_puts(so, "Content-Language: "))
4522 return(pwbh_finish(0, so));
4524 do {
4525 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4526 return(pwbh_finish(0, so));
4528 tmp[0] = '\0';
4529 rbuf.f = dummy_soutr;
4530 rbuf.s = NULL;
4531 rbuf.beg = tmp;
4532 rbuf.cur = tmp;
4533 rbuf.end = tmp+sizeof(tmp)-1;
4534 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4535 *rbuf.cur = '\0';
4537 if(!so_puts(so, tmp)
4538 || ((stl = stl->next) && !so_puts(so, ", ")))
4539 return(pwbh_finish(0, so));
4541 while (stl);
4543 if(!so_puts(so, "\015\012"))
4544 return(pwbh_finish(0, so));
4547 if (body->disposition.type) {
4548 if(!(so_puts(so, "Content-Disposition: ")
4549 && so_puts(so, body->disposition.type)))
4550 return(pwbh_finish(0, so));
4552 if(!pine_write_params(body->disposition.parameter, so))
4553 return(pwbh_finish(0, so));
4555 if(!so_puts(so, "\015\012"))
4556 return(pwbh_finish(0, so));
4559 /* copy out of so, a line at a time (or less than a K)
4560 * and send it down the pike
4562 so_seek(so, 0L, 0);
4563 i = 0;
4564 while(so_readc(&c, so))
4565 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4566 tmp[i] = '\0';
4567 if((f && !(*f)(s, tmp))
4568 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4569 return(pwbh_finish(0, so));
4571 i = 0;
4574 /* Finally, write blank line */
4575 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4576 return(pwbh_finish(0, so));
4578 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4581 return(0);
4586 * pine_write_header - convert, encode (if needed) and
4587 * write "header-name: field-body"
4590 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4592 char *cv, *cs, *vp;
4593 int rv;
4595 cs = posting_characterset(val, NULL, HdrText);
4596 cv = utf8_to_charset(val, cs, 0);
4597 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4598 (unsigned char *) cv, cs);
4600 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4602 if(cv && cv != val)
4603 fs_give((void **) &cv);
4606 return(rv);
4611 * pine_write_param - convert, encode and write MIME header-field parameters
4614 pine_write_params(PARAMETER *param, STORE_S *so)
4616 for(; param; param = param->next){
4617 int rv;
4618 char *cv, *cs;
4619 extern const char *tspecials;
4621 cs = posting_characterset(param->value, NULL, HdrText);
4622 cv = utf8_to_charset(param->value, cs, 0);
4623 rv = (so_puts(so, "; ")
4624 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4626 if(cv && cv != param->value)
4627 fs_give((void **) &cv);
4629 if(!rv)
4630 return(0);
4633 return(1);
4639 lmc_body_header_line(char *line, int beginning)
4641 if(lmc.so && !lmc.all_written){
4642 if(beginning && lmc.text_only && lmc.text_written
4643 && (!struncmp(line, "content-type:", 13)
4644 || !struncmp(line, "content-transfer-encoding:", 26)
4645 || !struncmp(line, "content-disposition:", 20))){
4647 * "comment out" the real values since our comment isn't
4648 * likely the same type, disposition nor encoding...
4650 if(!so_puts(lmc.so, "X-"))
4651 return(FALSE);
4654 return(so_puts(lmc.so, line));
4657 return(TRUE);
4662 lmc_body_header_finish(void)
4664 if(lmc.so && !lmc.all_written){
4665 if(lmc.text_only && lmc.text_written
4666 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4667 return(FALSE);
4669 return(so_puts(lmc.so, "\015\012"));
4672 return(TRUE);
4678 pwbh_finish(int rv, STORE_S *so)
4680 if(so)
4681 so_give(&so);
4683 return(rv);
4688 * pine_free_body - c-client call wrapper so the body data pointer we
4689 * we're using in a way c-client doesn't know about
4690 * gets free'd appropriately.
4692 void
4693 pine_free_body(struct mail_bodystruct **body)
4696 * Preempt c-client's contents.text.data clean up since we've
4697 * usurped it's meaning for our own purposes...
4699 pine_free_body_data (*body);
4701 /* Then let c-client handle the rest... */
4702 mail_free_body(body);
4707 * pine_free_body_data - free pine's interpretations of the body part's
4708 * data pointer.
4710 void
4711 pine_free_body_data(struct mail_bodystruct *body)
4713 if(body){
4714 if(body->type == TYPEMULTIPART){
4715 PART *part = body->nested.part;
4716 do /* for each part */
4717 pine_free_body_data(&part->body);
4718 while ((part = part->next) != NULL); /* until done */
4720 else if(body->contents.text.data)
4721 so_give((STORE_S **) &body->contents.text.data);
4726 long
4727 send_body_size(struct mail_bodystruct *body)
4729 long l = 0L;
4730 PART *part;
4732 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4733 part = body->nested.part; /* first body part */
4734 do /* for each part */
4735 l += send_body_size(&part->body);
4736 while ((part = part->next) != NULL); /* until done */
4737 return(l);
4740 return(l + body->size.bytes);
4745 sent_percent(void)
4747 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4748 / send_bytes_to_send);
4749 return(MIN(i, 100));
4754 * pine_smtp_verbose_out - write
4756 void
4757 pine_smtp_verbose_out(char *s)
4759 #ifdef _WINDOWS
4760 LPTSTR slpt;
4761 #endif
4762 if(verbose_send_output && s){
4763 char *p, last = '\0';
4765 for(p = s; *p; p++)
4766 if(*p == '\015')
4767 *p = ' ';
4768 else
4769 last = *p;
4771 #ifdef _WINDOWS
4773 * The stream is opened in Unicode mode, so we need to fix the
4774 * argument to fputs.
4776 slpt = utf8_to_lptstr((LPSTR) s);
4777 if(slpt){
4778 _fputts(slpt, verbose_send_output);
4779 fs_give((void **) &slpt);
4782 if(last != '\012')
4783 _fputtc(L'\n', verbose_send_output);
4784 #else
4785 fputs(s, verbose_send_output);
4786 if(last != '\012')
4787 fputc('\n', verbose_send_output);
4788 #endif
4795 * pine_header_forbidden - is this name a "forbidden" header?
4797 * name - the header name to check
4798 * We don't allow user to change these.
4801 pine_header_forbidden(char *name)
4803 char **p;
4804 static char *forbidden_headers[] = {
4805 "sender",
4806 "x-sender",
4807 "x-x-sender",
4808 "date",
4809 "received",
4810 "message-id",
4811 "in-reply-to",
4812 "path",
4813 "resent-message-id",
4814 "resent-date",
4815 "resent-from",
4816 "resent-sender",
4817 "resent-to",
4818 "resent-cc",
4819 "resent-reply-to",
4820 "mime-version",
4821 "content-type",
4822 "x-priority",
4823 "user-agent",
4824 "list-help", /* rfc 2369, section 3 */
4825 "list-unsubscribe",
4826 "list-subscribe",
4827 "list-post",
4828 "list-owner",
4829 "list-archive",
4830 NULL
4833 for(p = forbidden_headers; *p; p++)
4834 if(!strucmp(name, *p))
4835 break;
4837 return((*p) ? 1 : 0);
4842 * hdr_is_in_list - is there a custom value for this header?
4844 * hdr - the header name to check
4845 * custom - the list to check in
4846 * Returns 1 if there is a custom value, 0 otherwise.
4849 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4851 PINEFIELD *pf;
4853 for(pf = custom; pf && pf->name; pf = pf->next)
4854 if(strucmp(pf->name, hdr) == 0)
4855 return 1;
4857 return 0;
4862 * count_custom_hdrs_pf - returns number of custom headers in arg
4863 * custom -- the list to be counted
4864 * only_nonstandard -- only count headers which aren't standard pine headers
4867 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4869 int ret = 0;
4871 for(; custom && custom->name; custom = custom->next)
4872 if(!only_nonstandard || !custom->standard)
4873 ret++;
4875 return(ret);
4880 * count_custom_hdrs_list - returns number of custom headers in arg
4883 count_custom_hdrs_list(char **list)
4885 char **p;
4886 char *q = NULL;
4887 char *name;
4888 char *t;
4889 char save;
4890 int ret = 0;
4892 if(list){
4893 for(p = list; (q = *p) != NULL; p++){
4894 if(q[0]){
4895 /* remove leading whitespace */
4896 name = skip_white_space(q);
4898 /* look for colon or space or end */
4899 for(t = name;
4900 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4901 ;/* do nothing */
4903 save = *t;
4904 *t = '\0';
4905 if(!pine_header_forbidden(name))
4906 ret++;
4908 *t = save;
4913 return(ret);
4918 * set_default_hdrval - put the user's default value for this header
4919 * into pf->textbuf.
4920 * setthis - the pinefield to be set
4921 * custom - where to look for the default
4923 CustomType
4924 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4926 PINEFIELD *pf;
4927 CustomType ret = NoMatch;
4929 if(!setthis || !setthis->name){
4930 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4931 return(ret);
4934 setthis->textbuf = NULL;
4936 for(pf = custom; pf && pf->name; pf = pf->next){
4937 if(strucmp(pf->name, setthis->name) != 0)
4938 continue;
4940 ret = pf->cstmtype;
4942 /* turn on editing */
4943 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4944 setthis->canedit = 1;
4946 if(pf->val)
4947 setthis->textbuf = cpystr(pf->val);
4950 if(!setthis->textbuf)
4951 setthis->textbuf = cpystr("");
4953 return(ret);
4958 * pine_header_standard - is this name a "standard" header?
4960 * name - the header name to check
4962 FieldType
4963 pine_header_standard(char *name)
4965 int i;
4967 /* check to see if this is a standard header */
4968 for(i = 0; pf_template[i].name; i++)
4969 if(!strucmp(name, pf_template[i].name))
4970 return(pf_template[i].type);
4972 return(TypeUnknown);
4977 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
4978 * Allocates space for each name and addr ptr.
4979 * Allocates space for default in textbuf, even if empty.
4981 * head - the first PINEFIELD to fill in
4982 * list - the list to parse
4984 void
4985 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
4987 char **p, *q, *t, *name, *value, save;
4988 PINEFIELD *pf;
4990 pf = head;
4992 if(list){
4993 for(p = list; (q = *p) != NULL; p++){
4995 if(q[0]){
4997 /* anything after leading whitespace? */
4998 if(!*(name = skip_white_space(q)))
4999 continue;
5001 /* look for colon or space or end */
5002 for(t = name;
5003 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5004 ;/* do nothing */
5006 /* if there is a space in the field-name, skip it */
5007 if(isspace((unsigned char)*t)){
5008 q_status_message1(SM_ORDER, 3, 3,
5009 _("Space not allowed in header name (%s)"),
5010 name);
5011 continue;
5014 save = *t;
5015 *t = '\0';
5017 /* Don't allow any of the forbidden headers. */
5018 if(pine_header_forbidden(name)){
5019 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5020 _("Not allowed to change header \"%s\""),
5021 name);
5023 *t = save;
5024 continue;
5027 if(pf){
5028 if(pine_header_standard(name) != TypeUnknown)
5029 pf->standard = 1;
5031 pf->name = cpystr(name);
5032 pf->type = FreeText;
5033 pf->cstmtype = cstmtype;
5034 pf->next = pf+1;
5036 #ifdef OLDWAY
5038 * Some mailers apparently break if we change
5039 * user@domain into Fred <user@domain> for
5040 * return-receipt-to,
5041 * so we'll just call this a FreeText field, too.
5044 * For now, all custom headers are FreeText except for
5045 * this one that we happen to know about. We might
5046 * have to add some syntax to the config option so that
5047 * people can tell us their custom header takes addresses.
5049 if(!strucmp(pf->name, "Return-Receipt-to")){
5050 pf->type = Address;
5051 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5052 *pf->addr = (ADDRESS *)NULL;
5054 #endif /* OLDWAY */
5056 *t = save;
5058 /* remove space between name and colon */
5059 value = skip_white_space(t);
5061 /* give them an alloc'd default, even if empty */
5062 pf->textbuf = cpystr((*value == ':')
5063 ? skip_white_space(++value) : "");
5064 if(pf->textbuf && pf->textbuf[0])
5065 pf->val = cpystr(pf->textbuf);
5067 pf++;
5069 else
5070 *t = save;
5075 /* fix last next pointer */
5076 if(head && pf != head)
5077 (pf-1)->next = NULL;
5082 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5083 * setup except that it doesn't have default values.
5084 * Add those defaults if they exist in "list".
5086 * head - the first PINEFIELD to add a default to
5087 * list - the list to get the defaults from
5089 void
5090 add_defaults_from_list(PINEFIELD *head, char **list)
5092 char **p, *q, *t, *name, *value, save;
5093 PINEFIELD *pf;
5095 for(pf = head; pf && list; pf = pf->next){
5096 if(!pf->name)
5097 continue;
5099 for(p = list; (q = *p) != NULL; p++){
5101 if(q[0]){
5103 /* anything after leading whitespace? */
5104 if(!*(name = skip_white_space(q)))
5105 continue;
5107 /* look for colon or space or end */
5108 for(t = name;
5109 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5110 ;/* do nothing */
5112 /* if there is a space in the field-name, skip it */
5113 if(isspace((unsigned char)*t))
5114 continue;
5116 save = *t;
5117 *t = '\0';
5119 if(strucmp(name, pf->name) != 0){
5120 *t = save;
5121 continue;
5124 *t = save;
5127 * Found the right header. See if it has a default
5128 * value set.
5131 /* remove space between name and colon */
5132 value = skip_white_space(t);
5134 if(*value == ':'){
5135 char *defval;
5137 defval = skip_white_space(++value);
5138 if(defval && *defval){
5139 if(pf->val)
5140 fs_give((void **)&pf->val);
5142 pf->val = cpystr(defval);
5146 break; /* on to next pf */
5154 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5155 * fill in the defaults
5156 * Args - list -- The list to parse.
5157 * cstmtype -- Fill the in cstmtype field with this value
5159 PINEFIELD *
5160 parse_custom_hdrs(char **list, CustomType cstmtype)
5162 PINEFIELD *pfields;
5163 int i;
5166 * add one for possible use by fcc
5167 * What is this "possible use"? I don't see it. I don't
5168 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5170 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5171 pfields = (PINEFIELD *)fs_get((size_t) i);
5172 memset(pfields, 0, (size_t) i);
5174 /* set up the custom header pfields */
5175 customized_hdr_setup(pfields, list, cstmtype);
5177 return(pfields);
5182 * Combine the two lists of headers into one list which is allocated here
5183 * and freed by the caller. Eliminate duplicates with values from the role
5184 * taking precedence over values from the default.
5186 PINEFIELD *
5187 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5189 PINEFIELD *pfields, *pf, *pf2;
5190 int max_hdrs, i;
5192 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5193 count_custom_hdrs_pf(dflthdrs,0);
5195 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5196 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5198 pf = pfields;
5199 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5200 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5201 pf->type = pf2->type;
5202 pf->cstmtype = pf2->cstmtype;
5203 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5204 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5205 pf->standard = pf2->standard;
5206 pf->next = pf+1;
5207 pf++;
5210 /* if these aren't already there, add them */
5211 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5212 /* check for already there */
5213 for(i = 0;
5214 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5215 i++)
5218 if(!pfields[i].name){ /* this is a new one */
5219 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5220 pf->type = pf2->type;
5221 pf->cstmtype = pf2->cstmtype;
5222 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5223 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5224 pf->standard = pf2->standard;
5225 pf->next = pf+1;
5226 pf++;
5230 /* fix last next pointer */
5231 if(pf != pfields)
5232 (pf-1)->next = NULL;
5234 return(pfields);
5239 * free_customs - free misc. resources associated with custom header fields
5241 * pf - pointer to first custom field
5243 void
5244 free_customs(PINEFIELD *head)
5246 PINEFIELD *pf;
5248 for(pf = head; pf && pf->name; pf = pf->next){
5250 fs_give((void **)&pf->name);
5252 if(pf->val)
5253 fs_give((void **)&pf->val);
5255 /* only true for FreeText */
5256 if(pf->textbuf)
5257 fs_give((void **)&pf->textbuf);
5259 /* only true for Address */
5260 if(pf->addr && *pf->addr)
5261 mail_free_address(pf->addr);
5264 fs_give((void **)&head);
5269 * encode_whole_header
5271 * Returns 1 if whole value should be encoded
5272 * 0 to encode only within comments
5275 encode_whole_header(char *field, METAENV *header)
5277 int retval = 0;
5278 PINEFIELD *pf;
5280 if(field && (!strucmp(field, "Subject") ||
5281 !strucmp(field, "Comment") ||
5282 !struncmp(field, "X-", 2)))
5283 retval++;
5284 else if(field && *field && header && header->custom){
5285 for(pf = header->custom; pf && pf->name; pf = pf->next){
5287 if(!pf->standard && !strucmp(pf->name, field)){
5288 retval++;
5289 break;
5294 return(retval);
5298 /*----------------------------------------------------------------------
5299 Post news via NNTP or inews
5301 Args: env -- envelope of message to post
5302 body -- body of message to post
5304 Returns: -1 if failed or cancelled, 1 if succeeded
5306 WARNING: This call function has the side effect of writing the message
5307 to the lmc.so object.
5308 ----*/
5310 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5311 void (*pipecb_f)(PIPE_S *, int, void *))
5313 char *error_mess, error_buf[200], **news_servers;
5314 char **servers_to_use;
5315 int we_cancel = 0, server_no = 0, done_posting = 0;
5316 void *orig_822_output;
5317 BODY *bp = NULL;
5319 error_buf[0] = '\0';
5320 we_cancel = busy_cue("Posting news", NULL, 0);
5322 dprint((4, "Posting: [%s]\n",
5323 (header && header->env && header->env->newsgroups)
5324 ? header->env->newsgroups : "?"));
5326 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5327 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5328 && ps_global->VAR_NNTP_SERVER[0][0])){
5329 /*---------- NNTP server defined ----------*/
5330 error_mess = NULL;
5331 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5332 && alt_nntp_servers[0][0])
5333 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5336 * Install our rfc822 output routine
5338 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5339 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5340 (void *)post_rfc822_output);
5342 server_no = 0;
5343 news_servers = (char **)fs_get(2 * sizeof(char *));
5344 news_servers[1] = NULL;
5345 while(!done_posting && servers_to_use[server_no] &&
5346 servers_to_use[server_no][0]){
5347 news_servers[0] = servers_to_use[server_no];
5348 ps_global->noshow_error = 1;
5349 #ifdef DEBUG
5350 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5351 #else
5352 sending_stream = nntp_open(news_servers, 0L);
5353 #endif
5354 ps_global->noshow_error = 0;
5356 if(sending_stream != NULL) {
5357 unsigned short save_encoding, added_encoding;
5360 * Fake that we've got clearance from the transport agent
5361 * for 8bit transport for the benefit of our output routines...
5363 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5364 && (bp = first_text_8bit(body))){
5365 int i;
5367 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5370 if(i > ENCMAX){ /* no empty encoding slots! */
5371 bp = NULL;
5373 else {
5374 added_encoding = i;
5375 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5376 save_encoding = bp->encoding;
5377 bp->encoding = added_encoding;
5382 * Set global header pointer so we can get at it later...
5384 send_header = header;
5385 ps_global->noshow_error = 1;
5386 if(nntp_mail(sending_stream, header->env, body) == 0)
5387 snprintf(error_mess = error_buf, sizeof(error_buf),
5388 _("Error posting message: %s"),
5389 sending_stream->reply);
5390 else{
5391 done_posting = 1;
5392 error_buf[0] = '\0';
5393 error_mess = NULL;
5396 error_buf[sizeof(error_buf)-1] = '\0';
5397 smtp_close(sending_stream);
5398 ps_global->noshow_error = 0;
5399 sending_stream = NULL;
5400 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5401 body_encodings[added_encoding] = NULL;
5402 bp->encoding = save_encoding;
5405 } else {
5406 /*---- Open of NNTP connection failed ------ */
5407 snprintf(error_mess = error_buf, sizeof(error_buf),
5408 _("Error connecting to news server: %s"),
5409 ps_global->c_client_error);
5410 error_buf[sizeof(error_buf)-1] = '\0';
5411 dprint((1, error_buf));
5413 server_no++;
5415 fs_give((void **)&news_servers);
5416 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5417 } else {
5418 /*----- Post via local mechanism -------*/
5419 #ifdef _WINDOWS
5420 snprintf(error_mess = error_buf, sizeof(error_buf),
5421 _("Can't post, NNTP-server must be defined!"));
5422 #else /* UNIX */
5423 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5424 pipecb_f);
5425 #endif
5428 if(we_cancel)
5429 cancel_busy_cue(0);
5431 if(error_mess){
5432 if(lmc.so && !lmc.all_written)
5433 so_give(&lmc.so); /* clean up any fcc data */
5435 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5436 return(-1);
5439 lmc.all_written = 1;
5440 return(1);
5444 /* ----------------------------------------------------------------------
5445 Figure out command to start local SMTP agent
5447 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5449 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5451 ----*/
5452 char *
5453 smtp_command(char *errbuf, size_t errbuflen)
5455 #ifdef _WINDOWS
5456 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5457 && ps_global->VAR_SMTP_SERVER[0][0]))
5458 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5460 errbuf[errbuflen-1] = '\0';
5461 #else /* UNIX */
5462 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5463 char tmp[256];
5465 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (sizeof(tmp)-3)/2, SENDMAIL,
5466 (sizeof(tmp)-3)/2, SENDMAILFLAGS);
5467 return(cpystr(tmp));
5468 # else
5469 strncpy(errbuf, _("No default posting command."), errbuflen);
5470 errbuf[errbuflen-1] = '\0';
5471 # endif
5472 #endif
5474 return(NULL);
5479 #ifndef _WINDOWS
5480 /*----------------------------------------------------------------------
5481 Hand off given message to local posting agent
5483 Args: envelope -- The envelope for the BCC and debugging
5484 header -- The text of the message header
5485 errbuf -- buffer for reporting errors (assumed non-NULL)
5486 len -- Length of errbuf
5488 ----*/
5490 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5491 char *errbuf, size_t len,
5492 void (*bigresult_f) (char *, int),
5493 void (*pipecb_f)(PIPE_S *, int, void *))
5495 #ifdef DF_SENDMAIL_PATH
5496 char cmd_buf[256];
5497 #endif
5498 char *cmd = NULL;
5501 * A bit of complicated policy implemented here.
5502 * There are two posting variables sendmail-path and smtp-server.
5503 * Precedence is in that order.
5504 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5505 * Precedence is in that order.
5506 * Said differently, the order goes something like what's below.
5508 * NOTE: the fixed/command-line/user precendence handling is also
5509 * indicated by what's pointed to by ps_global->VAR_*, but since
5510 * that also includes the global defaults, it's not sufficient.
5513 if(ps_global->FIX_SENDMAIL_PATH
5514 && ps_global->FIX_SENDMAIL_PATH[0]){
5515 cmd = ps_global->FIX_SENDMAIL_PATH;
5517 else if(!(ps_global->FIX_SMTP_SERVER
5518 && ps_global->FIX_SMTP_SERVER[0])){
5519 if(ps_global->COM_SENDMAIL_PATH
5520 && ps_global->COM_SENDMAIL_PATH[0]){
5521 cmd = ps_global->COM_SENDMAIL_PATH;
5523 else if(!(ps_global->COM_SMTP_SERVER
5524 && ps_global->COM_SMTP_SERVER[0])){
5525 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5526 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5527 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5528 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5529 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5530 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5531 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5532 else
5533 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5535 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5536 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5537 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5538 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5539 if(ps_global->GLO_SENDMAIL_PATH
5540 && ps_global->GLO_SENDMAIL_PATH[0]){
5541 cmd = ps_global->GLO_SENDMAIL_PATH;
5543 #ifdef DF_SENDMAIL_PATH
5545 * This defines the default method of posting. So,
5546 * unless we're told otherwise use it...
5548 else if(!(ps_global->GLO_SMTP_SERVER
5549 && ps_global->GLO_SMTP_SERVER[0])){
5550 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5551 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5553 #endif
5558 *errbuf = '\0';
5559 if(cmd){
5560 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5562 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5563 return(1);
5565 else
5566 return(0);
5571 /*----------------------------------------------------------------------
5572 Hand off given message to local posting agent
5574 Args: envelope -- The envelope for the BCC and debugging
5575 header -- The text of the message header
5576 errbuf -- buffer for reporting errors (assumed non-NULL)
5577 errbuflen -- Length of errbuf
5579 Fork off mailer process and pipe the message into it
5580 Called to post news via Inews when NNTP is unavailable
5582 ----*/
5583 char *
5584 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5585 size_t errbuflen,
5586 void (*bigresult_f) (char *, int),
5587 void (*pipecb_f)(PIPE_S *, int, void *))
5589 char *err = NULL;
5590 #ifdef SENDNEWS
5591 char *s;
5592 char tmp[200];
5594 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5595 *s = '\0';
5597 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5598 strncpy(tmp, err, sizeof(tmp)-1);
5599 tmp[sizeof(tmp)-1] = '\0';
5600 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5601 SENDNEWS, tmp);
5604 if(s)
5605 *s = ' '; /* restore the date */
5607 #else /* !SENDNEWS */ /* this is the default case */
5608 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5609 err[errbuflen-1] = '\0';
5610 #endif /* !SENDNEWS */
5611 return(err);
5616 /*----------------------------------------------------------------------
5617 Hand off message to local MTA; it parses recipients from 822 header
5619 Args: header -- struct containing header data
5620 body -- struct containing message body data
5621 cmd -- command to use for handoff (%s says where file should go)
5622 errs -- pointer to buf to hold errors
5624 ----*/
5625 char *
5626 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5627 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5628 void (*pipecb_f)(PIPE_S *, int, void *))
5630 char *result = NULL;
5631 PIPE_S *pipe;
5633 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5635 if((pipe = open_system_pipe(cmd, &result, NULL,
5636 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5637 0, pipecb_f, pipe_report_error)) != NULL){
5638 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5639 (TCPSTREAM *) pipe)){
5640 strncpy(errs, _("Error posting."), errslen-1);
5641 errs[errslen-1] = '\0';
5644 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5645 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5646 if(result && bigresult_f)
5647 (*bigresult_f)(result, CM_BR_ERROR);
5650 else
5651 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5653 if(result){
5654 our_unlink(result);
5655 fs_give((void **)&result);
5658 return(*errs ? errs : NULL);
5663 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5664 * pipes rather than a tcp stream
5666 long
5667 pine_pipe_soutr_nl (void *stream, char *s)
5669 long rv = T;
5670 char *p;
5671 size_t n;
5673 while(*s && rv){
5674 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0)
5675 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5676 if(rv < 0){
5677 if(errno != EINTR){
5678 rv = 0;
5679 break;
5682 else{
5683 s += rv;
5684 n -= rv;
5687 if(p && rv){
5688 s = p + 2; /* write UNIX EOL */
5689 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5690 if(rv < 0 && errno != EINTR){
5691 rv = 0;
5692 break;
5695 else
5696 break;
5699 return(rv);
5701 #endif
5704 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5708 * helpful def's
5710 #define S(X) ((PIPE_S *)(X))
5711 #define GETBUFLEN (4 * MAILTMPLEN)
5714 void *
5715 piped_smtp_open (char *host, char *service, long unsigned int port)
5717 char *postcmd;
5718 void *rv = NULL;
5720 if(strucmp(host, "localhost")){
5721 char tmp[MAILTMPLEN];
5723 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5724 sizeof(tmp)-50, host);
5725 tmp[sizeof(tmp)-1] = '\0';
5726 mm_log(tmp, ERROR);
5728 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5729 rv = open_system_pipe(postcmd, NULL, NULL,
5730 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5731 0, NULL, pipe_report_error);
5732 fs_give((void **) &postcmd);
5734 else
5735 mm_log(ps_global->c_client_error, ERROR);
5737 return(rv);
5741 void *
5742 piped_aopen (NETMBX *mb, char *service, char *user)
5744 return(NULL);
5749 * piped_soutr - Replacement for tcp_soutr that writes one of our
5750 * pipes rather than a tcp stream
5752 long
5753 piped_soutr (void *stream, char *s)
5755 return(piped_sout(stream, s, strlen(s)));
5760 * piped_sout - Replacement for tcp_soutr that writes one of our
5761 * pipes rather than a tcp stream
5763 long
5764 piped_sout (void *stream, char *s, long unsigned int size)
5766 int i, o;
5768 if(S(stream)->out.d < 0)
5769 return(0L);
5771 if((i = (int) size) != 0){
5772 while((o = write(S(stream)->out.d, s, i)) != i)
5773 if(o < 0){
5774 if(errno != EINTR){
5775 piped_abort(stream);
5776 return(0L);
5779 else{
5780 s += o; /* try again, fix up counts */
5781 i -= o;
5785 return(1L);
5790 * piped_getline - Replacement for tcp_getline that reads one
5791 * of our pipes rather than a tcp pipe
5793 * C-client expects that the \r\n will be stripped off.
5795 char *
5796 piped_getline (void *stream)
5798 static int cnt;
5799 static char *ptr;
5800 int n, m;
5801 char *ret, *s, *sp, c = '\0', d;
5803 if(S(stream)->in.d < 0)
5804 return(NULL);
5806 if(!S(stream)->tmp){ /* initialize! */
5807 /* alloc space to collect input (freed in close_system_pipe) */
5808 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5809 memset(S(stream)->tmp, 0, GETBUFLEN);
5810 cnt = -1;
5813 while(cnt < 0){
5814 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5815 if(errno != EINTR){
5816 piped_abort(stream);
5817 return(NULL);
5820 if(cnt == 0){
5821 piped_abort(stream);
5822 return(NULL);
5825 ptr = S(stream)->tmp;
5828 s = ptr;
5829 n = 0;
5830 while(cnt--){
5831 d = *ptr++;
5832 if((c == '\015') && (d == '\012')){
5833 ret = (char *)fs_get (n--);
5834 memcpy(ret, s, n);
5835 ret[n] = '\0';
5836 return(ret);
5839 n++;
5840 c = d;
5842 /* copy partial string from buffer */
5843 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5844 /* get more data */
5845 while(cnt < 0){
5846 memset(S(stream)->tmp, 0, GETBUFLEN);
5847 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5848 if(errno != EINTR){
5849 fs_give((void **) &ret);
5850 piped_abort(stream);
5851 return(NULL);
5854 if(cnt == 0){
5855 if(n > 0)
5856 ret[n-1] = '\0'; /* to try to get error message logged */
5857 else{
5858 piped_abort(stream);
5859 return(NULL);
5863 ptr = S(stream)->tmp;
5866 if(c == '\015' && *ptr == '\012'){
5867 ptr++;
5868 cnt--;
5869 ret[n - 1] = '\0'; /* tie off string with null */
5871 else if ((s = piped_getline(stream)) != NULL) {
5872 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5873 memcpy(ret, sp, n); /* copy first part */
5874 memcpy(ret + n, s, m); /* and second part */
5875 fs_give((void **) &sp); /* flush first part */
5876 fs_give((void **) &s); /* flush second part */
5877 ret[n + m] = '\0'; /* tie off string with null */
5880 return(ret);
5885 * piped_close - Replacement for tcp_close that closes pipes to our
5886 * child rather than a tcp connection
5888 void
5889 piped_close(void *stream)
5892 * Uninstall our hooks into smtp_send since it's being used by
5893 * the nntp driver as well...
5895 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5900 * piped_abort - close down the pipe we were using to post
5902 void
5903 piped_abort(void *stream)
5905 if(S(stream)->in.d >= 0){
5906 close(S(stream)->in.d);
5907 S(stream)->in.d = -1;
5910 if(S(stream)->out.d){
5911 close(S(stream)->out.d);
5912 S(stream)->out.d = -1;
5917 char *
5918 piped_host(void *stream)
5920 return(ps_global->hostname ? ps_global->hostname : "localhost");
5924 unsigned long
5925 piped_port(void *stream)
5927 return(0L);