* Update to version 2.19.5
[alpine.git] / pith / send.c
blob4af84ebe46533a01b2986443e2bd1e12330d1f11
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);
1569 /*----------------------------------------------------------------------
1570 Set answered flags for messages specified by reply structure
1572 Args: reply --
1574 Returns: with appropriate flags set and index cache entries suitably tweeked
1575 ----*/
1576 void
1577 update_answered_flags(REPLY_S *reply)
1579 char *seq = NULL, *p;
1580 long i, ourstream = 0, we_cancel = 0;
1581 MAILSTREAM *stream = NULL;
1583 /* nothing to flip in a pseudo reply */
1584 if(reply && (reply->msgno || reply->uid)){
1585 int j;
1586 MAILSTREAM *m;
1589 * If an established stream will do, use it, else
1590 * build one unless we have an array of msgno's...
1592 * I was just mimicking what was already here. I don't really
1593 * understand why we use strcmp instead of same_stream_and_mailbox().
1594 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1595 * Hubert 2003-07-09
1597 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1598 m = ps_global->s_pool.streams[j];
1599 if(m && reply->mailbox && m->mailbox
1600 && !strcmp(reply->mailbox, m->mailbox))
1601 stream = m;
1604 if(!stream && reply->msgno)
1605 return;
1608 * This is here only for people who ran pine4.42 and are
1609 * processing postponed mail from 4.42 now. Pine4.42 saved the
1610 * original mailbox name in the canonical name's position in
1611 * the postponed-msgs folder so it won't match the canonical
1612 * name from the stream.
1614 if(!stream && (!reply->origmbox ||
1615 (reply->mailbox &&
1616 !strcmp(reply->origmbox, reply->mailbox))))
1617 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1619 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1620 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1621 if(!stream){
1622 if((stream = pine_mail_open(NULL,
1623 reply->origmbox ? reply->origmbox
1624 : reply->mailbox,
1625 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1626 NULL)) != NULL){
1627 ourstream++;
1629 else{
1630 if(we_cancel)
1631 cancel_busy_cue(0);
1633 return;
1637 if(stream->uid_validity == reply->data.uid.validity){
1638 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1639 if(i){
1640 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1641 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1644 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1645 SIZEOF_20KBUF-(p-tmp_20k_buf));
1646 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1649 if(reply->forwarded){
1651 * $Forwarded is a regular keyword so we only try to
1652 * set it if the stream allows keywords.
1653 * We could mess up if the stream has keywords but just
1654 * isn't allowing anymore and $Forwarded already exists,
1655 * but what are the odds?
1657 if(stream && stream->kwd_create)
1658 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1659 FORWARDED_FLAG,
1660 ST_SET | ((reply->uid) ? ST_UID : 0L));
1662 else
1663 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1664 "\\ANSWERED",
1665 ST_SET | ((reply->uid) ? ST_UID : 0L));
1667 if(seq)
1668 fs_give((void **)&seq);
1671 if(ourstream)
1672 pine_mail_close(stream); /* clean up dangling stream */
1674 if(we_cancel)
1675 cancel_busy_cue(0);
1681 * phone_home_from - make phone home request's from address IMpersonal.
1682 * Doesn't include user's personal name.
1684 ADDRESS *
1685 phone_home_from(void)
1687 ADDRESS *addr = mail_newaddr();
1688 char tmp[32];
1690 /* garble up mailbox name */
1691 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1692 tmp[sizeof(tmp)-1] = '\0';
1693 addr->mailbox = cpystr(tmp);
1694 addr->host = cpystr(ps_global->maildomain);
1695 return(addr);
1700 * one-way-hash a username into an 8-digit decimal number
1702 * Corey Satten, corey@cac.washington.edu, 7/15/98
1704 unsigned int
1705 phone_home_hash(char *s)
1707 unsigned int h;
1709 for (h=0; *s; ++s) {
1710 if (h & 1)
1711 h = (h>>1) | (PH_MAXHASH/2);
1712 else
1713 h = (h>>1);
1715 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1718 return (h);
1722 /*----------------------------------------------------------------------
1723 Call the mailer, SMTP, sendmail or whatever
1725 Args: header -- full header (envelope and local parts) of message to send
1726 body -- The full body of the message including text
1727 alt_smtp_servers --
1728 verbosefile -- non-null means caller wants verbose interaction and the resulting
1729 output file name to be returned
1731 Returns: -1 if failed, 1 if succeeded
1732 ----*/
1734 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1735 int flags, void (*bigresult_f)(char *, int),
1736 void (*pipecb_f)(PIPE_S *, int, void *))
1738 char error_buf[200], *error_mess = NULL, *postcmd;
1739 ADDRESS *a;
1740 ENVELOPE *fake_env = NULL;
1741 int addr_error_count, we_cancel = 0;
1742 long smtp_opts = 0L;
1743 char *verbose_file = NULL;
1744 BODY *bp = NULL;
1745 PINEFIELD *pf;
1746 BODY *origBody = body;
1748 dprint((4, "Sending mail...\n"));
1750 /* Check for any recipients */
1751 for(pf = header->local; pf && pf->name; pf = pf->next)
1752 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1753 break;
1755 if(!pf){
1756 q_status_message(SM_ORDER,3,3,
1757 _("Can't send message. No recipients specified!"));
1758 return(0);
1761 #ifdef SMIME
1762 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1763 int result;
1765 STORE_S *so = lmc.so;
1766 lmc.so = NULL;
1768 result = 1;
1770 if(ps_global->smime->do_encrypt)
1771 result = encrypt_outgoing_message(header, &body);
1773 /* need to free new body from encrypt if sign fails? */
1774 if(result && ps_global->smime->do_sign)
1775 result = sign_outgoing_message(header, &body, ps_global->smime->do_encrypt);
1777 lmc.so = so;
1779 if(!result)
1780 return 0;
1782 #endif
1784 /* set up counts and such to keep track sent percentage */
1785 send_bytes_sent = 0;
1786 gf_filter_init(); /* zero piped byte count, 'n */
1787 send_bytes_to_send = send_body_size(body); /* count body bytes */
1788 ps_global->c_client_error[0] = error_buf[0] = '\0';
1789 we_cancel = busy_cue(_("Sending mail"),
1790 send_bytes_to_send ? sent_percent : NULL, 0);
1792 #ifndef _WINDOWS
1794 /* try posting via local "<mta> <-t>" if specified */
1795 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1796 if(error_buf[0])
1797 error_mess = error_buf;
1799 goto done;
1802 #endif
1805 * If the user's asked for it, and we find that the first text
1806 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1808 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1809 smtp_opts |= SOP_8BITMIME;
1811 #ifdef DEBUG
1812 #ifndef DEBUGJOURNAL
1813 if(debug > 5 || (flags & CM_VERBOSE))
1814 #endif
1815 smtp_opts |= SOP_DEBUG;
1816 #endif
1818 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1819 smtp_opts |= SOP_DSN;
1820 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1821 if(flags & CM_DSN_DELAY)
1822 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1823 if(flags & CM_DSN_SUCCESS)
1824 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1827 * If it isn't Never, then we're always going to let them
1828 * know about failures. This means we don't allow for the
1829 * possibility of setting delay or success without failure.
1831 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1833 if(flags & CM_DSN_FULL)
1834 smtp_opts |= SOP_DSN_RETURN_FULL;
1840 * Set global header pointer so post_rfc822_output can get at it when
1841 * it's called back from c-client's sending routine...
1843 send_header = header;
1846 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1847 * The purpose is to give smtp_mail the list for SMTP RCPT when
1848 * there are recipients in pine's METAENV that are outside c-client's
1849 * envelope.
1851 * NOTE: If there aren't any, don't bother. Dealt with it below.
1853 for(pf = header->local; pf && pf->name; pf = pf->next)
1854 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1855 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1856 || *pf->addr == header->env->bcc))
1857 break;
1859 if(pf && pf->name){
1860 ADDRESS **tail;
1862 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1863 memset(fake_env, 0, sizeof(ENVELOPE));
1864 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1865 tail = &(fake_env->to);
1866 for(pf = header->local; pf && pf->name; pf = pf->next)
1867 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1868 *tail = rfc822_cpy_adr(*pf->addr);
1869 while(*tail)
1870 tail = &((*tail)->next);
1875 * Install our rfc822 output routine
1877 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1878 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1881 * Allow for verbose posting
1883 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1884 (void *) pine_smtp_verbose_out);
1887 * We do this because we want mm_log to put the error message into
1888 * c_client_error instead of showing it itself.
1890 ps_global->noshow_error = 1;
1893 * OK, who posts what? We tried an mta_handoff above, but there
1894 * was either none specified or we decided not to use it. So,
1895 * if there's an smtp-server defined anywhere,
1897 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1898 /*---------- SMTP ----------*/
1899 dprint((4, "call_mailer: via TCP (%s)\n",
1900 alt_smtp_servers[0]));
1901 TIME_STAMP("smtp-open start (tcp)", 1);
1902 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1904 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1905 && ps_global->VAR_SMTP_SERVER[0][0]){
1906 /*---------- SMTP ----------*/
1907 dprint((4, "call_mailer: via TCP\n"));
1908 TIME_STAMP("smtp-open start (tcp)", 1);
1909 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1911 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1912 char *cmdlist[2];
1914 /*----- Send via LOCAL SMTP agent ------*/
1915 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1917 TIME_STAMP("smtp-open start (pipe)", 1);
1918 fs_give((void **) &postcmd);
1919 cmdlist[0] = "localhost";
1920 cmdlist[1] = NULL;
1921 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1922 SMTPTCPPORT, smtp_opts);
1923 /* BUG: should provide separate stderr output! */
1926 ps_global->noshow_error = 0;
1928 TIME_STAMP("smtp open", 1);
1929 if(sending_stream){
1930 unsigned short save_encoding, added_encoding;
1932 dprint((1, "Opened SMTP server \"%s\"\n",
1933 net_host(sending_stream->netstream)
1934 ? net_host(sending_stream->netstream) : "?"));
1936 if(flags & CM_VERBOSE){
1937 TIME_STAMP("verbose start", 1);
1938 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1939 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1940 if(!smtp_verbose(sending_stream)){
1941 snprintf(error_mess = error_buf, sizeof(error_buf),
1942 "Mail not sent. VERBOSE mode error%s%.50s.",
1943 (sending_stream && sending_stream->reply)
1944 ? ": ": "",
1945 (sending_stream && sending_stream->reply)
1946 ? sending_stream->reply : "");
1947 error_buf[sizeof(error_buf)-1] = '\0';
1950 else{
1951 our_unlink(verbose_file);
1952 strncpy(error_mess = error_buf,
1953 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1954 error_buf[sizeof(error_buf)-1] = '\0';
1957 else{
1958 strncpy(error_mess = error_buf,
1959 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1960 error_buf[sizeof(error_buf)-1] = '\0';
1963 TIME_STAMP("verbose end", 1);
1967 * Before we actually send data, see if we have to protect
1968 * the first text body part from getting encoded. We protect
1969 * it from getting encoded in "pine_rfc822_output_body" by
1970 * temporarily inventing a synonym for ENC8BIT...
1971 * This works like so:
1972 * Suppose bp->encoding is set to ENC8BIT.
1973 * We change that here to some unused value (added_encoding) and
1974 * set body_encodings[added_encoding] to "8BIT".
1975 * Then post_rfc822_output is called which calls
1976 * pine_rfc822_output_body. Inside that routine
1977 * pine_write_body_header writes out the encoding for the
1978 * part. Normally it would see encoding == ENC8BIT and it would
1979 * change that to QUOTED-PRINTABLE, but since encoding has been
1980 * set to added_encoding it uses body_encodings[added_encoding]
1981 * which is "8BIT" instead. Then the actual body is written by
1982 * pine_write_body_header which does not do the gf_8bit_qp
1983 * filtering because encoding != ENC8BIT (instead it's equal
1984 * to added_encoding).
1986 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1987 && sending_stream->protocol.esmtp.eightbit.want){
1988 int i;
1990 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1993 if(i > ENCMAX){ /* no empty encoding slots! */
1994 bp = NULL;
1996 else {
1997 added_encoding = i;
1998 body_encodings[added_encoding] = body_encodings[ENC8BIT];
1999 save_encoding = bp->encoding;
2000 bp->encoding = added_encoding;
2004 if(sending_stream->protocol.esmtp.ok
2005 && sending_stream->protocol.esmtp.dsn.want
2006 && !sending_stream->protocol.esmtp.dsn.ok)
2007 q_status_message(SM_ORDER,3,3,
2008 _("Delivery Status Notification not available from this server."));
2010 TIME_STAMP("smtp start", 1);
2011 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2012 fake_env ? fake_env : header->env, body)){
2014 snprintf(error_buf, sizeof(error_buf),
2015 _("Mail not sent. Sending error%s%s"),
2016 (sending_stream && sending_stream->reply) ? ": ": ".",
2017 (sending_stream && sending_stream->reply)
2018 ? sending_stream->reply : "");
2019 error_buf[sizeof(error_buf)-1] = '\0';
2020 dprint((1, error_buf));
2021 addr_error_count = 0;
2022 if(fake_env){
2023 for(a = fake_env->to; a != NULL; a = a->next)
2024 if(a->error != NULL){
2025 if(addr_error_count++ < MAX_ADDR_ERROR){
2028 * Too complicated to figure out which header line
2029 * has the error in the fake_env case, so just
2030 * leave cursor at default.
2034 if(error_mess) /* previous error? */
2035 q_status_message(SM_ORDER, 4, 7, error_mess);
2037 error_mess = tidy_smtp_mess(a->error,
2038 _("Mail not sent: %.80s"),
2039 error_buf, sizeof(error_buf));
2042 dprint((1, "Send Error: \"%s\"\n",
2043 a->error));
2046 else{
2047 for(pf = header->local; pf && pf->name; pf = pf->next)
2048 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2049 for(a = *pf->addr; a != NULL; a = a->next)
2050 if(a->error != NULL){
2051 if(addr_error_count++ < MAX_ADDR_ERROR){
2053 if(error_mess) /* previous error? */
2054 q_status_message(SM_ORDER, 4, 7, error_mess);
2056 error_mess = tidy_smtp_mess(a->error,
2057 _("Mail not sent: %.80s"),
2058 error_buf, sizeof(error_buf));
2061 dprint((1, "Send Error: \"%s\"\n",
2062 a->error));
2066 if(!error_mess)
2067 error_mess = error_buf;
2070 /* repair modified "body_encodings" array? */
2071 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2072 && sending_stream->protocol.esmtp.eightbit.want){
2073 body_encodings[added_encoding] = NULL;
2074 bp->encoding = save_encoding;
2077 TIME_STAMP("smtp closing", 1);
2078 smtp_close(sending_stream);
2079 sending_stream = NULL;
2080 TIME_STAMP("smtp done", 1);
2082 else if(!error_mess){
2083 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2084 ps_global->c_client_error[0] ? ": " : "",
2085 ps_global->c_client_error);
2086 error_buf[sizeof(error_buf)-1] = '\0';
2089 if(verbose_file){
2090 if(verbose_send_output){
2091 TIME_STAMP("verbose start", 1);
2092 fclose(verbose_send_output);
2093 verbose_send_output = NULL;
2094 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2096 if(bigresult_f)
2097 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2099 TIME_STAMP("verbose end", 1);
2102 fs_give((void **)&verbose_file);
2106 * Restore original 822 emitter...
2108 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2110 if(fake_env)
2111 mail_free_envelope(&fake_env);
2113 done:
2115 #ifdef SMIME
2116 /* Free replacement encrypted body */
2117 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2119 if(body->type == TYPEMULTIPART){
2120 /* Just get rid of first part, it's actually origBody */
2121 void *x = body->nested.part;
2123 body->nested.part = body->nested.part->next;
2125 fs_give(&x);
2128 pine_free_body(&body);
2130 #endif
2132 if(we_cancel)
2133 cancel_busy_cue(0);
2135 TIME_STAMP("call_mailer done", 1);
2136 /*-------- Did message make it ? ----------*/
2137 if(error_mess){
2138 /*---- Error sending mail -----*/
2139 if(lmc.so && !lmc.all_written)
2140 so_give(&lmc.so);
2142 if(error_mess){
2143 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2144 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2147 return(-1);
2149 else{
2150 lmc.all_written = 1;
2151 return(1);
2157 * write_postponed - exported method to write the given message
2158 * to the postponed folder
2161 write_postponed(METAENV *header, struct mail_bodystruct *body)
2163 char **pp, *folder;
2164 int rv = 0, sz;
2165 CONTEXT_S *fcc_cntxt = NULL;
2166 PINEFIELD *pf;
2167 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2169 if(!ps_global->VAR_POSTPONED_FOLDER
2170 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2171 q_status_message(SM_ORDER | SM_DING, 3, 3,
2172 _("No postponed file defined"));
2173 return(-1);
2176 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2178 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2180 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2182 if(lmc.so){
2183 /* BUG: writem sufficient ? */
2184 for(pf = header->local; pf && pf->name; pf = pf->next)
2185 for(pp = writem; *pp; pp++)
2186 if(!strucmp(pf->name, *pp)){
2187 pf->localcopy = 1;
2188 pf->writehdr = 1;
2189 break;
2193 * Work around c-client reply-to bug. C-client will
2194 * return a reply_to in an envelope even if there is
2195 * no reply-to header field. We want to note here whether
2196 * the reply-to is real or not.
2198 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2199 for(pf = header->local; pf; pf = pf->next)
2200 if(!strcmp(pf->name, "Reply-To")){
2201 pf->writehdr = 1;
2202 pf->localcopy = 1;
2203 if(header->env->reply_to)
2204 pf->textbuf = cpystr("Full");
2205 else
2206 pf->textbuf = cpystr("Empty");
2210 * Write the list of custom headers to the
2211 * X-Our-Headers header so that we can recover the
2212 * list in redraft.
2214 sz = 0;
2215 for(pf = header->custom; pf && pf->name; pf = pf->next)
2216 sz += strlen(pf->name) + 1;
2218 if(sz){
2219 int i;
2220 char *pstart, *pend;
2222 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2225 pf->writehdr = 1;
2226 pf->localcopy = 1;
2227 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2228 pf->text = &pf->textbuf;
2229 pf->textbuf[sz] = '\0'; /* tie off overflow */
2230 /* note: "pf" overloaded */
2231 for(pf = header->custom; pf && pf->name; pf = pf->next){
2232 int r = sz - (pend - pstart); /* remaining buffer */
2234 if(r > 0 && r != sz){
2235 r--;
2236 *pend++ = ',';
2239 sstrncpy(&pend, pf->name, r);
2243 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2244 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2245 rv = -1;
2247 so_give(&lmc.so);
2249 else {
2250 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2251 "Can't allocate internal storage: %s ",
2252 error_description(errno));
2253 rv = -1;
2256 fs_give((void **) &folder);
2257 return(rv);
2262 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2264 if(fcc && *fcc){
2265 lmc.all_written = lmc.text_written = 0;
2266 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2267 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2269 else
2270 lmc.so = NULL;
2272 return(TRUE);
2277 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2279 int rv = TRUE;
2281 if(lmc.so){
2282 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2283 char label[50];
2285 strncpy(label, "Fcc", sizeof(label));
2286 label[sizeof(label)-1] = '\0';
2287 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2288 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2289 label[sizeof(label)-1] = '\0';
2292 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2294 else{
2295 rv = FALSE;
2298 so_give(&lmc.so);
2301 return(rv);
2305 /*----------------------------------------------------------------------
2306 Checks to make sure the fcc is available and can be opened
2308 Args: fcc -- the name of the fcc to create. It can't be NULL.
2309 fcc_cntxt -- Returns the context the fcc is in.
2310 force -- supress user option prompt
2312 Returns allocated storage object on success, NULL on failure
2313 ----*/
2314 STORE_S *
2315 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2317 int exists, ok = 0;
2319 ps_global->mm_log_error = 0;
2322 * check for fcc's existance...
2324 TIME_STAMP("open_fcc start", 1);
2325 if(!is_absolute_path(fcc) && context_isambig(fcc)
2326 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2327 int flip_dot = 0;
2330 * Don't want to preclude a user from Fcc'ing a .name'd folder
2332 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2333 flip_dot = 1;
2334 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2338 * We only want to set the "context" if fcc is an ambiguous
2339 * name. Otherwise, our "relativeness" rules for contexts
2340 * (implemented in context.c) might cause the name to be
2341 * interpreted in the wrong context...
2343 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2344 *fcc_cntxt = ps_global->context_list;
2346 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2347 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2348 if(force
2349 || (pith_opt_save_create_prompt
2350 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2352 ps_global->noshow_error = 1;
2354 if(context_create(*fcc_cntxt, NULL, fcc))
2355 ok++;
2357 ps_global->noshow_error = 0;
2359 else
2360 ok--; /* declined! */
2362 else
2363 ok++; /* found! */
2365 if(flip_dot)
2366 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2368 free_folder_list(*fcc_cntxt);
2370 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2371 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2372 ok++;
2374 else{
2375 if(force
2376 || (pith_opt_save_create_prompt
2377 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2379 ps_global->mm_log_error = 0;
2380 ps_global->noshow_error = 1;
2382 ok = pine_mail_create(NULL, fcc) != 0L;
2384 ps_global->noshow_error = 0;
2386 else
2387 ok--; /* declined! */
2391 TIME_STAMP("open_fcc done.", 1);
2392 if(ok > 0){
2393 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2395 else{
2396 int l1, l2, l3, wid, w;
2397 char *errstr, tmp[MAILTMPLEN];
2398 char *s1, *s2;
2400 if(ok == 0){
2401 if(ps_global->mm_log_error){
2402 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2403 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2405 l1 = strlen(s1);
2406 l2 = strlen(s2);
2407 l3 = strlen(ps_global->c_client_error);
2408 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2409 ? ps_global->ttyo->screen_cols : 80;
2410 w = wid - l1 - l2 - 5;
2412 snprintf(errstr = tmp, sizeof(tmp),
2413 "%.99s\"%.*s%.99s\".%.99s",
2415 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2416 ps_global->c_client_error,
2417 (l3 > w) ? "..." : "",
2418 s2);
2419 tmp[sizeof(tmp)-1] = '\0';
2422 else
2423 errstr = _("Fcc creation error. Message NOT sent or copied.");
2425 else
2426 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2428 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2431 return(NULL);
2435 /*----------------------------------------------------------------------
2436 mail_append() the fcc accumulated in temp_storage to proper destination
2438 Args: fcc -- name of folder
2439 fcc_cntxt -- context for folder
2440 temp_storage -- String of file where Fcc has been accumulated
2442 This copies the string of file to the actual folder, which might be IMAP
2443 or a disk folder. The temp_storage is freed after it is written.
2444 An error message is produced if this fails.
2445 ----*/
2447 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2448 MAILSTREAM *stream, char *label, char *flags)
2450 STRING msg;
2451 CONTEXT_S *cntxt;
2452 int we_cancel = 0;
2454 if(!tmp_storage)
2455 return(0);
2457 TIME_STAMP("write_fcc start.", 1);
2458 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2459 if(label && *label){
2460 char msg_buf[80];
2462 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2463 msg_buf[sizeof(msg_buf)-1] = '\0';
2464 strncat(msg_buf, label, sizeof(msg_buf)-10);
2465 we_cancel = busy_cue(msg_buf, NULL, 0);
2467 else
2468 we_cancel = busy_cue(NULL, NULL, 1);
2470 so_seek(tmp_storage, 0L, 0);
2473 * Before changing this note that these lines depend on the
2474 * definition of FCC_SOURCE.
2476 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2477 strlen((char *)so_text(tmp_storage)));
2479 cntxt = fcc_cntxt;
2481 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2482 cancel_busy_cue(-1);
2483 we_cancel = 0;
2485 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2486 "Write to \"%s\" FAILED!!!", fcc);
2487 dprint((1, "ERROR appending %s in \"%s\"",
2488 fcc ? fcc : "?",
2489 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2490 return(0);
2493 if(we_cancel)
2494 cancel_busy_cue(label ? 0 : -1);
2496 dprint((4, "done.\n"));
2497 TIME_STAMP("write_fcc done.", 1);
2498 return(1);
2503 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2504 * contained.
2506 BODY *
2507 first_text_8bit(struct mail_bodystruct *body)
2509 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2510 body = &body->nested.part->body;
2512 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2513 ? body : NULL);
2518 * Build and return the "From:" address for outbound messages from
2519 * global data...
2521 ADDRESS *
2522 generate_from(void)
2524 ADDRESS *addr = mail_newaddr();
2525 if(ps_global->VAR_PERSONAL_NAME){
2526 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2527 removing_leading_and_trailing_white_space(addr->personal);
2528 if(addr->personal[0] == '\0')
2529 fs_give((void **)&addr->personal);
2532 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2533 addr->host = cpystr(ps_global->maildomain);
2534 removing_leading_and_trailing_white_space(addr->mailbox);
2535 removing_leading_and_trailing_white_space(addr->host);
2536 return(addr);
2541 * set_mime_type_by_grope - sniff the given storage object to determine its
2542 * type, subtype, encoding, and charset
2544 * "Type" and "encoding" must be set before calling this routine.
2545 * If "type" is set to something other than TYPEOTHER on entry,
2546 * then that is the "type" we wish to use. Same for "encoding"
2547 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2548 * figure them out here. If "type" is already set, we also
2549 * leave subtype alone. If not, we figure out subtype here.
2550 * There is a chance that we will upgrade encoding to a "higher"
2551 * level. For example, if it comes in as 7BIT we may change
2552 * that to 8BIT if we find a From_ we want to escape.
2553 * We may also set the charset attribute if the type is TEXT.
2555 * NOTE: this is rather inefficient if the store object is a CharStar
2556 * but the win is all types are handled the same
2558 void
2559 set_mime_type_by_grope(struct mail_bodystruct *body)
2561 #define RBUFSZ (8193)
2562 unsigned char *buf, *p, *bol;
2563 register size_t n;
2564 long max_line = 0L,
2565 eight_bit_chars = 0L,
2566 line_so_far = 0L,
2567 len = 0L;
2568 STORE_S *so = (STORE_S *)body->contents.text.data;
2569 unsigned short new_encoding = ENCOTHER;
2570 int we_cancel = 0;
2571 #ifdef ENCODE_FROMS
2572 short froms = 0, dots = 0,
2573 bmap = 0x1, dmap = 0x1;
2574 #endif
2576 we_cancel = busy_cue(NULL, NULL, 1);
2578 buf = (unsigned char *)fs_get(RBUFSZ);
2579 so_seek(so, 0L, 0);
2581 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2584 buf[n] = '\0';
2586 if(n){ /* check first few bytes to look for magic numbers */
2587 if(body->type == TYPEOTHER){
2588 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2589 body->type = TYPEIMAGE;
2590 body->subtype = cpystr("GIF");
2592 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2593 && buf[2] == 0xFF && buf[3] == 0xE0
2594 && !strncmp((char *)&buf[6], "JFIF", 4)){
2595 body->type = TYPEIMAGE;
2596 body->subtype = cpystr("JPEG");
2598 else if((buf[0] == 'M' && buf[1] == 'M')
2599 || (buf[0] == 'I' && buf[1] == 'I')){
2600 body->type = TYPEIMAGE;
2601 body->subtype = cpystr("TIFF");
2603 else if((buf[0] == '%' && buf[1] == '!')
2604 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2605 body->type = TYPEAPPLICATION;
2606 body->subtype = cpystr("PostScript");
2608 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2609 body->type = TYPEAPPLICATION;
2610 body->subtype = cpystr("PDF");
2612 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2613 body->type = TYPEAUDIO;
2614 body->subtype = cpystr("Basic");
2616 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2617 && buf[2] == 0x16 && buf[3] == 0x00){
2618 body->type = TYPEAPPLICATION;
2619 body->subtype = cpystr("APPLEFILE");
2621 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2622 && buf[2] == 0x03 && buf[3] == 0x04){
2623 body->type = TYPEAPPLICATION;
2624 body->subtype = cpystr("ZIP");
2628 * if type was set above, but no encoding specified, go
2629 * ahead and make it BASE64...
2631 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2632 body->encoding = ENCBINARY;
2635 else{
2636 /* PROBLEM !!! */
2637 if(body->type == TYPEOTHER){
2638 body->type = TYPEAPPLICATION;
2639 body->subtype = cpystr("octet-stream");
2640 if(body->encoding == ENCOTHER)
2641 body->encoding = ENCBINARY;
2645 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2646 #if defined(DOS) || defined(OS2) /* for binary file detection */
2647 int lastchar = '\0';
2648 #define BREAKOUT 300 /* a value that a character can't be */
2649 #endif
2651 p = bol = buf;
2652 len = n;
2653 while (n--){
2654 /* Some people don't like quoted-printable caused by leading Froms */
2655 #ifdef ENCODE_FROMS
2656 Find_Froms(froms, dots, bmap, dmap, *p);
2657 #endif
2658 if(*p == '\n'){
2659 max_line = MAX(max_line, line_so_far + p - bol);
2660 bol = NULL; /* clear beginning of line */
2661 line_so_far = 0L; /* clear line count */
2662 #if defined(DOS) || defined(OS2)
2663 /* LF with no CR!! */
2664 if(lastchar != '\r') /* must be non-text data! */
2665 lastchar = BREAKOUT;
2666 #endif
2668 else if(*p & 0x80){
2669 eight_bit_chars++;
2671 else if(!*p){
2672 /* NULL found. Unless we're told otherwise, must be binary */
2673 if(body->type == TYPEOTHER){
2674 body->type = TYPEAPPLICATION;
2675 body->subtype = cpystr("octet-stream");
2679 * The "TYPETEXT" here handles the case that the NULL
2680 * comes from imported text generated by some external
2681 * editor that permits or inserts NULLS. Otherwise,
2682 * assume it's a binary segment...
2684 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2687 * Since we've already set encoding, count this as a
2688 * hi bit char and continue. The reason is that if this
2689 * is text, there may be a high percentage of encoded
2690 * characters, so base64 may get set below...
2692 if(body->type == TYPETEXT)
2693 eight_bit_chars++;
2694 else
2695 break;
2698 #if defined(DOS) || defined(OS2) /* for binary file detection */
2699 if(lastchar != BREAKOUT)
2700 lastchar = *p;
2701 #endif
2703 /* read another buffer in */
2704 if(n == 0){
2705 if(bol)
2706 line_so_far += p - bol;
2708 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2711 len += n;
2712 p = buf;
2714 else
2715 p++;
2718 * If there's no beginning-of-line pointer, then we must
2719 * have seen an end-of-line. Set bol to the start of the
2720 * new line...
2722 if(!bol)
2723 bol = p;
2725 #if defined(DOS) || defined(OS2) /* for binary file detection */
2726 /* either a lone \r or lone \n indicate binary file */
2727 if(lastchar == '\r' || lastchar == BREAKOUT){
2728 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2729 if(body->type == TYPEOTHER){
2730 body->type = TYPEAPPLICATION;
2731 body->subtype = cpystr("octet-stream");
2734 new_encoding = ENCBINARY;
2735 break;
2738 #endif
2742 /* stash away for later */
2743 so_attr(so, "maxline", long2string(max_line));
2745 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2747 * Since the type or encoding aren't set yet, fall thru a
2748 * series of tests to make sure an adequate type and
2749 * encoding are set...
2752 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2753 if(body->type == TYPEOTHER){
2755 * Since the types not set, then we didn't find a NULL.
2756 * If there's no NULL, then this is likely text. However,
2757 * since we can't be *completely* sure, we set it to
2758 * the generic type.
2760 body->type = TYPEAPPLICATION;
2761 body->subtype = cpystr("octet-stream");
2764 if(new_encoding != ENCBINARY)
2766 * As with NULL handling, if we're told it's text,
2767 * qp-encode it, else it gets base 64...
2769 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2772 if(eight_bit_chars == 0L){
2773 if(body->type == TYPEOTHER)
2774 body->type = TYPETEXT;
2776 if(new_encoding == ENCOTHER)
2777 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2779 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2781 * The 30% threshold is based on qp encoded readability
2782 * on non-MIME UA's.
2784 if(body->type == TYPEOTHER)
2785 body->type = TYPETEXT;
2787 if(new_encoding != ENCBINARY)
2788 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2790 else{
2791 if(body->type == TYPEOTHER){
2792 body->type = TYPEAPPLICATION;
2793 body->subtype = cpystr("octet-stream");
2797 * Apply maximal encoding regardless of previous
2798 * setting. This segment's either not text, or is
2799 * unlikely to be readable with > 30% of the
2800 * text encoded anyway, so we might as well save space...
2802 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2806 #ifdef ENCODE_FROMS
2807 /* If there were From_'s at the beginning of a line or standalone dots */
2808 if((froms || dots) && new_encoding != ENCBINARY)
2809 new_encoding = ENC8BIT;
2810 #endif
2812 /* Set the subtype */
2813 if(body->subtype == NULL)
2814 body->subtype = cpystr(rfc822_default_subtype(body->type));
2816 if(body->encoding == ENCOTHER)
2817 body->encoding = new_encoding;
2819 fs_give((void **)&buf);
2821 if(we_cancel)
2822 cancel_busy_cue(-1);
2827 * Call this to set the charset of an attachment we have
2828 * created. If the attachment contains any non-ascii characters
2829 * then we'll set the charset to the passed in charset, otherwise
2830 * we'll make it us-ascii.
2832 void
2833 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2835 unsigned char c;
2836 int can_be_ascii = 1;
2837 STORE_S *so = (STORE_S *)body->contents.text.data;
2838 int we_cancel = 0;
2840 if(!body || body->type != TYPETEXT)
2841 return;
2843 we_cancel = busy_cue(NULL, NULL, 1);
2845 so_seek(so, 0L, 0);
2847 while(can_be_ascii && so_readc(&c, so))
2848 if(!c || c & 0x80)
2849 can_be_ascii--;
2851 if(can_be_ascii)
2852 set_parameter(&body->parameter, "charset", "US-ASCII");
2853 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2854 set_parameter(&body->parameter, "charset", charset);
2855 else{
2857 * Else we don't know. There are non ascii characters but we either
2858 * don't have a charset to set it to or that charset is just us_ascii,
2859 * which is impossible. So we label it unknown. An alternative would
2860 * have been to strip the high bits instead and label it ascii.
2862 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2865 if(we_cancel)
2866 cancel_busy_cue(-1);
2871 * since encoding happens on the way out the door, this is basically
2872 * just needed to handle TYPEMULTIPART
2874 void
2875 pine_encode_body (struct mail_bodystruct *body)
2877 PART *part;
2879 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2880 if (body) switch (body->type) {
2881 char *freethis;
2883 case TYPEMULTIPART: /* multi-part */
2884 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2885 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2887 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2888 (long) getpid ());
2889 tmp[sizeof(tmp)-1] = '\0';
2890 set_parameter(&body->parameter, "BOUNDARY", tmp);
2893 if(freethis)
2894 fs_give((void **) &freethis);
2896 part = body->nested.part; /* encode body parts */
2897 do pine_encode_body (&part->body);
2898 while ((part = part->next) != NULL); /* until done */
2899 break;
2901 case TYPETEXT :
2903 * If the part is text we edited, then it is UTF-8.
2904 * The user may be asking us to send it as something else
2905 * or we may want to downconvert to a more-specific characterset.
2906 * Mark it for conversion here so the right MIME header's written.
2907 * Do conversion pine_rfc822_output_body.
2908 * Attachments are left as is.
2910 if(body->contents.text.data
2911 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2912 char *charset, *posting_charset, *lp;
2914 if(!((charset = parameter_val(body->parameter, "charset"))
2915 && !strucmp(charset, UNKNOWN_CHARSET))
2916 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2918 set_parameter(&body->parameter, "charset", posting_charset);
2921 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2922 * and doesn't use anything but ASCII characters.
2923 * Why is it not ENC7BIT already? Because when we set the encoding
2924 * in set_mime_type_by_grope we were groping through UTF-8 text
2925 * not 2022 text. Not only that, but we didn't know at that point
2926 * that it wouldn't stay UTF-8 when we sent it, which would require
2927 * encoding.
2929 if(!strucmp(posting_charset, "iso-2022-jp")
2930 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2931 && strlen(lp) < 4)
2932 body->encoding = ENC7BIT;
2935 if(charset)
2936 fs_give((void **)&charset);
2939 break;
2941 /* case MESSAGE: */ /* here for documentation */
2942 /* Encapsulated messages are always treated as text objects at this point.
2943 This means that you must replace body->contents.msg with
2944 body->contents.text, which probably involves copying
2945 body->contents.msg.text to body->contents.text */
2946 default: /* all else has some encoding */
2948 * but we'll delay encoding it until the message is on the way
2949 * into the mail slot...
2951 break;
2957 * pine_header_line - simple wrapper around c-client call to contain
2958 * repeated code, and to write fcc if required.
2961 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2962 int writehdr, int localcopy)
2964 int ret = 1;
2965 int big = 10000;
2966 char *value, *folded = NULL, *cs;
2967 char *converted;
2969 if(!text)
2970 return 1;
2972 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2974 if(converted){
2975 if(cs && !strucmp(cs, "us-ascii"))
2976 value = converted;
2977 else
2978 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2979 (unsigned char *) converted, cs,
2980 encode_whole_header(field, header));
2982 if(value && value == converted){ /* no encoding was done, have to fold */
2983 int fold_by, len;
2984 char *actual_field;
2986 len = ((header && header->env && header->env->remail)
2987 ? strlen("ReSent-") : 0) +
2988 (field ? strlen(field) : 0) + 2;
2990 actual_field = (char *)fs_get((len+1) * sizeof(char));
2991 snprintf(actual_field, len+1, "%s%s: ",
2992 (header && header->env && header->env->remail) ? "ReSent-" : "",
2993 field ? field : "");
2994 actual_field[len] = '\0';
2997 * We were folding everything except message-id, but that wasn't
2998 * sufficient. Since 822 only allows folding where linear-white-space
2999 * is allowed we'd need a smarter folder than "fold" to do it. So,
3000 * instead of inventing that smarter folder (which would have to
3001 * know 822 syntax)
3003 * We could just alloc space and copy the actual_field followed by
3004 * the value into it, but since that's what fold does anyway we'll
3005 * waste some cpu time and use fold with a big fold parameter.
3007 * We upped the references folding from 75 to 256 because we were
3008 * encountering longer-than-75 message ids, and to break one line
3009 * in references is to break them all.
3011 if(field && !strucmp("Subject", field))
3012 fold_by = 75;
3013 else if(field && !strucmp("References", field))
3014 fold_by = 256;
3015 else
3016 fold_by = big;
3018 folded = fold(value, fold_by, big, actual_field, " ", FLD_CRLF);
3020 if(actual_field)
3021 fs_give((void **)&actual_field);
3023 else if(value){ /* encoding was done */
3024 RFC822BUFFER rbuf;
3025 size_t ll;
3028 * rfc1522_encode already inserted continuation lines and did
3029 * the necessary folding so we don't have to do it. Let
3030 * rfc822_header_line add the trailing crlf and the resent- if
3031 * necessary. The 20 could actually be a 12.
3033 ll = strlen(field) + strlen(value) + 20;
3034 folded = (char *) fs_get(ll * sizeof(char));
3035 *folded = '\0';
3036 rbuf.f = dummy_soutr;
3037 rbuf.s = NULL;
3038 rbuf.beg = folded;
3039 rbuf.cur = folded;
3040 rbuf.end = folded+ll-1;
3041 rfc822_output_header_line(&rbuf, field,
3042 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3043 *rbuf.cur = '\0';
3046 if(value && folded){
3047 if(writehdr && f)
3048 ret = (*f)(s, folded);
3050 if(ret && localcopy && lmc.so && !lmc.all_written)
3051 ret = so_puts(lmc.so, folded);
3054 if(folded)
3055 fs_give((void **)&folded);
3057 if(converted && converted != text)
3058 fs_give((void **) &converted);
3060 else
3061 ret = 0;
3063 return(ret);
3068 * Do appropriate encoding of text header lines.
3069 * For some field types (those that consist of 822 *text) we just encode
3070 * the whole thing. For structured fields we encode only within comments
3071 * if possible.
3073 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3074 * s -- Source string.
3075 * charset -- Charset to encode with.
3076 * encode_all -- If set, encode the whole string. If not, try to encode
3077 * only within comments if possible.
3079 * Returns S is returned if no encoding is done. D is returned if encoding
3080 * was needed.
3082 char *
3083 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3085 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3086 int in_comment = 0;
3088 if(!s)
3089 return((char *)s);
3091 if(dlen < SIZEOF_20KBUF)
3092 panic("bad call to encode_header_value");
3094 if(!encode_all){
3096 * We don't have to worry about keeping track of quoted-strings because
3097 * none of these fields which aren't addresses contain quoted-strings.
3098 * We do keep track of escaped parens inside of comments and comment
3099 * nesting.
3101 p = d+7000;
3102 for(q = (char *)s; *q; q++){
3103 switch(*q){
3104 case LPAREN:
3105 if(in_comment++ == 0)
3106 start_of_comment = q;
3108 break;
3110 case RPAREN:
3111 if(--in_comment == 0){
3112 /* encode the comment, excluding the outer parens */
3113 if(p-d < dlen-1)
3114 *p++ = LPAREN;
3116 *q = '\0';
3117 r = rfc1522_encode(d+14000, dlen-14000,
3118 (unsigned char *)start_of_comment+1,
3119 charset);
3120 if(r != start_of_comment+1)
3121 value = d+7000; /* some encoding was done */
3123 start_of_comment = NULL;
3124 if(r)
3125 sstrncpy(&p, r, dlen-1-(p-d));
3127 *q = RPAREN;
3128 if(p-d < dlen-1)
3129 *p++ = *q;
3131 else if(in_comment < 0){
3132 in_comment = 0;
3133 if(p-d < dlen-1)
3134 *p++ = *q;
3137 break;
3139 case BSLASH:
3140 if(!in_comment && *(q+1)){
3141 if(p-d < dlen-2){
3142 *p++ = *q++;
3143 *p++ = *q;
3147 break;
3149 default:
3150 if(!in_comment && p-d < dlen-1)
3151 *p++ = *q;
3153 break;
3157 if(value){
3158 /* Unterminated comment (wasn't really a comment) */
3159 if(start_of_comment)
3160 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3162 *p = '\0';
3167 * We have to check if there is anything that needs to be encoded that
3168 * wasn't in a comment. If there is, we'd better just start over and
3169 * encode the whole thing. So, if no encoding has been done within
3170 * comments, or if encoding is needed both within and outside of
3171 * comments, then we encode the whole thing. Otherwise, go with
3172 * the version that has only comments encoded.
3174 if(!value || rfc1522_encode(d, dlen,
3175 (unsigned char *)value, charset) != value)
3176 return(rfc1522_encode(d, dlen, s, charset));
3177 else{
3178 strncpy(d, value, dlen-1);
3179 d[dlen-1] = '\0';
3180 return(d);
3186 * pine_address_line - write a header field containing addresses,
3187 * one by one (so there's no buffer limit), and
3188 * wrapping where necessary.
3189 * Note: we use c-client functions to properly build the text string,
3190 * but have to screw around with pointers to fool c-client functions
3191 * into not blatting all the text into a single buffer. Yeah, I know.
3194 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3195 soutr_t f, void *s, int writehdr, int localcopy)
3197 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3198 size_t alloced = 0, sz;
3199 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3200 char *converted, *cs;
3201 ADDRESS *atmp;
3202 int i, count;
3203 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3204 RFC822BUFFER rbuf;
3205 static char comma[] = ", ";
3206 static char end_group[] = ";";
3207 #define no_comma (&comma[1])
3209 if(!alist) /* nothing in field! */
3210 return(1);
3212 if(!alist->host && alist->mailbox){ /* c-client group convention */
3213 in_group++;
3214 was_start_of_group++;
3215 /* encode mailbox of group */
3216 mtmp = alist->mailbox;
3217 if(mtmp){
3218 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3219 buftmp[sizeof(buftmp)-1] = '\0';
3220 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3221 if(converted){
3222 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3223 (unsigned char *) converted, cs));
3224 if(converted && converted != buftmp)
3225 fs_give((void **) &converted);
3227 else{
3228 failed++;
3229 goto bail_out;
3233 else
3234 mtmp = NULL;
3236 ptmp = alist->personal; /* remember personal name */
3237 /* make sure personal name is encoded */
3238 if(ptmp){
3239 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3240 buftmp[sizeof(buftmp)-1] = '\0';
3241 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3242 if(converted){
3243 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3244 (unsigned char *) converted, cs));
3245 if(converted && converted != buftmp)
3246 fs_give((void **) &converted);
3248 else{
3249 failed++;
3250 goto bail_out;
3254 atmp = alist->next;
3255 alist->next = NULL; /* digest only first address! */
3257 /* use automatic buffer unless it isn't big enough */
3258 if((alloced = est_size(alist)) > sizeof(tmp)){
3259 tmpptr = (char *)fs_get(alloced);
3260 sz = alloced;
3262 else{
3263 tmpptr = tmp;
3264 sz = sizeof(tmp);
3267 rbuf.f = dummy_soutr;
3268 rbuf.s = NULL;
3269 rbuf.beg = tmpptr;
3270 rbuf.cur = tmpptr;
3271 rbuf.end = tmpptr+sz-1;
3272 rfc822_output_address_line(&rbuf, field,
3273 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3274 *rbuf.cur = '\0';
3276 alist->next = atmp; /* restore pointer to next addr */
3278 if(alist->personal && alist->personal != ptmp)
3279 fs_give((void **) &alist->personal);
3281 alist->personal = ptmp; /* in case it changed, restore name */
3283 if(mtmp){
3284 if(alist->mailbox && alist->mailbox != mtmp)
3285 fs_give((void **) &alist->mailbox);
3287 alist->mailbox = mtmp;
3290 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3291 count -= 2;
3292 tmpptr[count] = '\0';
3296 * If there is no sending_stream and we are writing the Lcc header,
3297 * then we are piping it to sendmail -t which expects it to be a bcc,
3298 * not lcc.
3300 * When we write it to the fcc or postponed (the lmc.so),
3301 * we want it to be lcc, not bcc, so we put it back.
3303 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3304 fix_lcc = 1;
3306 if(writehdr && f && *tmpptr){
3307 if(fix_lcc)
3308 tmpptr[0] = 'b';
3310 failed = !(*f)(s, tmpptr);
3311 if(fix_lcc)
3312 tmpptr[0] = 'L';
3314 if(failed)
3315 goto bail_out;
3318 if(localcopy && lmc.so &&
3319 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3320 goto bail_out;
3322 for(alist = atmp; alist; alist = alist->next){
3323 delim = comma;
3324 /* account for c-client's representation of group names */
3325 if(in_group){
3326 if(!alist->host){ /* end of group */
3327 in_group = 0;
3328 was_start_of_group = 0;
3330 * Rfc822_write_address no longer writes out the end of group
3331 * unless the whole group address is passed to it, so we do
3332 * it ourselves.
3334 delim = end_group;
3336 else if(!localcopy || !lmc.so || lmc.all_written)
3337 continue;
3339 /* start of new group, print phrase below */
3340 else if(!alist->host && alist->mailbox){
3341 in_group++;
3342 was_start_of_group++;
3345 /* no comma before first address in group syntax */
3346 if(was_start_of_group && alist->host){
3347 delim = no_comma;
3348 was_start_of_group = 0;
3351 /* write delimiter */
3352 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3353 || (localcopy && lmc.so && !lmc.all_written
3354 && !so_puts(lmc.so, delim)))
3355 goto bail_out;
3357 ptmp = alist->personal; /* remember personal name */
3358 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3359 buftmp[sizeof(buftmp)-1] = '\0';
3360 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3361 if(converted){
3362 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3363 (unsigned char *) converted, cs));
3364 if(converted && converted != buftmp)
3365 fs_give((void **) &converted);
3367 else{
3368 failed++;
3369 goto bail_out;
3372 atmp = alist->next;
3373 alist->next = NULL; /* tie off linked list */
3374 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3375 alloced = i;
3376 sz = alloced;
3377 fs_resize((void **)&tmpptr, alloced);
3380 *tmpptr = '\0';
3381 /* make sure we don't write out group end with rfc822_write_address */
3382 if(alist->host || alist->mailbox){
3383 rbuf.f = dummy_soutr;
3384 rbuf.s = NULL;
3385 rbuf.beg = tmpptr;
3386 rbuf.cur = tmpptr;
3387 rbuf.end = tmpptr+sz-1;
3388 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3389 *rbuf.cur = '\0';
3392 alist->next = atmp; /* restore next pointer */
3394 if(alist->personal && alist->personal != ptmp)
3395 fs_give((void **) &alist->personal);
3397 alist->personal = ptmp; /* in case it changed, restore name */
3400 * BUG
3401 * With group syntax addresses we no longer have two identical
3402 * streams of output. Instead, for the fcc/postpone copy we include
3403 * all of the addresses inside the :; of the group, and for the
3404 * mail we're sending we don't include them. That means we aren't
3405 * correctly keeping track of the column to wrap in, below. That is,
3406 * we are keeping track of the fcc copy but we aren't keeping track
3407 * of the regular copy. It could result in too long or too short
3408 * lines. Should almost never come up since group addresses are almost
3409 * never followed by other addresses in the same header, and even
3410 * when they are, you have to go out of your way to get the headers
3411 * messed up.
3413 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3414 count = i + 4;
3415 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3416 || (localcopy && lmc.so && !lmc.all_written &&
3417 !so_puts(lmc.so, "\015\012 ")))
3418 goto bail_out;
3420 else
3421 count += i + 2;
3423 if(((!in_group || was_start_of_group)
3424 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3425 || (localcopy && lmc.so && !lmc.all_written
3426 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3427 goto bail_out;
3430 bail_out:
3431 if(tmpptr && tmpptr != tmp)
3432 fs_give((void **)&tmpptr);
3434 if(failed)
3435 return(0);
3437 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3438 && ((localcopy && lmc.so
3439 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3444 * mutated pine version of c-client's rfc822_header() function.
3445 * changed to call pine-wrapped header and address functions
3446 * so we don't have to limit the header size to a fixed buffer.
3447 * This function also calls pine's body_header write function
3448 * because encoding is delayed until output_body() is called.
3450 long
3451 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3453 PINEFIELD *pf;
3454 int j;
3456 if(header->env->remail){ /* if remailing */
3457 long i = strlen (header->env->remail);
3458 if(i > 4 && header->env->remail[i-4] == '\015')
3459 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3461 if((f && !(*f)(s, header->env->remail))
3462 || (lmc.so && !lmc.all_written
3463 && !so_puts(lmc.so, header->env->remail)))
3464 return(0L); /* start with remail header */
3467 j = 0;
3468 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3469 switch(pf->type){
3471 * Warning: This is confusing. The 2nd to last argument used to
3472 * be just pf->writehdr. We want Bcc lines to be written out
3473 * if we are handing off to a sendmail temp file but not if we
3474 * are talking smtp, so bcc's writehdr is set to 0 and
3475 * pine_address_line was sending if writehdr OR !sending_stream.
3476 * That works as long as we want to write everything when
3477 * !sending_stream (an mta handoff to sendmail). But then we
3478 * added the undisclosed recipients line which should only get
3479 * written if writehdr is set, and not when we pass to a
3480 * sendmail temp file. So pine_address_line has been changed
3481 * so it bases its decision solely on the writehdr passed to it,
3482 * and the logic that worries about Bcc and sending_stream
3483 * was moved up to the caller (here) to decide when to set it.
3485 * So we have:
3486 * undisclosed recipients:; This will just be written
3487 * if writehdr was set and not
3488 * otherwise, nothing magical.
3489 *** We may want to change this, because sendmail -t doesn't handle
3490 *** the empty group syntax well unless it has been configured to
3491 *** do so. It isn't configured by default, or in any of the
3492 *** sendmail v8 configs. So we may want to not write this line
3493 *** if we're doing an mta_handoff (!sending_stream).
3495 * !sending_stream (which means a handoff to a sendmail -t)
3496 * bcc or lcc both set the arg so they'll get written
3497 * (There is also Lcc hocus pocus in pine_address_line
3498 * which converts the Lcc: to Bcc: for sendmail
3499 * processing.)
3500 * sending_stream (which means an smtp handoff)
3501 * bcc and lcc will never have writehdr set, so
3502 * will never be written (They both do have rcptto set,
3503 * so they both do cause RCPT TO commands.)
3505 * The localcopy is independent of sending_stream and is just
3506 * written if it is set for all of these.
3508 case Address:
3509 if(!pine_address_line(pf->name,
3510 header,
3511 pf->addr ? *pf->addr : NULL,
3514 (!strucmp("bcc",pf->name ? pf->name : "")
3515 || !strucmp("Lcc",pf->name ? pf->name : ""))
3516 ? !sending_stream
3517 : pf->writehdr,
3518 pf->localcopy))
3519 return(0L);
3521 break;
3523 case Fcc:
3524 case FreeText:
3525 case Subject:
3526 if(!pine_header_line(pf->name, header,
3527 pf->text ? *pf->text : NULL,
3528 f, s, pf->writehdr, pf->localcopy))
3529 return(0L);
3531 break;
3533 default:
3534 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3535 pf->name);
3536 break;
3541 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3543 * Add comforting "X-" header line indicating what sort of
3544 * authenticity the receiver can expect...
3546 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3547 NETMBX netmbox;
3548 char sstring[MAILTMPLEN], *label; /* place to write */
3549 MAILSTREAM *m;
3550 int i, anonymous = 1;
3552 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3553 m = ps_global->s_pool.streams[i];
3554 if(m && sp_flagged(m, SP_LOCKED)
3555 && mail_valid_net_parse(m->mailbox, &netmbox)
3556 && !netmbox.anoflag)
3557 anonymous = 0;
3560 if(!anonymous){
3561 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3562 *user = (*netmbox.user)
3563 ? netmbox.user
3564 : cached_user_name(netmbox.mailbox);
3565 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3566 isdigit((unsigned char)last_char) ? "[" : "",
3567 netmbox.host,
3568 isdigit((unsigned char) last_char) ? "]" : "");
3569 sstring[sizeof(sstring)-1] = '\0';
3570 label = "X-X-Sender"; /* Jeez. */
3571 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3572 label += 4;
3574 else{
3575 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3576 sstring[sizeof(sstring)-1] = '\0';
3577 label = "X-Warning";
3580 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3581 return(0L);
3583 #endif
3585 if(body && !header->env->remail){ /* not if remail or no body */
3586 if((f && !(*f)(s, MIME_VER))
3587 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3588 || !pine_write_body_header(body, f, s))
3589 return(0L);
3591 else{ /* write terminating newline */
3592 if((f && !(*f)(s, "\015\012"))
3593 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3594 return(0L);
3597 return(1L);
3602 * pine_rfc822_output - pine's version of c-client call. Necessary here
3603 * since we're not using its structures as intended!
3605 long
3606 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3608 int we_cancel = 0;
3609 long retval;
3611 dprint((4, "-- pine_rfc822_output\n"));
3613 we_cancel = busy_cue(NULL, NULL, 1);
3614 pine_encode_body(body); /* encode body as necessary */
3615 /* build and output RFC822 header, output body */
3616 retval = pine_rfc822_header(header, body, f, s)
3617 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3619 if(we_cancel)
3620 cancel_busy_cue(-1);
3622 return(retval);
3627 * post_rfc822_output - cloak for pine's 822 output routine. Since
3628 * we can't pass opaque envelope thru c-client posting
3629 * logic, we need to wrap the real output inside
3630 * something that c-client knows how to call.
3632 long
3633 post_rfc822_output(char *tmp,
3634 ENVELOPE *env,
3635 struct mail_bodystruct *body,
3636 soutr_t f,
3637 void *s,
3638 long int ok8bit)
3640 return(pine_rfc822_output(send_header, body, f, s));
3645 * posting_characterset- determine what transliteration is reasonable
3646 * for posting the given non-ascii messsage data.
3648 * preferred_charset is the charset the original data was labeled in.
3649 * If we can keep that we do.
3651 * Returns: always returns the preferred character set.
3653 char *
3654 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3656 unsigned long *charsetmap = NULL;
3657 unsigned long validbitmap;
3658 static char *ascii = "US-ASCII";
3659 static char *utf8 = "UTF-8";
3660 int notcjk = 0;
3662 if(!ps_global->post_utf8){
3663 validbitmap = 0;
3665 if(mp == HdrText){
3666 char *text = NULL;
3667 UCS *ucs = NULL, *ucsp;
3669 text = (char *) data;
3671 /* convert text in header to UCS characters */
3672 if(text)
3673 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3675 if(!(ucs && *ucs))
3676 return(ascii);
3679 * After the while loop is done the validbitmap has
3680 * a 1 bit for all the character sets that can
3681 * represent all of the characters of this header.
3683 charsetmap = init_charsetchecker(preferred_charset);
3685 if(!charsetmap)
3686 return(utf8);
3688 validbitmap = ~0;
3689 while((validbitmap & ~0x1) && (*ucsp)){
3690 if(*ucsp > 0xffff){
3691 fs_give((void **) &ucs);
3692 return(utf8);
3695 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3698 fs_give((void **) &ucs);
3700 notcjk = validbitmap & 0x1;
3701 validbitmap &= ~0x1;
3703 if(!validbitmap)
3704 return(utf8);
3706 else{
3707 struct mail_bodystruct *body = NULL;
3708 STORE_S *the_text = NULL;
3709 int outchars;
3710 unsigned char c;
3711 UCS ucs;
3712 CBUF_S cbuf;
3714 cbuf.cbuf[0] = '\0';
3715 cbuf.cbufp = cbuf.cbuf;
3716 cbuf.cbufend = cbuf.cbuf;
3718 body = (struct mail_bodystruct *) data;
3720 if(body && body->type == TYPEMULTIPART)
3721 body = &body->nested.part->body;
3723 if(body && body->type == TYPETEXT)
3724 the_text = (STORE_S *) body->contents.text.data;
3726 if(!the_text)
3727 return(ascii);
3729 so_seek(the_text, 0L, 0); /* rewind */
3731 charsetmap = init_charsetchecker(preferred_charset);
3733 if(!charsetmap)
3734 return(utf8);
3736 validbitmap = ~0;
3739 * Read a stream of UTF-8 characters from the_text
3740 * and convert them to UCS-4 characters for the translatable
3741 * test.
3743 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3744 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3745 /* got a ucs character */
3746 if(ucs > 0xffff)
3747 return(utf8);
3749 validbitmap &= charsetmap[(unsigned long) ucs];
3753 notcjk = validbitmap & 0x1;
3754 validbitmap &= ~0x1;
3756 if(!validbitmap)
3757 return(utf8);
3760 /* user chooses something other than UTF-8 */
3761 if(strucmp(ps_global->posting_charmap, utf8)){
3763 * If we're to post in other than UTF-8, and it can be
3764 * transliterated without losing fidelity, do it.
3765 * Else, use UTF-8.
3768 /* if ascii works, always use that */
3769 if(representable_in_charset(validbitmap, ascii))
3770 return(ascii);
3772 /* does the user's posting character set work? */
3773 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3774 return(ps_global->posting_charmap);
3776 /* this is the charset the message we are replying to was in */
3777 if(preferred_charset
3778 && strucmp(preferred_charset, ascii)
3779 && representable_in_charset(validbitmap, preferred_charset))
3780 return(preferred_charset);
3782 /* else, use UTF-8 */
3785 /* user chooses nothing, going with the default */
3786 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3787 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3788 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3789 char *most_preferred;
3792 * In this case the user didn't specify a posting character set
3793 * and we will choose the most-specific one from our list.
3796 /* ascii is best */
3797 if(representable_in_charset(validbitmap, ascii))
3798 return(ascii);
3800 /* Can we keep the original from the message we're replying to? */
3801 if(preferred_charset
3802 && strucmp(preferred_charset, ascii)
3803 && representable_in_charset(validbitmap, preferred_charset))
3804 return(preferred_charset);
3806 /* choose the best of the rest */
3807 most_preferred = most_preferred_charset(validbitmap);
3808 if(!most_preferred)
3809 return(utf8);
3812 * If the text we're labeling contains something like
3813 * smart quotes but no CJK characters, then instead of
3814 * labeling it as ISO-2022-JP we want to use UTF-8.
3816 if(notcjk){
3817 const CHARSET *cs;
3819 cs = utf8_charset(most_preferred);
3820 if(!cs
3821 || cs->script == SC_CHINESE_SIMPLIFIED
3822 || cs->script == SC_CHINESE_TRADITIONAL
3823 || cs->script == SC_JAPANESE
3824 || cs->script == SC_KOREAN)
3825 return(utf8);
3828 return(most_preferred);
3830 /* user explicitly chooses UTF-8 */
3831 else{
3832 /* if ascii works, always use that */
3833 if(representable_in_charset(validbitmap, ascii))
3834 return(ascii);
3836 /* else, use UTF-8 */
3841 return(utf8);
3845 static char **charsetlist = NULL;
3846 static int items_in_charsetlist = 0;
3847 static unsigned long *charsetmap = NULL;
3849 static char *downgrades[] = {
3850 "US-ASCII",
3851 "ISO-8859-15",
3852 "ISO-8859-1",
3853 "ISO-8859-2",
3854 "VISCII",
3855 "KOI8-R",
3856 "KOI8-U",
3857 "ISO-8859-7",
3858 "ISO-8859-6",
3859 "ISO-8859-8",
3860 "TIS-620",
3861 "ISO-2022-JP",
3862 "GB2312",
3863 "BIG5",
3864 "EUC-KR"
3868 unsigned long *
3869 init_charsetchecker(char *preferred_charset)
3871 int i, count = 0, reset = 0;
3872 char *ascii = "US-ASCII";
3873 char *utf8 = "UTF-8";
3876 * When user doesn't set a posting character set posting_charmap ends up
3877 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3878 * That's where the strange set of if-else's come from.
3881 /* user chooses something other than UTF-8 */
3882 if(strucmp(ps_global->posting_charmap, utf8)){
3883 count++; /* US-ASCII */
3884 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3885 reset++;
3887 /* if posting_charmap is valid, include it in list */
3888 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3889 && strucmp(ps_global->posting_charmap, ascii)
3890 && strucmp(ps_global->posting_charmap, utf8)
3891 && utf8_charset(ps_global->posting_charmap)){
3892 count++;
3893 if(!reset
3894 && (items_in_charsetlist < count
3895 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3896 reset++;
3899 if(preferred_charset && preferred_charset[0]
3900 && strucmp(preferred_charset, ascii)
3901 && strucmp(preferred_charset, utf8)
3902 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3903 count++;
3904 if(!reset
3905 && (items_in_charsetlist < count
3906 || strucmp(charsetlist[count-1], preferred_charset)))
3907 reset++;
3910 if(items_in_charsetlist != count)
3911 reset++;
3913 if(reset){
3914 if(charsetlist)
3915 free_list_array(&charsetlist);
3917 items_in_charsetlist = count;
3918 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3920 i = 0;
3921 charsetlist[i++] = cpystr(ascii);
3923 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3924 && strucmp(ps_global->posting_charmap, ascii)
3925 && strucmp(ps_global->posting_charmap, utf8)
3926 && utf8_charset(ps_global->posting_charmap))
3927 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3929 if(preferred_charset && preferred_charset[0]
3930 && strucmp(preferred_charset, ascii)
3931 && strucmp(preferred_charset, utf8)
3932 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3933 charsetlist[i++] = cpystr(preferred_charset);
3935 charsetlist[i] = NULL;
3938 /* user chooses nothing, going with the default */
3939 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3940 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3941 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3942 int add_preferred = 0;
3944 /* does preferred_charset have to be added to the list? */
3945 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3946 add_preferred = 1;
3947 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3948 if(!strucmp(downgrades[i], preferred_charset))
3949 add_preferred = 0;
3952 if(add_preferred){
3953 /* existing list is right size already */
3954 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3955 /* just check to see if last list item is the preferred_charset */
3956 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3957 /* no, fix it */
3958 reset++;
3959 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3960 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3963 else{
3964 reset++;
3965 if(charsetlist)
3966 free_list_array(&charsetlist);
3968 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3969 items_in_charsetlist = count;
3970 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3971 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3972 charsetlist[i] = cpystr(downgrades[i]);
3974 charsetlist[i++] = cpystr(preferred_charset);
3975 charsetlist[i] = NULL;
3978 else{
3979 /* if list is same size as downgrades, consider it good */
3980 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
3981 reset++;
3983 if(reset){
3984 if(charsetlist)
3985 free_list_array(&charsetlist);
3987 count = sizeof(downgrades)/sizeof(downgrades[0]);
3988 items_in_charsetlist = count;
3989 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3990 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3991 charsetlist[i] = cpystr(downgrades[i]);
3993 charsetlist[i] = NULL;
3997 /* user explicitly chooses UTF-8 */
3998 else{
3999 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4000 count++; /* US-ASCII */
4001 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4002 reset++;
4004 if(items_in_charsetlist != count)
4005 reset++;
4007 if(reset){
4008 if(charsetlist)
4009 free_list_array(&charsetlist);
4011 /* the list is just ascii and nothing else */
4012 items_in_charsetlist = count;
4013 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4015 i = 0;
4016 charsetlist[i++] = cpystr(ascii);
4017 charsetlist[i] = NULL;
4022 if(reset){
4023 if(charsetmap)
4024 fs_give((void **) &charsetmap);
4026 if(charsetlist)
4027 charsetmap = utf8_csvalidmap(charsetlist);
4030 return(charsetmap);
4034 /* total reset */
4035 void
4036 free_charsetchecker(void)
4038 if(charsetlist)
4039 free_list_array(&charsetlist);
4041 items_in_charsetlist = 0;
4043 if(charsetmap)
4044 fs_give((void **) &charsetmap);
4049 representable_in_charset(unsigned long validbitmap, char *charset)
4051 int i, done = 0, ret = 0;
4052 unsigned long j;
4054 if(!(charset && charset[0]))
4055 return ret;
4057 if(!strucmp(charset, "UTF-8"))
4058 return 1;
4060 for(i = 0; !done && i < items_in_charsetlist; i++){
4061 if(!strucmp(charset, charsetlist[i])){
4062 j = 1;
4063 j <<= (i+1);
4064 done++;
4065 if(validbitmap & j)
4066 ret = 1;
4070 return ret;
4074 char *
4075 most_preferred_charset(unsigned long validbitmap)
4077 unsigned long bm;
4078 unsigned long rm;
4079 int index;
4081 if(!(validbitmap && items_in_charsetlist > 0))
4082 return("UTF-8");
4084 /* careful, find_rightmost_bit modifies the bitmap */
4085 bm = validbitmap;
4086 rm = find_rightmost_bit(&bm);
4087 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4089 return(charsetlist[index]);
4094 * Set parameter to new value.
4096 void
4097 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4099 PARAMETER *pm;
4101 if(!param || !(paramname && *paramname))
4102 return;
4104 if(*param == NULL){
4105 pm = (*param) = mail_newbody_parameter();
4106 pm->attribute = cpystr(paramname);
4108 else{
4109 int nomatch;
4111 for(pm = *param;
4112 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4113 pm = pm->next)
4114 ;/* searching for paramname parameter */
4116 if(nomatch){ /* add charset parameter */
4117 pm->next = mail_newbody_parameter();
4118 pm = pm->next;
4119 pm->attribute = cpystr(paramname);
4121 /* else pm is existing paramname parameter */
4124 if(pm){
4125 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4126 if(pm->value)
4127 fs_give((void **) &pm->value);
4129 if(new_value)
4130 pm->value = cpystr(new_value);
4136 /*----------------------------------------------------------------------
4137 Remove the leading digits from SMTP error messages
4138 -----*/
4139 char *
4140 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4142 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4143 (*error == '.' && isdigit((unsigned char)*(error+1))))
4144 error++;
4146 snprintf(outbuf, outbuflen, printstring, error);
4147 outbuf[outbuflen-1] = '\0';
4148 return(outbuf);
4153 * Local globals pine's body output routine needs
4155 static soutr_t l_f;
4156 static TCPSTREAM *l_stream;
4157 static unsigned c_in_buf = 0;
4160 * def to make our pipe write's more friendly
4162 #ifdef PIPE_MAX
4163 #if PIPE_MAX > 20000
4164 #undef PIPE_MAX
4165 #endif
4166 #endif
4168 #ifndef PIPE_MAX
4169 #define PIPE_MAX 1024
4170 #endif
4174 * l_flust_net - empties gf_io terminal function's buffer
4177 l_flush_net(int force)
4179 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4180 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4182 tmp_20k_buf[c_in_buf] = '\0';
4183 if(!force){
4185 * The start of each write is expected to be the start of a
4186 * "record" (i.e., a CRLF terminated line). Make sure that is true
4187 * else we might screw up SMTP dot quoting...
4189 for(p = tmp_20k_buf, lp = NULL;
4190 (p = strstr(p, "\015\012")) != NULL;
4191 lp = (p += 2))
4195 if(!lp && c_in_buf > 2) /* no CRLF! */
4196 for(p = &tmp_20k_buf[c_in_buf] - 2;
4197 p > &tmp_20k_buf[0] && *p == '.';
4198 p--) /* find last non-dot */
4201 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4202 /* snippet remains */
4203 c = *lp;
4204 *lp = '\0';
4208 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4209 || (lmc.so && !lmc.all_written
4210 && !(lmc.text_only && lmc.text_written)
4211 && !so_puts(lmc.so, tmp_20k_buf)))
4212 return(0);
4214 c_in_buf = 0;
4215 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4216 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4217 c_in_buf++, lp++;
4220 return(1);
4225 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4229 l_putc(int c)
4231 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4232 tmp_20k_buf[c_in_buf++] = (char) c;
4234 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4240 * pine_rfc822_output_body - pine's version of c-client call. Again,
4241 * necessary since c-client doesn't know about how
4242 * we're treating attachments
4244 long
4245 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4247 PART *part;
4248 PARAMETER *param;
4249 char *t, *cookie = NIL, *encode_error;
4250 char tmp[MAILTMPLEN];
4251 int add_trailing_crlf;
4252 LOC_2022_JP ljp;
4253 gf_io_t gc;
4255 dprint((4, "-- pine_rfc822_output_body: %d\n",
4256 body ? body->type : 0));
4257 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4258 part = body->nested.part; /* first body part */
4259 /* find cookie */
4260 for (param = body->parameter; param && !cookie; param = param->next)
4261 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4262 if (!cookie) cookie = "-"; /* yucky default */
4265 * Output a bit of text before the first multipart delimiter
4266 * to warn unsuspecting users of non-mime-aware ua's that
4267 * they should expect weirdness. We do not add this when signing a
4268 * message, though...
4270 #ifdef SMIME
4271 if(ps_global->smime && !ps_global->smime->do_sign)
4272 #endif
4273 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"))
4274 return(0);
4276 do { /* for each part */
4277 /* build cookie */
4278 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4279 tmp[sizeof(tmp)-1] = '\0';
4280 /* append cookie,mini-hdr,contents */
4281 if((f && !(*f)(s, tmp))
4282 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4283 || !pine_write_body_header(&part->body,f,s)
4284 || !pine_rfc822_output_body (&part->body,f,s))
4285 return(0);
4286 } while ((part = part->next) != NULL); /* until done */
4287 /* output trailing cookie */
4288 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4289 tmp[sizeof(tmp)-1] = '\0';
4290 if(lmc.so && !lmc.all_written){
4291 so_puts(lmc.so, t);
4292 so_puts(lmc.so, "\015\012");
4295 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4298 l_f = f; /* set up for writing chars... */
4299 l_stream = s; /* out other end of pipe... */
4300 gf_filter_init();
4301 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4302 body->size.bytes));
4304 if(body->contents.text.data)
4305 gf_set_so_readc(&gc, (STORE_S *) body->contents.text.data);
4306 else
4307 return(1);
4310 * Don't add trailing line if it is ExternalText, which already guarantees
4311 * a trailing newline.
4313 add_trailing_crlf = !(((STORE_S *) body->contents.text.data)->src == ExternalText);
4315 so_seek((STORE_S *) body->contents.text.data, 0L, 0);
4317 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4318 char *charset;
4320 if(body->type == TYPETEXT
4321 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)
4322 && (charset = parameter_val(body->parameter, "charset"))){
4323 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4324 if(!strucmp(charset, "iso-2022-jp")){
4325 ljp.report_err = 0;
4326 gf_link_filter(gf_line_test,
4327 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4329 else{
4330 void *table = utf8_rmap(charset);
4332 if(table){
4333 gf_link_filter(gf_convert_utf8_charset,
4334 gf_convert_utf8_charset_opt(table,0));
4336 else{
4337 /* else, just send it? */
4338 set_parameter(&body->parameter, "charset", "UTF-8");
4343 fs_give((void **)&charset);
4347 * Convert text pieces to canonical form
4348 * BEFORE applying any encoding (rfc1341: appendix G)...
4349 * NOTE: almost all filters expect CRLF newlines
4351 if(body->type == TYPETEXT
4352 && body->encoding != ENCBASE64
4353 && !so_attr((STORE_S *) body->contents.text.data, "rawbody", NULL)){
4354 gf_link_filter(gf_local_nvtnl, NULL);
4357 switch (body->encoding) { /* all else needs filtering */
4358 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4359 gf_link_filter(gf_8bit_qp, NULL);
4360 break;
4362 case ENCBINARY: /* encode binary into BASE64 */
4363 gf_link_filter(gf_binary_b64, NULL);
4364 break;
4366 default: /* otherwise text */
4367 break;
4371 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4372 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4373 _("Encoding Error \"%s\""), encode_error);
4374 display_message('x');
4377 gf_clear_so_readc((STORE_S *) body->contents.text.data);
4379 if(encode_error || !l_flush_net(TRUE))
4380 return(0);
4382 send_bytes_sent += gf_bytes_piped();
4383 so_release((STORE_S *)body->contents.text.data);
4385 if(lmc.so && !lmc.all_written && lmc.text_only){
4386 if(lmc.text_written){ /* we have some splainin' to do */
4387 char tmp[MAILTMPLEN];
4388 char *name = NULL;
4390 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4391 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4392 return(0);
4395 * BUG: If this name is not ascii it's going to cause trouble.
4397 name = parameter_val(body->parameter, "name");
4398 snprintf(tmp, sizeof(tmp),
4399 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4400 body_type_names(body->type),
4401 body->subtype ? body->subtype : "Unknown",
4402 name ? " (Name=\"" : "",
4403 name ? name : "",
4404 name ? "\")" : "",
4405 comatose(body->size.bytes));
4406 tmp[sizeof(tmp)-1] = '\0';
4407 if(name)
4408 fs_give((void **)&name);
4410 if(!so_puts(lmc.so, tmp))
4411 return(0);
4413 else /* suppress everything after first text part */
4414 lmc.text_written = (body->type == TYPETEXT
4415 && (!body->subtype
4416 || !strucmp(body->subtype, "plain")));
4419 if(add_trailing_crlf)
4420 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4421 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4422 else
4423 return(1);
4426 char *
4427 ToLower(char *s, char *t)
4429 int i;
4431 for(i = 0; s != NULL && s[i] != '\0'; i++)
4432 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4433 t[i] = '\0';
4435 return t;
4439 * pine_write_body_header - another c-client clone. This time
4440 * so the final encoding labels get set
4441 * correctly since it hasn't happened yet,
4442 * and to be paranoid about line lengths.
4444 * Returns: TRUE/nonzero on success, zero on error
4447 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4449 char tmp[MAILTMPLEN];
4450 RFC822BUFFER rbuf;
4451 int i;
4452 unsigned char c;
4453 STRINGLIST *stl;
4454 STORE_S *so;
4455 extern const char *tspecials;
4457 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4458 if(!(so_puts(so, "Content-Type: ")
4459 && so_puts(so, ToLower(body_types[body->type], tmp))
4460 && so_puts(so, "/")
4461 && so_puts(so, ToLower(body->subtype
4462 ? body->subtype
4463 : rfc822_default_subtype (body->type),tmp))))
4464 return(pwbh_finish(0, so));
4466 if(body->parameter){
4467 if(!pine_write_params(body->parameter, so))
4468 return(pwbh_finish(0, so));
4470 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4471 return(pwbh_finish(0, so));
4473 if(!so_puts(so, "\015\012"))
4474 return(pwbh_finish(0, so));
4476 if ((body->encoding /* note: encoding 7BIT never output! */
4477 && !(so_puts(so, "Content-Transfer-Encoding: ")
4478 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4479 ? ENCBASE64
4480 : (body->encoding == ENC8BIT)
4481 ? ENCQUOTEDPRINTABLE
4482 : (body->encoding <= ENCMAX)
4483 ? body->encoding
4484 : ENCOTHER])
4485 && so_puts(so, "\015\012")))
4487 * If requested, strip Content-ID headers that don't look like they
4488 * are needed. Microsoft's Outlook XP has a bug that causes it to
4489 * not show that there is an attachment when there is a Content-ID
4490 * header present on that attachment.
4492 * If user has quell-content-id turned on, don't output content-id
4493 * unless it is of type message/external-body.
4494 * Since this code doesn't look inside messages being forwarded
4495 * type message content-ids will remain as is and type multipart
4496 * alternative will remain as is. We don't create those on our
4497 * own. If we did, we'd have to worry about getting this right.
4499 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4500 || (body->type == TYPEMESSAGE
4501 && body->subtype
4502 && !strucmp(body->subtype, "external-body")))
4503 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4504 && so_puts(so, "\015\012")))
4505 || (body->description
4506 && strlen(body->description) < 5000 /* arbitrary! */
4507 && !pine_write_header_line("Content-Description: ", body->description, so))
4508 || (body->md5
4509 && !(so_puts(so, "Content-MD5: ")
4510 && so_puts(so, body->md5)
4511 && so_puts(so, "\015\012"))))
4512 return(pwbh_finish(0, so));
4514 if ((stl = body->language) != NULL) {
4515 if(!so_puts(so, "Content-Language: "))
4516 return(pwbh_finish(0, so));
4518 do {
4519 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4520 return(pwbh_finish(0, so));
4522 tmp[0] = '\0';
4523 rbuf.f = dummy_soutr;
4524 rbuf.s = NULL;
4525 rbuf.beg = tmp;
4526 rbuf.cur = tmp;
4527 rbuf.end = tmp+sizeof(tmp)-1;
4528 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4529 *rbuf.cur = '\0';
4531 if(!so_puts(so, tmp)
4532 || ((stl = stl->next) && !so_puts(so, ", ")))
4533 return(pwbh_finish(0, so));
4535 while (stl);
4537 if(!so_puts(so, "\015\012"))
4538 return(pwbh_finish(0, so));
4541 if (body->disposition.type) {
4542 if(!(so_puts(so, "Content-Disposition: ")
4543 && so_puts(so, body->disposition.type)))
4544 return(pwbh_finish(0, so));
4546 if(!pine_write_params(body->disposition.parameter, so))
4547 return(pwbh_finish(0, so));
4549 if(!so_puts(so, "\015\012"))
4550 return(pwbh_finish(0, so));
4553 /* copy out of so, a line at a time (or less than a K)
4554 * and send it down the pike
4556 so_seek(so, 0L, 0);
4557 i = 0;
4558 while(so_readc(&c, so))
4559 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4560 tmp[i] = '\0';
4561 if((f && !(*f)(s, tmp))
4562 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4563 return(pwbh_finish(0, so));
4565 i = 0;
4568 /* Finally, write blank line */
4569 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4570 return(pwbh_finish(0, so));
4572 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4575 return(0);
4580 * pine_write_header - convert, encode (if needed) and
4581 * write "header-name: field-body"
4584 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4586 char *cv, *cs, *vp;
4587 int rv;
4589 cs = posting_characterset(val, NULL, HdrText);
4590 cv = utf8_to_charset(val, cs, 0);
4591 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4592 (unsigned char *) cv, cs);
4594 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4596 if(cv && cv != val)
4597 fs_give((void **) &cv);
4600 return(rv);
4605 * pine_write_param - convert, encode and write MIME header-field parameters
4608 pine_write_params(PARAMETER *param, STORE_S *so)
4610 for(; param; param = param->next){
4611 int rv;
4612 char *cv, *cs;
4613 extern const char *tspecials;
4615 cs = posting_characterset(param->value, NULL, HdrText);
4616 cv = utf8_to_charset(param->value, cs, 0);
4617 rv = (so_puts(so, "; ")
4618 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4620 if(cv && cv != param->value)
4621 fs_give((void **) &cv);
4623 if(!rv)
4624 return(0);
4627 return(1);
4633 lmc_body_header_line(char *line, int beginning)
4635 if(lmc.so && !lmc.all_written){
4636 if(beginning && lmc.text_only && lmc.text_written
4637 && (!struncmp(line, "content-type:", 13)
4638 || !struncmp(line, "content-transfer-encoding:", 26)
4639 || !struncmp(line, "content-disposition:", 20))){
4641 * "comment out" the real values since our comment isn't
4642 * likely the same type, disposition nor encoding...
4644 if(!so_puts(lmc.so, "X-"))
4645 return(FALSE);
4648 return(so_puts(lmc.so, line));
4651 return(TRUE);
4656 lmc_body_header_finish(void)
4658 if(lmc.so && !lmc.all_written){
4659 if(lmc.text_only && lmc.text_written
4660 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4661 return(FALSE);
4663 return(so_puts(lmc.so, "\015\012"));
4666 return(TRUE);
4672 pwbh_finish(int rv, STORE_S *so)
4674 if(so)
4675 so_give(&so);
4677 return(rv);
4682 * pine_free_body - c-client call wrapper so the body data pointer we
4683 * we're using in a way c-client doesn't know about
4684 * gets free'd appropriately.
4686 void
4687 pine_free_body(struct mail_bodystruct **body)
4690 * Preempt c-client's contents.text.data clean up since we've
4691 * usurped it's meaning for our own purposes...
4693 pine_free_body_data (*body);
4695 /* Then let c-client handle the rest... */
4696 mail_free_body(body);
4701 * pine_free_body_data - free pine's interpretations of the body part's
4702 * data pointer.
4704 void
4705 pine_free_body_data(struct mail_bodystruct *body)
4707 if(body){
4708 if(body->type == TYPEMULTIPART){
4709 PART *part = body->nested.part;
4710 do /* for each part */
4711 pine_free_body_data(&part->body);
4712 while ((part = part->next) != NULL); /* until done */
4714 else if(body->contents.text.data)
4715 so_give((STORE_S **) &body->contents.text.data);
4720 long
4721 send_body_size(struct mail_bodystruct *body)
4723 long l = 0L;
4724 PART *part;
4726 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4727 part = body->nested.part; /* first body part */
4728 do /* for each part */
4729 l += send_body_size(&part->body);
4730 while ((part = part->next) != NULL); /* until done */
4731 return(l);
4734 return(l + body->size.bytes);
4739 sent_percent(void)
4741 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4742 / send_bytes_to_send);
4743 return(MIN(i, 100));
4748 * pine_smtp_verbose_out - write
4750 void
4751 pine_smtp_verbose_out(char *s)
4753 #ifdef _WINDOWS
4754 LPTSTR slpt;
4755 #endif
4756 if(verbose_send_output && s){
4757 char *p, last = '\0';
4759 for(p = s; *p; p++)
4760 if(*p == '\015')
4761 *p = ' ';
4762 else
4763 last = *p;
4765 #ifdef _WINDOWS
4767 * The stream is opened in Unicode mode, so we need to fix the
4768 * argument to fputs.
4770 slpt = utf8_to_lptstr((LPSTR) s);
4771 if(slpt){
4772 _fputts(slpt, verbose_send_output);
4773 fs_give((void **) &slpt);
4776 if(last != '\012')
4777 _fputtc(L'\n', verbose_send_output);
4778 #else
4779 fputs(s, verbose_send_output);
4780 if(last != '\012')
4781 fputc('\n', verbose_send_output);
4782 #endif
4789 * pine_header_forbidden - is this name a "forbidden" header?
4791 * name - the header name to check
4792 * We don't allow user to change these.
4795 pine_header_forbidden(char *name)
4797 char **p;
4798 static char *forbidden_headers[] = {
4799 "sender",
4800 "x-sender",
4801 "x-x-sender",
4802 "date",
4803 "received",
4804 "message-id",
4805 "in-reply-to",
4806 "path",
4807 "resent-message-id",
4808 "resent-date",
4809 "resent-from",
4810 "resent-sender",
4811 "resent-to",
4812 "resent-cc",
4813 "resent-reply-to",
4814 "mime-version",
4815 "content-type",
4816 "x-priority",
4817 "user-agent",
4818 "list-help", /* rfc 2369, section 3 */
4819 "list-unsubscribe",
4820 "list-subscribe",
4821 "list-post",
4822 "list-owner",
4823 "list-archive",
4824 NULL
4827 for(p = forbidden_headers; *p; p++)
4828 if(!strucmp(name, *p))
4829 break;
4831 return((*p) ? 1 : 0);
4836 * hdr_is_in_list - is there a custom value for this header?
4838 * hdr - the header name to check
4839 * custom - the list to check in
4840 * Returns 1 if there is a custom value, 0 otherwise.
4843 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4845 PINEFIELD *pf;
4847 for(pf = custom; pf && pf->name; pf = pf->next)
4848 if(strucmp(pf->name, hdr) == 0)
4849 return 1;
4851 return 0;
4856 * count_custom_hdrs_pf - returns number of custom headers in arg
4857 * custom -- the list to be counted
4858 * only_nonstandard -- only count headers which aren't standard pine headers
4861 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4863 int ret = 0;
4865 for(; custom && custom->name; custom = custom->next)
4866 if(!only_nonstandard || !custom->standard)
4867 ret++;
4869 return(ret);
4874 * count_custom_hdrs_list - returns number of custom headers in arg
4877 count_custom_hdrs_list(char **list)
4879 char **p;
4880 char *q = NULL;
4881 char *name;
4882 char *t;
4883 char save;
4884 int ret = 0;
4886 if(list){
4887 for(p = list; (q = *p) != NULL; p++){
4888 if(q[0]){
4889 /* remove leading whitespace */
4890 name = skip_white_space(q);
4892 /* look for colon or space or end */
4893 for(t = name;
4894 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4895 ;/* do nothing */
4897 save = *t;
4898 *t = '\0';
4899 if(!pine_header_forbidden(name))
4900 ret++;
4902 *t = save;
4907 return(ret);
4912 * set_default_hdrval - put the user's default value for this header
4913 * into pf->textbuf.
4914 * setthis - the pinefield to be set
4915 * custom - where to look for the default
4917 CustomType
4918 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4920 PINEFIELD *pf;
4921 CustomType ret = NoMatch;
4923 if(!setthis || !setthis->name){
4924 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4925 return(ret);
4928 setthis->textbuf = NULL;
4930 for(pf = custom; pf && pf->name; pf = pf->next){
4931 if(strucmp(pf->name, setthis->name) != 0)
4932 continue;
4934 ret = pf->cstmtype;
4936 /* turn on editing */
4937 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4938 setthis->canedit = 1;
4940 if(pf->val)
4941 setthis->textbuf = cpystr(pf->val);
4944 if(!setthis->textbuf)
4945 setthis->textbuf = cpystr("");
4947 return(ret);
4952 * pine_header_standard - is this name a "standard" header?
4954 * name - the header name to check
4956 FieldType
4957 pine_header_standard(char *name)
4959 int i;
4961 /* check to see if this is a standard header */
4962 for(i = 0; pf_template[i].name; i++)
4963 if(!strucmp(name, pf_template[i].name))
4964 return(pf_template[i].type);
4966 return(TypeUnknown);
4971 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
4972 * Allocates space for each name and addr ptr.
4973 * Allocates space for default in textbuf, even if empty.
4975 * head - the first PINEFIELD to fill in
4976 * list - the list to parse
4978 void
4979 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
4981 char **p, *q, *t, *name, *value, save;
4982 PINEFIELD *pf;
4984 pf = head;
4986 if(list){
4987 for(p = list; (q = *p) != NULL; p++){
4989 if(q[0]){
4991 /* anything after leading whitespace? */
4992 if(!*(name = skip_white_space(q)))
4993 continue;
4995 /* look for colon or space or end */
4996 for(t = name;
4997 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4998 ;/* do nothing */
5000 /* if there is a space in the field-name, skip it */
5001 if(isspace((unsigned char)*t)){
5002 q_status_message1(SM_ORDER, 3, 3,
5003 _("Space not allowed in header name (%s)"),
5004 name);
5005 continue;
5008 save = *t;
5009 *t = '\0';
5011 /* Don't allow any of the forbidden headers. */
5012 if(pine_header_forbidden(name)){
5013 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5014 _("Not allowed to change header \"%s\""),
5015 name);
5017 *t = save;
5018 continue;
5021 if(pf){
5022 if(pine_header_standard(name) != TypeUnknown)
5023 pf->standard = 1;
5025 pf->name = cpystr(name);
5026 pf->type = FreeText;
5027 pf->cstmtype = cstmtype;
5028 pf->next = pf+1;
5030 #ifdef OLDWAY
5032 * Some mailers apparently break if we change
5033 * user@domain into Fred <user@domain> for
5034 * return-receipt-to,
5035 * so we'll just call this a FreeText field, too.
5038 * For now, all custom headers are FreeText except for
5039 * this one that we happen to know about. We might
5040 * have to add some syntax to the config option so that
5041 * people can tell us their custom header takes addresses.
5043 if(!strucmp(pf->name, "Return-Receipt-to")){
5044 pf->type = Address;
5045 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5046 *pf->addr = (ADDRESS *)NULL;
5048 #endif /* OLDWAY */
5050 *t = save;
5052 /* remove space between name and colon */
5053 value = skip_white_space(t);
5055 /* give them an alloc'd default, even if empty */
5056 pf->textbuf = cpystr((*value == ':')
5057 ? skip_white_space(++value) : "");
5058 if(pf->textbuf && pf->textbuf[0])
5059 pf->val = cpystr(pf->textbuf);
5061 pf++;
5063 else
5064 *t = save;
5069 /* fix last next pointer */
5070 if(head && pf != head)
5071 (pf-1)->next = NULL;
5076 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5077 * setup except that it doesn't have default values.
5078 * Add those defaults if they exist in "list".
5080 * head - the first PINEFIELD to add a default to
5081 * list - the list to get the defaults from
5083 void
5084 add_defaults_from_list(PINEFIELD *head, char **list)
5086 char **p, *q, *t, *name, *value, save;
5087 PINEFIELD *pf;
5089 for(pf = head; pf && list; pf = pf->next){
5090 if(!pf->name)
5091 continue;
5093 for(p = list; (q = *p) != NULL; p++){
5095 if(q[0]){
5097 /* anything after leading whitespace? */
5098 if(!*(name = skip_white_space(q)))
5099 continue;
5101 /* look for colon or space or end */
5102 for(t = name;
5103 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5104 ;/* do nothing */
5106 /* if there is a space in the field-name, skip it */
5107 if(isspace((unsigned char)*t))
5108 continue;
5110 save = *t;
5111 *t = '\0';
5113 if(strucmp(name, pf->name) != 0){
5114 *t = save;
5115 continue;
5118 *t = save;
5121 * Found the right header. See if it has a default
5122 * value set.
5125 /* remove space between name and colon */
5126 value = skip_white_space(t);
5128 if(*value == ':'){
5129 char *defval;
5131 defval = skip_white_space(++value);
5132 if(defval && *defval){
5133 if(pf->val)
5134 fs_give((void **)&pf->val);
5136 pf->val = cpystr(defval);
5140 break; /* on to next pf */
5148 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5149 * fill in the defaults
5150 * Args - list -- The list to parse.
5151 * cstmtype -- Fill the in cstmtype field with this value
5153 PINEFIELD *
5154 parse_custom_hdrs(char **list, CustomType cstmtype)
5156 PINEFIELD *pfields;
5157 int i;
5160 * add one for possible use by fcc
5161 * What is this "possible use"? I don't see it. I don't
5162 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5164 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5165 pfields = (PINEFIELD *)fs_get((size_t) i);
5166 memset(pfields, 0, (size_t) i);
5168 /* set up the custom header pfields */
5169 customized_hdr_setup(pfields, list, cstmtype);
5171 return(pfields);
5176 * Combine the two lists of headers into one list which is allocated here
5177 * and freed by the caller. Eliminate duplicates with values from the role
5178 * taking precedence over values from the default.
5180 PINEFIELD *
5181 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5183 PINEFIELD *pfields, *pf, *pf2;
5184 int max_hdrs, i;
5186 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5187 count_custom_hdrs_pf(dflthdrs,0);
5189 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5190 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5192 pf = pfields;
5193 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5194 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5195 pf->type = pf2->type;
5196 pf->cstmtype = pf2->cstmtype;
5197 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5198 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5199 pf->standard = pf2->standard;
5200 pf->next = pf+1;
5201 pf++;
5204 /* if these aren't already there, add them */
5205 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5206 /* check for already there */
5207 for(i = 0;
5208 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5209 i++)
5212 if(!pfields[i].name){ /* this is a new one */
5213 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5214 pf->type = pf2->type;
5215 pf->cstmtype = pf2->cstmtype;
5216 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5217 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5218 pf->standard = pf2->standard;
5219 pf->next = pf+1;
5220 pf++;
5224 /* fix last next pointer */
5225 if(pf != pfields)
5226 (pf-1)->next = NULL;
5228 return(pfields);
5233 * free_customs - free misc. resources associated with custom header fields
5235 * pf - pointer to first custom field
5237 void
5238 free_customs(PINEFIELD *head)
5240 PINEFIELD *pf;
5242 for(pf = head; pf && pf->name; pf = pf->next){
5244 fs_give((void **)&pf->name);
5246 if(pf->val)
5247 fs_give((void **)&pf->val);
5249 /* only true for FreeText */
5250 if(pf->textbuf)
5251 fs_give((void **)&pf->textbuf);
5253 /* only true for Address */
5254 if(pf->addr && *pf->addr)
5255 mail_free_address(pf->addr);
5258 fs_give((void **)&head);
5263 * encode_whole_header
5265 * Returns 1 if whole value should be encoded
5266 * 0 to encode only within comments
5269 encode_whole_header(char *field, METAENV *header)
5271 int retval = 0;
5272 PINEFIELD *pf;
5274 if(field && (!strucmp(field, "Subject") ||
5275 !strucmp(field, "Comment") ||
5276 !struncmp(field, "X-", 2)))
5277 retval++;
5278 else if(field && *field && header && header->custom){
5279 for(pf = header->custom; pf && pf->name; pf = pf->next){
5281 if(!pf->standard && !strucmp(pf->name, field)){
5282 retval++;
5283 break;
5288 return(retval);
5292 /*----------------------------------------------------------------------
5293 Post news via NNTP or inews
5295 Args: env -- envelope of message to post
5296 body -- body of message to post
5298 Returns: -1 if failed or cancelled, 1 if succeeded
5300 WARNING: This call function has the side effect of writing the message
5301 to the lmc.so object.
5302 ----*/
5304 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5305 void (*pipecb_f)(PIPE_S *, int, void *))
5307 char *error_mess, error_buf[200], **news_servers;
5308 char **servers_to_use;
5309 int we_cancel = 0, server_no = 0, done_posting = 0;
5310 void *orig_822_output;
5311 BODY *bp = NULL;
5313 error_buf[0] = '\0';
5314 we_cancel = busy_cue("Posting news", NULL, 0);
5316 dprint((4, "Posting: [%s]\n",
5317 (header && header->env && header->env->newsgroups)
5318 ? header->env->newsgroups : "?"));
5320 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5321 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5322 && ps_global->VAR_NNTP_SERVER[0][0])){
5323 /*---------- NNTP server defined ----------*/
5324 error_mess = NULL;
5325 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5326 && alt_nntp_servers[0][0])
5327 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5330 * Install our rfc822 output routine
5332 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5333 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5334 (void *)post_rfc822_output);
5336 server_no = 0;
5337 news_servers = (char **)fs_get(2 * sizeof(char *));
5338 news_servers[1] = NULL;
5339 while(!done_posting && servers_to_use[server_no] &&
5340 servers_to_use[server_no][0]){
5341 news_servers[0] = servers_to_use[server_no];
5342 ps_global->noshow_error = 1;
5343 #ifdef DEBUG
5344 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5345 #else
5346 sending_stream = nntp_open(news_servers, 0L);
5347 #endif
5348 ps_global->noshow_error = 0;
5350 if(sending_stream != NULL) {
5351 unsigned short save_encoding, added_encoding;
5354 * Fake that we've got clearance from the transport agent
5355 * for 8bit transport for the benefit of our output routines...
5357 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5358 && (bp = first_text_8bit(body))){
5359 int i;
5361 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5364 if(i > ENCMAX){ /* no empty encoding slots! */
5365 bp = NULL;
5367 else {
5368 added_encoding = i;
5369 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5370 save_encoding = bp->encoding;
5371 bp->encoding = added_encoding;
5376 * Set global header pointer so we can get at it later...
5378 send_header = header;
5379 ps_global->noshow_error = 1;
5380 if(nntp_mail(sending_stream, header->env, body) == 0)
5381 snprintf(error_mess = error_buf, sizeof(error_buf),
5382 _("Error posting message: %s"),
5383 sending_stream->reply);
5384 else{
5385 done_posting = 1;
5386 error_buf[0] = '\0';
5387 error_mess = NULL;
5390 error_buf[sizeof(error_buf)-1] = '\0';
5391 smtp_close(sending_stream);
5392 ps_global->noshow_error = 0;
5393 sending_stream = NULL;
5394 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5395 body_encodings[added_encoding] = NULL;
5396 bp->encoding = save_encoding;
5399 } else {
5400 /*---- Open of NNTP connection failed ------ */
5401 snprintf(error_mess = error_buf, sizeof(error_buf),
5402 _("Error connecting to news server: %s"),
5403 ps_global->c_client_error);
5404 error_buf[sizeof(error_buf)-1] = '\0';
5405 dprint((1, error_buf));
5407 server_no++;
5409 fs_give((void **)&news_servers);
5410 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5411 } else {
5412 /*----- Post via local mechanism -------*/
5413 #ifdef _WINDOWS
5414 snprintf(error_mess = error_buf, sizeof(error_buf),
5415 _("Can't post, NNTP-server must be defined!"));
5416 #else /* UNIX */
5417 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5418 pipecb_f);
5419 #endif
5422 if(we_cancel)
5423 cancel_busy_cue(0);
5425 if(error_mess){
5426 if(lmc.so && !lmc.all_written)
5427 so_give(&lmc.so); /* clean up any fcc data */
5429 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5430 return(-1);
5433 lmc.all_written = 1;
5434 return(1);
5438 /* ----------------------------------------------------------------------
5439 Figure out command to start local SMTP agent
5441 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5443 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5445 ----*/
5446 char *
5447 smtp_command(char *errbuf, size_t errbuflen)
5449 #ifdef _WINDOWS
5450 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5451 && ps_global->VAR_SMTP_SERVER[0][0]))
5452 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5454 errbuf[errbuflen-1] = '\0';
5455 #else /* UNIX */
5456 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5457 char tmp[256];
5459 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (sizeof(tmp)-3)/2, SENDMAIL,
5460 (sizeof(tmp)-3)/2, SENDMAILFLAGS);
5461 return(cpystr(tmp));
5462 # else
5463 strncpy(errbuf, _("No default posting command."), errbuflen);
5464 errbuf[errbuflen-1] = '\0';
5465 # endif
5466 #endif
5468 return(NULL);
5473 #ifndef _WINDOWS
5474 /*----------------------------------------------------------------------
5475 Hand off given message to local posting agent
5477 Args: envelope -- The envelope for the BCC and debugging
5478 header -- The text of the message header
5479 errbuf -- buffer for reporting errors (assumed non-NULL)
5480 len -- Length of errbuf
5482 ----*/
5484 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5485 char *errbuf, size_t len,
5486 void (*bigresult_f) (char *, int),
5487 void (*pipecb_f)(PIPE_S *, int, void *))
5489 #ifdef DF_SENDMAIL_PATH
5490 char cmd_buf[256];
5491 #endif
5492 char *cmd = NULL;
5495 * A bit of complicated policy implemented here.
5496 * There are two posting variables sendmail-path and smtp-server.
5497 * Precedence is in that order.
5498 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5499 * Precedence is in that order.
5500 * Said differently, the order goes something like what's below.
5502 * NOTE: the fixed/command-line/user precendence handling is also
5503 * indicated by what's pointed to by ps_global->VAR_*, but since
5504 * that also includes the global defaults, it's not sufficient.
5507 if(ps_global->FIX_SENDMAIL_PATH
5508 && ps_global->FIX_SENDMAIL_PATH[0]){
5509 cmd = ps_global->FIX_SENDMAIL_PATH;
5511 else if(!(ps_global->FIX_SMTP_SERVER
5512 && ps_global->FIX_SMTP_SERVER[0])){
5513 if(ps_global->COM_SENDMAIL_PATH
5514 && ps_global->COM_SENDMAIL_PATH[0]){
5515 cmd = ps_global->COM_SENDMAIL_PATH;
5517 else if(!(ps_global->COM_SMTP_SERVER
5518 && ps_global->COM_SMTP_SERVER[0])){
5519 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5520 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5521 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5522 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5523 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5524 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5525 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5526 else
5527 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5529 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5530 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5531 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5532 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5533 if(ps_global->GLO_SENDMAIL_PATH
5534 && ps_global->GLO_SENDMAIL_PATH[0]){
5535 cmd = ps_global->GLO_SENDMAIL_PATH;
5537 #ifdef DF_SENDMAIL_PATH
5539 * This defines the default method of posting. So,
5540 * unless we're told otherwise use it...
5542 else if(!(ps_global->GLO_SMTP_SERVER
5543 && ps_global->GLO_SMTP_SERVER[0])){
5544 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5545 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5547 #endif
5552 *errbuf = '\0';
5553 if(cmd){
5554 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5556 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5557 return(1);
5559 else
5560 return(0);
5565 /*----------------------------------------------------------------------
5566 Hand off given message to local posting agent
5568 Args: envelope -- The envelope for the BCC and debugging
5569 header -- The text of the message header
5570 errbuf -- buffer for reporting errors (assumed non-NULL)
5571 errbuflen -- Length of errbuf
5573 Fork off mailer process and pipe the message into it
5574 Called to post news via Inews when NNTP is unavailable
5576 ----*/
5577 char *
5578 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5579 size_t errbuflen,
5580 void (*bigresult_f) (char *, int),
5581 void (*pipecb_f)(PIPE_S *, int, void *))
5583 char *err = NULL;
5584 #ifdef SENDNEWS
5585 char *s;
5586 char tmp[200];
5588 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5589 *s = '\0';
5591 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5592 strncpy(tmp, err, sizeof(tmp)-1);
5593 tmp[sizeof(tmp)-1] = '\0';
5594 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5595 SENDNEWS, tmp);
5598 if(s)
5599 *s = ' '; /* restore the date */
5601 #else /* !SENDNEWS */ /* this is the default case */
5602 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5603 err[errbuflen-1] = '\0';
5604 #endif /* !SENDNEWS */
5605 return(err);
5610 /*----------------------------------------------------------------------
5611 Hand off message to local MTA; it parses recipients from 822 header
5613 Args: header -- struct containing header data
5614 body -- struct containing message body data
5615 cmd -- command to use for handoff (%s says where file should go)
5616 errs -- pointer to buf to hold errors
5618 ----*/
5619 char *
5620 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5621 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5622 void (*pipecb_f)(PIPE_S *, int, void *))
5624 char *result = NULL;
5625 PIPE_S *pipe;
5627 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5629 if((pipe = open_system_pipe(cmd, &result, NULL,
5630 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5631 0, pipecb_f, pipe_report_error)) != NULL){
5632 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5633 (TCPSTREAM *) pipe)){
5634 strncpy(errs, _("Error posting."), errslen-1);
5635 errs[errslen-1] = '\0';
5638 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5639 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5640 if(result && bigresult_f)
5641 (*bigresult_f)(result, CM_BR_ERROR);
5644 else
5645 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5647 if(result){
5648 our_unlink(result);
5649 fs_give((void **)&result);
5652 return(*errs ? errs : NULL);
5657 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5658 * pipes rather than a tcp stream
5660 long
5661 pine_pipe_soutr_nl (void *stream, char *s)
5663 long rv = T;
5664 char *p;
5665 size_t n;
5667 while(*s && rv){
5668 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0)
5669 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5670 if(rv < 0){
5671 if(errno != EINTR){
5672 rv = 0;
5673 break;
5676 else{
5677 s += rv;
5678 n -= rv;
5681 if(p && rv){
5682 s = p + 2; /* write UNIX EOL */
5683 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5684 if(rv < 0 && errno != EINTR){
5685 rv = 0;
5686 break;
5689 else
5690 break;
5693 return(rv);
5695 #endif
5698 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5702 * helpful def's
5704 #define S(X) ((PIPE_S *)(X))
5705 #define GETBUFLEN (4 * MAILTMPLEN)
5708 void *
5709 piped_smtp_open (char *host, char *service, long unsigned int port)
5711 char *postcmd;
5712 void *rv = NULL;
5714 if(strucmp(host, "localhost")){
5715 char tmp[MAILTMPLEN];
5717 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5718 sizeof(tmp)-50, host);
5719 tmp[sizeof(tmp)-1] = '\0';
5720 mm_log(tmp, ERROR);
5722 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5723 rv = open_system_pipe(postcmd, NULL, NULL,
5724 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5725 0, NULL, pipe_report_error);
5726 fs_give((void **) &postcmd);
5728 else
5729 mm_log(ps_global->c_client_error, ERROR);
5731 return(rv);
5735 void *
5736 piped_aopen (NETMBX *mb, char *service, char *user)
5738 return(NULL);
5743 * piped_soutr - Replacement for tcp_soutr that writes one of our
5744 * pipes rather than a tcp stream
5746 long
5747 piped_soutr (void *stream, char *s)
5749 return(piped_sout(stream, s, strlen(s)));
5754 * piped_sout - Replacement for tcp_soutr that writes one of our
5755 * pipes rather than a tcp stream
5757 long
5758 piped_sout (void *stream, char *s, long unsigned int size)
5760 int i, o;
5762 if(S(stream)->out.d < 0)
5763 return(0L);
5765 if((i = (int) size) != 0){
5766 while((o = write(S(stream)->out.d, s, i)) != i)
5767 if(o < 0){
5768 if(errno != EINTR){
5769 piped_abort(stream);
5770 return(0L);
5773 else{
5774 s += o; /* try again, fix up counts */
5775 i -= o;
5779 return(1L);
5784 * piped_getline - Replacement for tcp_getline that reads one
5785 * of our pipes rather than a tcp pipe
5787 * C-client expects that the \r\n will be stripped off.
5789 char *
5790 piped_getline (void *stream)
5792 static int cnt;
5793 static char *ptr;
5794 int n, m;
5795 char *ret, *s, *sp, c = '\0', d;
5797 if(S(stream)->in.d < 0)
5798 return(NULL);
5800 if(!S(stream)->tmp){ /* initialize! */
5801 /* alloc space to collect input (freed in close_system_pipe) */
5802 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5803 memset(S(stream)->tmp, 0, GETBUFLEN);
5804 cnt = -1;
5807 while(cnt < 0){
5808 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5809 if(errno != EINTR){
5810 piped_abort(stream);
5811 return(NULL);
5814 if(cnt == 0){
5815 piped_abort(stream);
5816 return(NULL);
5819 ptr = S(stream)->tmp;
5822 s = ptr;
5823 n = 0;
5824 while(cnt--){
5825 d = *ptr++;
5826 if((c == '\015') && (d == '\012')){
5827 ret = (char *)fs_get (n--);
5828 memcpy(ret, s, n);
5829 ret[n] = '\0';
5830 return(ret);
5833 n++;
5834 c = d;
5836 /* copy partial string from buffer */
5837 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5838 /* get more data */
5839 while(cnt < 0){
5840 memset(S(stream)->tmp, 0, GETBUFLEN);
5841 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5842 if(errno != EINTR){
5843 fs_give((void **) &ret);
5844 piped_abort(stream);
5845 return(NULL);
5848 if(cnt == 0){
5849 if(n > 0)
5850 ret[n-1] = '\0'; /* to try to get error message logged */
5851 else{
5852 piped_abort(stream);
5853 return(NULL);
5857 ptr = S(stream)->tmp;
5860 if(c == '\015' && *ptr == '\012'){
5861 ptr++;
5862 cnt--;
5863 ret[n - 1] = '\0'; /* tie off string with null */
5865 else if ((s = piped_getline(stream)) != NULL) {
5866 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5867 memcpy(ret, sp, n); /* copy first part */
5868 memcpy(ret + n, s, m); /* and second part */
5869 fs_give((void **) &sp); /* flush first part */
5870 fs_give((void **) &s); /* flush second part */
5871 ret[n + m] = '\0'; /* tie off string with null */
5874 return(ret);
5879 * piped_close - Replacement for tcp_close that closes pipes to our
5880 * child rather than a tcp connection
5882 void
5883 piped_close(void *stream)
5886 * Uninstall our hooks into smtp_send since it's being used by
5887 * the nntp driver as well...
5889 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5894 * piped_abort - close down the pipe we were using to post
5896 void
5897 piped_abort(void *stream)
5899 if(S(stream)->in.d >= 0){
5900 close(S(stream)->in.d);
5901 S(stream)->in.d = -1;
5904 if(S(stream)->out.d){
5905 close(S(stream)->out.d);
5906 S(stream)->out.d = -1;
5911 char *
5912 piped_host(void *stream)
5914 return(ps_global->hostname ? ps_global->hostname : "localhost");
5918 unsigned long
5919 piped_port(void *stream)
5921 return(0L);