* Minor updates to source to update copyright notices (part 1)
[alpine.git] / pith / send.c
blobf074f27c84d7aefe66ff110020ba9cc1e627640c
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-2016 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/send.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/store.h"
24 #include "../pith/mimedesc.h"
25 #include "../pith/context.h"
26 #include "../pith/status.h"
27 #include "../pith/folder.h"
28 #include "../pith/bldaddr.h"
29 #include "../pith/pipe.h"
30 #include "../pith/mailview.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/list.h"
33 #include "../pith/filter.h"
34 #include "../pith/reply.h"
35 #include "../pith/addrstring.h"
36 #include "../pith/rfc2231.h"
37 #include "../pith/stream.h"
38 #include "../pith/util.h"
39 #include "../pith/adrbklib.h"
40 #include "../pith/options.h"
41 #include "../pith/busy.h"
42 #include "../pith/text.h"
43 #include "../pith/imap.h"
44 #include "../pith/ablookup.h"
45 #include "../pith/sort.h"
46 #include "../pith/smime.h"
48 #include "../c-client/smtp.h"
49 #include "../c-client/nntp.h"
52 /* this is used in pine_send and pine_simple_send */
53 /* name::type::canedit::writehdr::localcopy::rcptto */
54 PINEFIELD pf_template[] = {
55 {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
56 {"From", Address, 0, 1, 1, 0},
57 {"Reply-To", Address, 0, 1, 1, 0},
58 {TONAME, Address, 1, 1, 1, 1},
59 {CCNAME, Address, 1, 1, 1, 1},
60 {"bcc", Address, 1, 0, 1, 1},
61 {"Newsgroups", FreeText, 1, 1, 1, 0},
62 {"Fcc", Fcc, 1, 0, 0, 0},
63 {"Lcc", Address, 1, 0, 1, 1},
64 {"Attchmnt", Attachment, 1, 1, 1, 0},
65 {SUBJNAME, Subject, 1, 1, 1, 0},
66 {"References", FreeText, 0, 1, 1, 0},
67 {"Date", FreeText, 0, 1, 1, 0},
68 {"In-Reply-To", FreeText, 0, 1, 1, 0},
69 {"Message-ID", FreeText, 0, 1, 1, 0},
70 {PRIORITYNAME, FreeText, 0, 1, 1, 0},
71 {"User-Agent", FreeText, 0, 1, 1, 0},
72 {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
73 {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
74 {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
75 {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
76 {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
77 {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
78 {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
79 {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
80 {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
81 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
82 {"X-X-Sender", Address, 0, 1, 1, 0},
83 #endif
84 {NULL, FreeText}
88 PRIORITY_S priorities[] = {
89 {1, "Highest"},
90 {2, "High"},
91 {3, "Normal"},
92 {4, "Low"},
93 {5, "Lowest"},
94 {0, NULL}
98 #define ctrl(c) ((c) & 0x1f)
100 /* which message part to test for xliteration */
101 typedef enum {MsgBody, HdrText} MsgPart;
105 * Internal prototypes
107 long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
108 int l_flush_net(int);
109 int l_putc(int);
110 int pine_write_header_line(char *, char *, STORE_S *);
111 int pine_write_params(PARAMETER *, STORE_S *);
112 char *tidy_smtp_mess(char *, char *, char *, size_t);
113 int lmc_body_header_line(char *, int);
114 int lmc_body_header_finish(void);
115 int pwbh_finish(int, STORE_S *);
116 int sent_percent(void);
117 unsigned short *setup_avoid_table(void);
118 #ifndef _WINDOWS
119 int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
120 void (*)(PIPE_S *, int, void *));
121 char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
122 void (*)(PIPE_S *, int, void *));
123 char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
124 void (*)(PIPE_S *, int, void *));
125 long pine_pipe_soutr_nl(void *, char *);
126 #endif
127 char *smtp_command(char *, size_t);
128 void *piped_smtp_open(char *, char *, unsigned long);
129 void *piped_aopen(NETMBX *, char *, char *);
130 long piped_soutr(void *, char *);
131 long piped_sout(void *, char *, unsigned long);
132 char *piped_getline(void *);
133 void piped_close(void *);
134 void piped_abort(void *);
135 char *piped_host(void *);
136 unsigned long piped_port(void *);
137 char *posting_characterset(void *, char *, MsgPart);
138 int body_is_translatable(void *, char *);
139 int text_is_translatable(void *, char *);
140 int dummy_putc(int);
141 unsigned long *init_charsetchecker(char *);
142 int representable_in_charset(unsigned long, char *);
143 char *most_preferred_charset(unsigned long);
146 * Storage object where the FCC (or postponed msg) is to be written.
147 * This is amazingly bogus. Much work was done to put messages
148 * together and encode them as they went to the tmp file for sendmail
149 * or into the SMTP slot (especially for for DOS, to prevent a temporary
150 * file (and needlessly copying the message).
152 * HOWEVER, since there's no piping into c-client routines
153 * (particularly mail_append() which copies the fcc), the fcc will have
154 * to be copied to disk. This global tells pine's copy of the rfc822
155 * output functions where to also write the message bytes for the fcc.
156 * With piping in the c-client we could just have two pipes to shove
157 * down rather than messing with damn copies. FIX THIS!
159 * The function open_fcc, locates the actual folder and creates it if
160 * requested before any mailing or posting is done.
162 struct local_message_copy lmc;
166 * Locally global pointer to stream used for sending/posting.
167 * It's also used to indicate when/if we write the Bcc: field in
168 * the header.
170 static SENDSTREAM *sending_stream = NULL;
173 static struct hooks {
174 void *rfc822_out; /* Message outputter */
175 } sending_hooks;
178 static FILE *verbose_send_output = NULL;
179 static long send_bytes_sent, send_bytes_to_send;
180 static METAENV *send_header = NULL;
183 * Hooks for prompts and confirmations
185 int (*pith_opt_daemon_confirm)(void);
188 static NETDRIVER piped_io = {
189 piped_smtp_open, /* open a connection */
190 piped_aopen, /* open an authenticated connection */
191 piped_getline, /* get a line */
192 NULL, /* get a buffer */
193 piped_soutr, /* output pushed data */
194 piped_sout, /* output string */
195 piped_close, /* close connection */
196 piped_host, /* return host name */
197 piped_host, /* remotehost */
198 piped_port, /* return port number */
199 piped_host /* return local host (NOTE: same as host!) */
204 * Since c-client preallocates, it's necessary here to define a limit
205 * such that we don't blow up in c-client (see rfc822_address_line()).
207 #define MAX_SINGLE_ADDR MAILTMPLEN
209 #define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
213 * Phone home hash controls
215 #define PH_HASHBITS 24
216 #define PH_MAXHASH (1<<(PH_HASHBITS))
220 * postponed_stream - return stream associated with postponed messages
221 * in argument.
224 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
226 MAILSTREAM *stream = NULL;
227 CONTEXT_S *p_cntxt = NULL;
228 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
229 int exists;
231 if(!(streamp && mbox))
232 return(0);
234 *streamp = NULL;
237 * find default context to look for folder...
239 * The "mbox" is assumed to be local if we're given what looks
240 * like an absolute path. This is different from Goto/Save
241 * where we do alot of work to interpret paths relative to the
242 * server. This reason is to support all the pre-4.00 pinerc'
243 * that specified a path and because there's yet to be a way
244 * in c-client to specify otherwise in the face of a remote
245 * context.
247 if(!is_absolute_path(mbox)
248 && !(p_cntxt = default_save_context(ps_global->context_list)))
249 p_cntxt = ps_global->context_list;
251 /* check to see if the folder exists, the user wants to continue
252 * and that we can actually read something in...
254 exists = folder_name_exists(p_cntxt, mbox, &fullname);
255 if(fullname)
256 mbox = fullname;
258 if(exists & FEX_ISFILE){
259 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
260 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
262 * The mbox is relative to the home directory.
263 * Make it absolute so we can compare it to
264 * stream->mailbox.
266 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
267 SIZEOF_20KBUF);
268 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
269 tmp[sizeof(tmp)-1] = '\0';
272 if((stream = ps_global->mail_stream)
273 && !(stream->mailbox
274 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
275 || (*tmp == '{'
276 && same_stream(tmp, stream)
277 && (p = strchr(tmp, '}'))
278 && (q = strchr(stream->mailbox,'}'))
279 && !strcmp(p + 1, q + 1)))))
280 stream = NULL;
282 if(!stream){
283 stream = context_open(p_cntxt, NULL, mbox,
284 SP_USEPOOL|SP_TEMPUSE, NULL);
285 if(stream && !stream->halfopen){
286 if(stream->nmsgs > 0)
287 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
289 if(checknmsgs && stream->nmsgs < 1){
290 pine_mail_close(stream);
291 exists = 0;
292 stream = NULL;
295 else{
296 q_status_message2(SM_ORDER | SM_DING, 3, 3,
297 _("Can't open %s mailbox: %s"), type, mbox);
298 if(stream)
299 pine_mail_close(stream);
301 exists = 0;
302 stream = NULL;
306 else{
307 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
308 q_status_message1(SM_ORDER | SM_DING, 3, 3,
309 _("%s message folder doesn't exist!"), type);
313 if(fullname)
314 fs_give((void **) &fullname);
316 *streamp = stream;
318 return(exists);
323 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
324 struct mail_bodystruct **body, char **fcc, char **lcc,
325 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
326 ACTION_S **role, int flags, STORE_S *so)
328 MAILSTREAM *stream;
329 ENVELOPE *e = NULL;
330 BODY *b;
331 PART *part;
332 PINEFIELD *pf;
333 gf_io_t pc;
334 char *extras, **fields, **values, *p;
335 char *hdrs[2], *h, *charset;
336 char **smtp_servers = NULL, **nntp_servers = NULL;
337 int i, pine_generated = 0, our_replyto = 0;
338 int added_to_role = 0;
339 unsigned gbpt_flags = GBPT_NONE;
340 MESSAGECACHE *mc;
342 if(!(streamp && *streamp))
343 return(redraft_cleanup(streamp, TRUE, flags));
345 stream = *streamp;
347 if(flags & REDRAFT_HTML)
348 gbpt_flags |= GBPT_HTML_OK;
350 /* grok any user-defined or non-c-client headers */
351 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
354 * The custom headers to look for in the suspended message should
355 * have been stored in the X-Our-Headers header. So first we get
356 * that list. If we can't find it (version that stored the
357 * message < 4.30) then we use the global custom list.
359 hdrs[0] = OUR_HDRS_LIST;
360 hdrs[1] = NULL;
361 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
362 int commas = 0;
363 char **list;
364 char *hdrval = NULL;
366 if((hdrval = strindex(h, ':')) != NULL){
367 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
368 hdrval++)
372 /* count elements in list */
373 for(p = hdrval; p && *p; p++)
374 if(*p == ',')
375 commas++;
377 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
379 *custom = parse_custom_hdrs(list, Replace);
380 add_defaults_from_list(*custom,
381 ps_global->VAR_CUSTOM_HDRS);
382 free_list_array(&list);
385 if(*custom && !(*custom)->name){
386 free_customs(*custom);
387 *custom = NULL;
390 fs_give((void **)&h);
393 if(!*custom)
394 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
396 #define INDEX_FCC 0
397 #define INDEX_POSTERR 1
398 #define INDEX_REPLYUID 2
399 #define INDEX_REPLYMBOX 3
400 #define INDEX_SMTP 4
401 #define INDEX_NNTP 5
402 #define INDEX_CURSORPOS 6
403 #define INDEX_OUR_REPLYTO 7
404 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
405 #define FIELD_COUNT 9
407 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
410 * Having these two fields separated isn't the slickest, but
411 * converting the pointer array for fetchheader_lines() to
412 * a list of structures or some such for simple_header_parse()
413 * is too goonie. We could do something like re-use c-client's
414 * PARAMETER struct which is a simple char * pairing, but that
415 * doesn't make sense to pass to fetchheader_lines()...
417 fields = (char **) fs_get((size_t) i * sizeof(char *));
418 values = (char **) fs_get((size_t) i * sizeof(char *));
419 memset(fields, 0, (size_t) i * sizeof(char *));
420 memset(values, 0, (size_t) i * sizeof(char *));
422 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
423 fields[++i] = "X-Post-Error"; /* posting errors too */
424 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
425 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
426 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
427 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
428 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
429 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
430 fields[++i] = "Lcc"; /* Lcc: too... */
431 if(++i != FIELD_COUNT)
432 alpine_panic("Fix FIELD_COUNT");
434 for(pf = *custom; pf && pf->name; pf = pf->next)
435 if(!pf->standard)
436 fields[i++] = pf->name; /* assign custom fields */
438 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
439 simple_header_parse(extras, fields, values);
440 fs_give((void **) &extras);
443 * translate RFC 1522 strings,
444 * starting with "Lcc" field
446 for(i = INDEX_LCC; fields[i]; i++)
447 if(values[i]){
448 size_t len;
449 char *bufp, *biggerbuf = NULL;
451 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
452 len++;
453 biggerbuf = (char *)fs_get(len * sizeof(char));
454 bufp = biggerbuf;
456 else{
457 bufp = tmp_20k_buf;
458 len = SIZEOF_20KBUF;
461 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
463 if(p == tmp_20k_buf){
464 fs_give((void **)&values[i]);
465 values[i] = cpystr(p);
468 if(biggerbuf)
469 fs_give((void **)&biggerbuf);
472 for(pf = *custom, i = FIELD_COUNT;
473 pf && pf->name;
474 pf = pf->next){
475 if(pf->standard){
477 * Because the value is already in the envelope.
479 pf->cstmtype = NoMatch;
480 continue;
483 if(values[i]){ /* use this instead of default */
484 if(pf->textbuf)
485 fs_give((void **)&pf->textbuf);
487 pf->textbuf = values[i]; /* freed in pine_send! */
489 else if(pf->textbuf) /* was erased before postpone */
490 fs_give((void **)&pf->textbuf);
492 i++;
495 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
496 pine_generated = 1; /* we put it there? */
499 * Since c-client fills in the reply_to field in the envelope
500 * even if there isn't a Reply-To header in the message we
501 * have to work around that. When we postpone we add
502 * a second header that has value "Empty" if there really
503 * was a Reply-To and it was empty. It has the
504 * value "Full" if we put the Reply-To contents there
505 * intentionally (and it isn't empty).
507 if(values[INDEX_OUR_REPLYTO]){
508 if(values[INDEX_OUR_REPLYTO][0] == 'E')
509 our_replyto = 'E'; /* we put an empty one there */
510 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
511 our_replyto = 'F'; /* we put it there */
513 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
516 if(fcc) /* fcc: special case... */
517 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
518 else if(values[INDEX_FCC])
519 fs_give((void **) &values[INDEX_FCC]);
521 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
522 q_status_message(SM_ORDER|SM_DING, 4, 4,
523 values[INDEX_POSTERR]);
524 fs_give((void **) &values[INDEX_POSTERR]);
527 if(values[INDEX_REPLYUID]){
528 if(reply)
529 *reply = build_reply_uid(values[INDEX_REPLYUID]);
531 fs_give((void **) &values[INDEX_REPLYUID]);
533 if(values[INDEX_REPLYMBOX] && reply && *reply)
534 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
536 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
537 (*reply)->origmbox = cpystr((*reply)->mailbox);
540 if(values[INDEX_REPLYMBOX])
541 fs_give((void **) &values[INDEX_REPLYMBOX]);
543 if(values[INDEX_SMTP]){
544 char *q;
545 size_t cnt = 0;
548 * Turn the space delimited list of smtp servers into
549 * a char ** list.
551 p = values[INDEX_SMTP];
553 if(!*p || isspace((unsigned char) *p))
554 cnt++;
555 } while(*p++);
557 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
558 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
560 cnt = 0;
561 q = p = values[INDEX_SMTP];
563 if(!*p || isspace((unsigned char) *p)){
564 if(*p){
565 *p = '\0';
566 smtp_servers[cnt++] = cpystr(q);
567 *p = ' ';
568 q = p+1;
570 else
571 smtp_servers[cnt++] = cpystr(q);
573 } while(*p++);
575 fs_give((void **) &values[INDEX_SMTP]);
578 if(values[INDEX_NNTP]){
579 char *q;
580 size_t cnt = 0;
583 * Turn the space delimited list of smtp nntp into
584 * a char ** list.
586 p = values[INDEX_NNTP];
588 if(!*p || isspace((unsigned char) *p))
589 cnt++;
590 } while(*p++);
592 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
593 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
595 cnt = 0;
596 q = p = values[INDEX_NNTP];
598 if(!*p || isspace((unsigned char) *p)){
599 if(*p){
600 *p = '\0';
601 nntp_servers[cnt++] = cpystr(q);
602 *p = ' ';
603 q = p+1;
605 else
606 nntp_servers[cnt++] = cpystr(q);
608 } while(*p++);
610 fs_give((void **) &values[INDEX_NNTP]);
613 if(values[INDEX_CURSORPOS]){
615 * The redraft cursor position is written as two fields
616 * separated by a space. First comes the name of the
617 * header field we're in, or just a ":" if we're in the
618 * body. Then comes the offset into that header or into
619 * the body.
621 if(redraft_pos){
622 char *q1, *q2;
624 *redraft_pos
625 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
626 (*redraft_pos)->offset = 0L;
628 q1 = skip_white_space(values[INDEX_CURSORPOS]);
629 if(*q1 && (q2 = strindex(q1, SPACE))){
630 *q2 = '\0';
631 (*redraft_pos)->hdrname = cpystr(q1);
632 q1 = skip_white_space(q2+1);
633 if(*q1)
634 (*redraft_pos)->offset = atol(q1);
636 else
637 (*redraft_pos)->hdrname = cpystr(":");
640 fs_give((void **) &values[INDEX_CURSORPOS]);
643 if(lcc)
644 *lcc = values[INDEX_LCC];
645 else
646 fs_give((void **) &values[INDEX_LCC]);
649 fs_give((void **)&fields);
650 fs_give((void **)&values);
652 *outgoing = copy_envelope(e);
655 * If the postponed message has a From which is different from
656 * the default, it is either because allow-changing-from is on
657 * or because there was a role with a from that allowed it to happen.
658 * If allow-changing-from is not on, put this back in a role
659 * so that it will be allowed again in pine_send.
661 if(role && *role == NULL &&
662 !ps_global->never_allow_changing_from &&
663 *outgoing){
665 * Now check to see if the from is different from default from.
667 ADDRESS *deffrom;
669 deffrom = generate_from();
670 if(!((*outgoing)->from &&
671 address_is_same(deffrom, (*outgoing)->from) &&
672 ((!(deffrom->personal && deffrom->personal[0]) &&
673 !((*outgoing)->from->personal &&
674 (*outgoing)->from->personal[0])) ||
675 (deffrom->personal && (*outgoing)->from->personal &&
676 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
678 *role = (ACTION_S *)fs_get(sizeof(**role));
679 memset((void *)*role, 0, sizeof(**role));
680 if(!(*outgoing)->from)
681 (*outgoing)->from = mail_newaddr();
683 (*role)->from = (*outgoing)->from;
684 (*outgoing)->from = NULL;
685 added_to_role++;
688 mail_free_address(&deffrom);
692 * Look at each empty address and see if the user has specified
693 * a default for that field or not. If they have, that means
694 * they have erased it before postponing, so they won't want
695 * the default to come back. If they haven't specified a default,
696 * then the default should be generated in pine_send. We prevent
697 * the default from being assigned by assigning an empty address
698 * to the variable here.
700 * BUG: We should do this for custom Address headers, too, but
701 * there isn't such a thing yet.
703 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
704 (*outgoing)->to = mail_newaddr();
705 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
706 (*outgoing)->cc = mail_newaddr();
707 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
708 (*outgoing)->bcc = mail_newaddr();
710 if(our_replyto == 'E'){
711 /* user erased reply-to before postponing */
712 if((*outgoing)->reply_to)
713 mail_free_address(&(*outgoing)->reply_to);
716 * If empty is not the normal default, make the outgoing
717 * reply_to be an emtpy address. If it is default, leave it
718 * as NULL and the default will be used.
720 if(hdr_is_in_list("reply-to", *custom)){
721 PINEFIELD pf;
723 pf.name = "reply-to";
724 set_default_hdrval(&pf, *custom);
725 if(pf.textbuf){
726 if(pf.textbuf[0]) /* empty is not default */
727 (*outgoing)->reply_to = mail_newaddr();
729 fs_give((void **)&pf.textbuf);
733 else if(our_replyto == 'F'){
734 int add_to_role = 0;
737 * The reply-to is real. If it is different from the default
738 * reply-to, put it in the role so that it will show up when
739 * the user edits.
741 if(hdr_is_in_list("reply-to", *custom)){
742 PINEFIELD pf;
743 char *str;
745 pf.name = "reply-to";
746 set_default_hdrval(&pf, *custom);
747 if(pf.textbuf && pf.textbuf[0]){
748 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
749 if(!strcmp(str, pf.textbuf)){
750 /* standard value, leave it alone */
753 else /* not standard, put in role */
754 add_to_role++;
756 fs_give((void **)&str);
759 else /* not standard, put in role */
760 add_to_role++;
762 if(pf.textbuf)
763 fs_give((void **)&pf.textbuf);
765 else /* not standard, put in role */
766 add_to_role++;
768 if(add_to_role && role && (*role == NULL || added_to_role)){
769 if(*role == NULL){
770 added_to_role++;
771 *role = (ACTION_S *)fs_get(sizeof(**role));
772 memset((void *)*role, 0, sizeof(**role));
775 (*role)->replyto = (*outgoing)->reply_to;
776 (*outgoing)->reply_to = NULL;
779 else{
780 /* this is a bogus c-client generated replyto */
781 if((*outgoing)->reply_to)
782 mail_free_address(&(*outgoing)->reply_to);
785 if((smtp_servers || nntp_servers)
786 && role && (*role == NULL || added_to_role)){
787 if(*role == NULL){
788 *role = (ACTION_S *)fs_get(sizeof(**role));
789 memset((void *)*role, 0, sizeof(**role));
792 if(smtp_servers)
793 (*role)->smtp = smtp_servers;
794 if(nntp_servers)
795 (*role)->nntp = nntp_servers;
798 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
799 (*outgoing)->subject = cpystr("");
801 if(!pine_generated){
803 * Now, this is interesting. We should have found
804 * the "fcc:" field if pine wrote the message being
805 * redrafted. Hence, we probably can't trust the
806 * "originator" type fields, so we'll blast them and let
807 * them get set later in pine_send. This should allow
808 * folks with custom or edited From's and such to still
809 * use redraft reasonably, without inadvertently sending
810 * messages that appear to be "From" others...
812 if((*outgoing)->from)
813 mail_free_address(&(*outgoing)->from);
816 * Ditto for Reply-To and Sender...
818 if((*outgoing)->reply_to)
819 mail_free_address(&(*outgoing)->reply_to);
821 if((*outgoing)->sender)
822 mail_free_address(&(*outgoing)->sender);
825 if(!pine_generated || !(flags & REDRAFT_DEL)){
828 * Generate a fresh message id for pretty much the same
829 * reason From and such got wacked...
830 * Also, if we're coming from a form letter, we need to
831 * generate a different id each time.
833 if((*outgoing)->message_id)
834 fs_give((void **)&(*outgoing)->message_id);
836 (*outgoing)->message_id = generate_message_id();
839 if(b && b->type != TYPETEXT){
840 if(b->type == TYPEMULTIPART){
841 if(strucmp(b->subtype, "mixed")){
842 q_status_message1(SM_INFO, 3, 4,
843 "Converting Multipart/%s to Multipart/Mixed",
844 b->subtype);
845 fs_give((void **)&b->subtype);
846 b->subtype = cpystr("mixed");
849 else{
850 q_status_message2(SM_ORDER | SM_DING, 3, 4,
851 "Unable to resume type %s/%s message",
852 body_types[b->type], b->subtype);
853 return(redraft_cleanup(streamp, TRUE, flags));
857 gf_set_so_writec(&pc, so);
859 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
860 *body = copy_body(NULL, b);
861 part = (*body)->nested.part;
862 part->body.contents.text.data = (void *)so;
863 set_mime_type_by_grope(&part->body);
864 if(part->body.type != TYPETEXT){
865 q_status_message2(SM_ORDER | SM_DING, 3, 4,
866 "Unable to resume; first part is non-text: %s/%s",
867 body_types[part->body.type],
868 part->body.subtype);
869 return(redraft_cleanup(streamp, TRUE, flags));
872 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
873 /* let outgoing routines decide on charset */
874 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
875 set_parameter(&part->body.parameter, "charset", NULL);
877 fs_give((void **) &charset);
880 ps_global->postpone_no_flow = 1;
882 get_body_part_text(stream, &b->nested.part->body,
883 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
884 ps_global->postpone_no_flow = 0;
886 if(!fetch_contents(stream, cont_msg, NULL, *body))
887 q_status_message(SM_ORDER | SM_DING, 3, 4,
888 _("Error including all message parts"));
890 else{
891 *body = mail_newbody();
892 (*body)->type = TYPETEXT;
893 if(b->subtype /* these types are transformed to text/plain */
894 && strucmp(b->subtype, "richtext")
895 && strucmp(b->subtype, "enriched")
896 && strucmp(b->subtype, "html"))
897 (*body)->subtype = cpystr(b->subtype);
899 if((charset = parameter_val(b->parameter,"charset")) != NULL){
900 /* let outgoing routines decide on charset */
901 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
902 fs_give((void **) &charset);
903 else{
904 (*body)->parameter = mail_newbody_parameter();
905 (*body)->parameter->attribute = cpystr("charset");
906 if(utf8_charset(charset)){
907 fs_give((void **) &charset);
908 (*body)->parameter->value = cpystr("UTF-8");
910 else
911 (*body)->parameter->value = charset;
915 (*body)->contents.text.data = (void *)so;
916 ps_global->postpone_no_flow = 1;
917 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
918 NULL, NULL, gbpt_flags);
919 ps_global->postpone_no_flow = 0;
922 gf_clear_so_writec(so);
924 /* We have what we want, blast this message... */
925 if((flags & REDRAFT_DEL)
926 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
927 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
928 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
930 else
931 return(redraft_cleanup(streamp, TRUE, flags));
933 return(redraft_cleanup(streamp, FALSE, flags));
937 /*----------------------------------------------------------------------
938 Clear deleted messages from given stream and expunge if necessary
940 Args: stream --
941 problem --
943 ----*/
945 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
947 MAILSTREAM *stream;
949 if(!(streamp && *streamp))
950 return(0);
952 if(!problem && streamp && (stream = *streamp)){
953 if(stream->nmsgs){
954 ps_global->expunge_in_progress = 1;
955 mail_expunge(stream); /* clean out deleted */
956 ps_global->expunge_in_progress = 0;
959 if(!stream->nmsgs){ /* close and delete folder */
960 int do_the_broach = 0;
961 char *mbox = NULL;
963 if(stream){
964 if(stream->original_mailbox && stream->original_mailbox[0])
965 mbox = cpystr(stream->original_mailbox);
966 else if(stream->mailbox && stream->mailbox[0])
967 mbox = cpystr(stream->mailbox);
970 /* if it is current, we have to change folders */
971 if(stream == ps_global->mail_stream)
972 do_the_broach++;
975 * This is the stream to the empty postponed-msgs folder.
976 * We are going to delete the folder in a second. It is
977 * probably preferable to unselect the mailbox and leave
978 * this stream open for re-use instead of actually closing it,
979 * so we do that if possible.
981 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
983 * This does the UNSELECT on the stream. A NULL
984 * return should mean that something went wrong and
985 * a mail_close already happened, so that should have
986 * cleaned things up in the callback.
988 if((stream=mail_open(stream, stream->mailbox,
989 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
990 /* now close it so it is put into the stream cache */
991 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
992 pine_mail_close(stream);
995 else
996 pine_mail_actually_close(stream);
998 *streamp = NULL;
1000 if(do_the_broach){
1001 ps_global->mail_stream = NULL; /* already closed above */
1004 if(mbox && !pine_mail_delete(NULL, mbox))
1005 q_status_message1(SM_ORDER|SM_DING, 3, 3,
1006 /* TRANSLATORS: Arg is a mailbox name */
1007 _("Can't delete %s"), mbox);
1009 if(mbox)
1010 fs_give((void **) &mbox);
1014 return(!problem);
1018 /*----------------------------------------------------------------------
1019 Parse the given header text for any given fields
1021 Args: text -- Text to parse for fcc and attachments refs
1022 fields -- array of field names to look for
1023 values -- array of pointer to save values to, returned NULL if
1024 fields isn't in text.
1026 This function simply looks for the given fields in the given header
1027 text string.
1028 NOTE: newlines are expected CRLF, and we'll ignore continuations
1029 ----*/
1030 void
1031 simple_header_parse(char *text, char **fields, char **values)
1033 int i, n;
1034 char *p, *t;
1036 for(i = 0; fields[i]; i++)
1037 values[i] = NULL; /* clear values array */
1039 /*---- Loop until the end of the header ----*/
1040 for(p = text; *p; ){
1041 for(i = 0; fields[i]; i++) /* find matching field? */
1042 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1043 for(p += n + 1; *p; p++){ /* find start of value */
1044 if(*p == '\015' && *(p+1) == '\012'
1045 && !isspace((unsigned char) *(p+2)))
1046 break;
1048 if(!isspace((unsigned char) *p))
1049 break; /* order here is key... */
1052 if(!values[i]){ /* if we haven't already */
1053 values[i] = fs_get(strlen(text) + 1);
1054 values[i][0] = '\0'; /* alloc space for it */
1057 if(*p && *p != '\015'){ /* non-blank value. */
1058 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1059 while(*p){ /* check for cont'n lines */
1060 if(*p == '\015' && *(p+1) == '\012'){
1061 if(isspace((unsigned char) *(p+2))){
1062 p += 2;
1063 continue;
1065 else
1066 break;
1069 *t++ = *p++;
1072 *t = '\0';
1075 break;
1078 /* Skip to end of line, what ever it was */
1079 for(; *p ; p++)
1080 if(*p == '\015' && *(p+1) == '\012'){
1081 p += 2;
1082 break;
1088 /*----------------------------------------------------------------------
1089 build a fresh REPLY_S from the given string (see pine_send for format)
1091 Args: s -- "X-Reply-UID" header value
1093 Returns: filled in REPLY_S or NULL on parse error
1094 ----*/
1095 REPLY_S *
1096 build_reply_uid(char *s)
1098 char *p, *prefix = NULL, *val, *seq, *mbox;
1099 int i, nseq, forwarded = 0;
1100 REPLY_S *reply = NULL;
1102 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1103 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1104 if(*s == '('){
1105 if(*(p = s + 1) == ')'){
1106 forwarded = 1;
1108 else{
1109 for(; isdigit(*p); p++)
1112 if(*p == ' '){
1113 *p++ = '\0';
1115 if((i = atoi(s+1)) && i < strlen(p)){
1116 prefix = p;
1117 *(p += i) = '\0';
1120 else
1121 return(NULL);
1124 if(*++p == '(' && *++p){
1125 for(seq = p; isdigit(*p); p++)
1128 if(*p == ' '){
1129 *p++ = '\0';
1130 for(val = p; isdigit(*p); p++)
1133 if(*p == ' '){
1134 *p++ = '\0';
1136 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1137 && (p = strchr(p, ')')) && *(mbox = ++p)){
1138 imapuid_t *uidl;
1140 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1141 for(i = 0; i < nseq; i++)
1142 if((p = strchr(seq,',')) != NULL){
1143 *p = '\0';
1144 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1145 seq = ++p;
1146 else
1147 break;
1149 else if((p = strchr(seq, ')')) != NULL){
1150 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1151 i++;
1153 break;
1156 if(i == nseq){
1157 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1158 memset(reply, 0, sizeof(REPLY_S));
1159 reply->uid = 1;
1160 reply->data.uid.validity = strtoul(val, NULL, 10);
1161 if(forwarded)
1162 reply->forwarded = 1;
1163 else
1164 reply->prefix = cpystr(prefix);
1166 reply->mailbox = cpystr(mbox);
1167 uidl[nseq] = 0;
1168 reply->data.uid.msgs = uidl;
1170 else
1171 fs_give((void **) &uidl);
1178 return(reply);
1183 * pine_new_env - allocate a new METAENV and fill it in.
1185 METAENV *
1186 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1188 int cnt, i, stdcnt;
1189 char *p;
1190 PINEFIELD *pfields, *pf, **sending_order;
1191 METAENV *header;
1193 header = (METAENV *) fs_get(sizeof(METAENV));
1195 /* how many fields are there? */
1196 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1199 stdcnt = cnt;
1201 for(pf = custom; pf; pf = pf->next)
1202 cnt++;
1204 /* temporary PINEFIELD array */
1205 i = (cnt + 1) * sizeof(PINEFIELD);
1206 pfields = (PINEFIELD *)fs_get((size_t) i);
1207 memset(pfields, 0, (size_t) i);
1209 i = (cnt + 1) * sizeof(PINEFIELD *);
1210 sending_order = (PINEFIELD **)fs_get((size_t) i);
1211 memset(sending_order, 0, (size_t) i);
1213 header->env = outgoing;
1214 header->local = pfields;
1215 header->custom = custom;
1216 header->sending_order = sending_order;
1218 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1219 # define NN 4
1220 #else
1221 # define NN 3
1222 #endif
1224 /* initialize pfield */
1225 pf = pfields;
1226 for(i=0; i < stdcnt; i++, pf++){
1228 pf->name = cpystr(pf_template[i].name);
1229 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
1230 /* slide string over so it is Sender instead of X-X-Sender */
1231 for(p=pf->name; *(p+1); p++)
1232 *p = *(p+4);
1234 pf->type = pf_template[i].type;
1235 pf->canedit = pf_template[i].canedit;
1236 pf->rcptto = pf_template[i].rcptto;
1237 pf->writehdr = pf_template[i].writehdr;
1238 pf->localcopy = pf_template[i].localcopy;
1239 pf->extdata = NULL; /* unused */
1240 pf->next = pf + 1;
1242 switch(pf->type){
1243 case FreeText:
1244 switch(i){
1245 case N_AUTHRCVD:
1246 sending_order[0] = pf;
1247 break;
1249 case N_NEWS:
1250 pf->text = &outgoing->newsgroups;
1251 sending_order[1] = pf;
1252 break;
1254 case N_DATE:
1255 pf->text = (char **) &outgoing->date;
1256 sending_order[2] = pf;
1257 break;
1259 case N_INREPLY:
1260 pf->text = &outgoing->in_reply_to;
1261 sending_order[NN+9] = pf;
1262 break;
1264 case N_MSGID:
1265 pf->text = &outgoing->message_id;
1266 sending_order[NN+10] = pf;
1267 break;
1269 case N_REF: /* won't be used here */
1270 sending_order[NN+11] = pf;
1271 break;
1273 case N_PRIORITY:
1274 sending_order[NN+12] = pf;
1275 break;
1277 case N_USERAGENT:
1278 pf->text = &pf->textbuf;
1279 pf->textbuf = generate_user_agent();
1280 sending_order[NN+13] = pf;
1281 break;
1283 case N_POSTERR: /* won't be used here */
1284 sending_order[NN+14] = pf;
1285 break;
1287 case N_RPLUID: /* won't be used here */
1288 sending_order[NN+15] = pf;
1289 break;
1291 case N_RPLMBOX: /* won't be used here */
1292 sending_order[NN+16] = pf;
1293 break;
1295 case N_SMTP: /* won't be used here */
1296 sending_order[NN+17] = pf;
1297 break;
1299 case N_NNTP: /* won't be used here */
1300 sending_order[NN+18] = pf;
1301 break;
1303 case N_CURPOS: /* won't be used here */
1304 sending_order[NN+19] = pf;
1305 break;
1307 case N_OURREPLYTO: /* won't be used here */
1308 sending_order[NN+20] = pf;
1309 break;
1311 case N_OURHDRS: /* won't be used here */
1312 sending_order[NN+21] = pf;
1313 break;
1315 default:
1316 q_status_message1(SM_ORDER,3,3,
1317 "Internal error: 1)FreeText header %s", comatose(i));
1318 break;
1321 break;
1323 case Attachment:
1324 break;
1326 case Address:
1327 switch(i){
1328 case N_FROM:
1329 sending_order[3] = pf;
1330 pf->addr = &outgoing->from;
1331 break;
1333 case N_TO:
1334 sending_order[NN+2] = pf;
1335 pf->addr = &outgoing->to;
1336 if(tobufpp)
1337 (*tobufpp) = &pf->scratch;
1339 break;
1341 case N_CC:
1342 sending_order[NN+3] = pf;
1343 pf->addr = &outgoing->cc;
1344 break;
1346 case N_BCC:
1347 sending_order[NN+4] = pf;
1348 pf->addr = &outgoing->bcc;
1349 break;
1351 case N_REPLYTO:
1352 sending_order[NN+1] = pf;
1353 pf->addr = &outgoing->reply_to;
1354 break;
1356 case N_LCC: /* won't be used here */
1357 sending_order[NN+7] = pf;
1358 break;
1360 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1361 case N_SENDER:
1362 sending_order[4] = pf;
1363 pf->addr = &outgoing->sender;
1364 break;
1365 #endif
1367 case N_NOBODY: /* won't be used here */
1368 sending_order[NN+5] = pf;
1369 break;
1371 default:
1372 q_status_message1(SM_ORDER,3,3,
1373 "Internal error: Address header %s", comatose(i));
1374 break;
1376 break;
1378 case Fcc:
1379 sending_order[NN+8] = pf;
1380 pf->text = fccp;
1381 break;
1383 case Subject:
1384 sending_order[NN+6] = pf;
1385 pf->text = &outgoing->subject;
1386 break;
1388 default:
1389 q_status_message1(SM_ORDER,3,3,
1390 "Unknown header type %d in pine_new_send", (void *)pf->type);
1391 break;
1395 if(((--pf)->next = custom) != NULL){
1396 i--;
1399 * NOTE: "i" is assumed to now index first custom field in sending
1400 * order.
1402 for(pf = pf->next; pf && pf->name; pf = pf->next){
1403 if(pf->standard)
1404 continue;
1406 pf->canedit = 1;
1407 pf->rcptto = 0;
1408 pf->writehdr = 1;
1409 pf->localcopy = 1;
1411 switch(pf->type){
1412 case Address:
1413 if(pf->addr){ /* better be set */
1414 char *addr = NULL;
1415 BuildTo bldto;
1417 bldto.type = Str;
1418 bldto.arg.str = pf->textbuf;
1420 sending_order[i++] = pf;
1421 /* change default text into an ADDRESS */
1422 /* strip quotes around whole default */
1423 removing_trailing_white_space(pf->textbuf);
1424 (void)removing_double_quotes(pf->textbuf);
1425 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1426 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1427 fs_give((void **)&addr);
1428 if(pf->textbuf)
1429 fs_give((void **)&pf->textbuf);
1432 break;
1434 case FreeText:
1435 sending_order[i++] = pf;
1436 pf->text = &pf->textbuf;
1437 break;
1439 default:
1440 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1441 (void *)pf->type);
1442 break;
1448 return(header);
1452 void
1453 pine_free_env(METAENV **menv)
1455 int cnt;
1458 if((*menv)->local){
1459 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1462 for(; cnt >= 0; cnt--){
1463 if((*menv)->local[cnt].textbuf)
1464 fs_give((void **) &(*menv)->local[cnt].textbuf);
1466 fs_give((void **) &(*menv)->local[cnt].name);
1469 fs_give((void **) &(*menv)->local);
1472 if((*menv)->sending_order)
1473 fs_give((void **) &(*menv)->sending_order);
1475 fs_give((void **) menv);
1479 /*----------------------------------------------------------------------
1480 Check for addresses the user is not permitted to send to, or probably
1481 doesn't want to send to
1483 Returns: 0 if OK
1484 1 if there are only empty groups
1485 -1 if the message shouldn't be sent
1487 Queues a message indicating what happened
1488 ---*/
1490 check_addresses(METAENV *header)
1492 PINEFIELD *pf;
1493 ADDRESS *a;
1494 int send_daemon = 0, rv = CA_EMPTY;
1496 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1497 for(pf = header->local; pf && pf->name; pf = pf->next)
1498 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1499 for(a = *pf->addr; a != NULL; a = a->next){
1500 if(a->host && (a->host[0] == '.'
1501 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1502 && a->host[0] == '@'))){
1503 q_status_message2(SM_ORDER, 4, 7,
1504 /* TRANSLATORS: First arg is the address we can't
1505 send to, second arg is "not in addressbook". */
1506 _("Can't send to address %s: %s"),
1507 a->mailbox,
1508 (a->host[0] == '.')
1509 ? a->host
1510 : _("not in addressbook"));
1511 return(CA_BAD);
1513 else if(ps_global->restricted
1514 && !address_is_us(*pf->addr, ps_global)){
1515 q_status_message(SM_ORDER, 3, 3,
1516 "Restricted demo version of Alpine. You may only send mail to yourself");
1517 return(CA_BAD);
1519 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1520 send_daemon = 1;
1521 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1523 else if(a->mailbox && a->host){
1524 rv = CA_OK;
1528 return(rv);
1533 * If this isn't general enough we can modify it. The value passed in
1534 * is expected to be one of the desc settings from the priorities array,
1535 * like "High". The header value is X-Priority: 2 (High)
1536 * or something similar. If value doesn't match any of the values then
1537 * the actual value is used instead.
1539 PINEFIELD *
1540 set_priority_header(METAENV *header, char *value)
1542 PINEFIELD *pf;
1544 for(pf = header->local; pf && pf->name; pf = pf->next)
1545 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1546 break;
1548 if(pf){
1549 if(pf->textbuf)
1550 fs_give((void **) &pf->textbuf);
1552 if(value){
1553 PRIORITY_S *p;
1555 for(p = priorities; p && p->desc; p++)
1556 if(!strcmp(p->desc, value))
1557 break;
1559 if(p && p->desc){
1560 char buf[100];
1562 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1563 pf->textbuf = cpystr(buf);
1565 else
1566 pf->textbuf = cpystr(value);
1569 return pf;
1573 /*----------------------------------------------------------------------
1574 Set answered flags for messages specified by reply structure
1576 Args: reply --
1578 Returns: with appropriate flags set and index cache entries suitably tweeked
1579 ----*/
1580 void
1581 update_answered_flags(REPLY_S *reply)
1583 char *seq = NULL, *p;
1584 long i, ourstream = 0, we_cancel = 0;
1585 MAILSTREAM *stream = NULL;
1587 /* nothing to flip in a pseudo reply */
1588 if(reply && (reply->msgno || reply->uid)){
1589 int j;
1590 MAILSTREAM *m;
1593 * If an established stream will do, use it, else
1594 * build one unless we have an array of msgno's...
1596 * I was just mimicking what was already here. I don't really
1597 * understand why we use strcmp instead of same_stream_and_mailbox().
1598 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1599 * Hubert 2003-07-09
1601 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1602 m = ps_global->s_pool.streams[j];
1603 if(m && reply->mailbox && m->mailbox
1604 && !strcmp(reply->mailbox, m->mailbox))
1605 stream = m;
1608 if(!stream && reply->msgno)
1609 return;
1612 * This is here only for people who ran pine4.42 and are
1613 * processing postponed mail from 4.42 now. Pine4.42 saved the
1614 * original mailbox name in the canonical name's position in
1615 * the postponed-msgs folder so it won't match the canonical
1616 * name from the stream.
1618 if(!stream && (!reply->origmbox ||
1619 (reply->mailbox &&
1620 !strcmp(reply->origmbox, reply->mailbox))))
1621 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1623 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1624 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1625 if(!stream){
1626 if((stream = pine_mail_open(NULL,
1627 reply->origmbox ? reply->origmbox
1628 : reply->mailbox,
1629 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1630 NULL)) != NULL){
1631 ourstream++;
1633 else{
1634 if(we_cancel)
1635 cancel_busy_cue(0);
1637 return;
1641 if(stream->uid_validity == reply->data.uid.validity){
1642 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1643 if(i){
1644 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1645 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1648 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1649 SIZEOF_20KBUF-(p-tmp_20k_buf));
1650 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1653 if(reply->forwarded){
1655 * $Forwarded is a regular keyword so we only try to
1656 * set it if the stream allows keywords.
1657 * We could mess up if the stream has keywords but just
1658 * isn't allowing anymore and $Forwarded already exists,
1659 * but what are the odds?
1661 if(stream && stream->kwd_create)
1662 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1663 FORWARDED_FLAG,
1664 ST_SET | ((reply->uid) ? ST_UID : 0L));
1666 else
1667 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1668 "\\ANSWERED",
1669 ST_SET | ((reply->uid) ? ST_UID : 0L));
1671 if(seq)
1672 fs_give((void **)&seq);
1675 if(ourstream)
1676 pine_mail_close(stream); /* clean up dangling stream */
1678 if(we_cancel)
1679 cancel_busy_cue(0);
1685 * phone_home_from - make phone home request's from address IMpersonal.
1686 * Doesn't include user's personal name.
1688 ADDRESS *
1689 phone_home_from(void)
1691 ADDRESS *addr = mail_newaddr();
1692 char tmp[64];
1694 /* garble up mailbox name */
1695 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1696 tmp[sizeof(tmp)-1] = '\0';
1697 addr->mailbox = cpystr(tmp);
1698 addr->host = cpystr(ps_global->maildomain);
1699 return(addr);
1704 * one-way-hash a username into an 8-digit decimal number
1706 * Corey Satten, corey@cac.washington.edu, 7/15/98
1708 unsigned int
1709 phone_home_hash(char *s)
1711 unsigned int h;
1713 for (h=0; *s; ++s) {
1714 if (h & 1)
1715 h = (h>>1) | (PH_MAXHASH/2);
1716 else
1717 h = (h>>1);
1719 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1722 return (h);
1726 /*----------------------------------------------------------------------
1727 Call the mailer, SMTP, sendmail or whatever
1729 Args: header -- full header (envelope and local parts) of message to send
1730 body -- The full body of the message including text
1731 alt_smtp_servers --
1732 verbosefile -- non-null means caller wants verbose interaction and the resulting
1733 output file name to be returned
1735 Returns: -1 if failed, 1 if succeeded
1736 ----*/
1738 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1739 int flags, void (*bigresult_f)(char *, int),
1740 void (*pipecb_f)(PIPE_S *, int, void *))
1742 char error_buf[200], *error_mess = NULL, *postcmd;
1743 ADDRESS *a;
1744 ENVELOPE *fake_env = NULL;
1745 int addr_error_count, we_cancel = 0;
1746 long smtp_opts = 0L;
1747 char *verbose_file = NULL;
1748 BODY *bp = NULL;
1749 PINEFIELD *pf;
1750 BODY *origBody = body;
1752 dprint((4, "Sending mail...\n"));
1754 /* Check for any recipients */
1755 for(pf = header->local; pf && pf->name; pf = pf->next)
1756 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1757 break;
1759 if(!pf){
1760 q_status_message(SM_ORDER,3,3,
1761 _("Can't send message. No recipients specified!"));
1762 return(0);
1765 #ifdef SMIME
1766 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1767 int result;
1769 STORE_S *so = lmc.so;
1770 lmc.so = NULL;
1772 result = 1;
1774 if(ps_global->smime->do_sign){
1775 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1776 result = sign_outgoing_message(header, &body, 0, &bp);
1779 /* need to free new body from encrypt if sign fails? */
1780 if(result && ps_global->smime->do_encrypt)
1781 result = encrypt_outgoing_message(header, &body);
1783 lmc.so = so;
1785 if(!result)
1786 return 0;
1788 #endif
1790 /* set up counts and such to keep track sent percentage */
1791 send_bytes_sent = 0;
1792 gf_filter_init(); /* zero piped byte count, 'n */
1793 send_bytes_to_send = send_body_size(body); /* count body bytes */
1794 ps_global->c_client_error[0] = error_buf[0] = '\0';
1795 we_cancel = busy_cue(_("Sending mail"),
1796 send_bytes_to_send ? sent_percent : NULL, 0);
1798 #ifndef _WINDOWS
1800 /* try posting via local "<mta> <-t>" if specified */
1801 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1802 if(error_buf[0])
1803 error_mess = error_buf;
1805 goto done;
1808 #endif
1811 * If the user's asked for it, and we find that the first text
1812 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1814 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1815 smtp_opts |= SOP_8BITMIME;
1817 #ifdef DEBUG
1818 #ifndef DEBUGJOURNAL
1819 if(debug > 5 || (flags & CM_VERBOSE))
1820 #endif
1821 smtp_opts |= SOP_DEBUG;
1822 #endif
1824 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1825 smtp_opts |= SOP_DSN;
1826 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1827 if(flags & CM_DSN_DELAY)
1828 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1829 if(flags & CM_DSN_SUCCESS)
1830 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1833 * If it isn't Never, then we're always going to let them
1834 * know about failures. This means we don't allow for the
1835 * possibility of setting delay or success without failure.
1837 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1839 if(flags & CM_DSN_FULL)
1840 smtp_opts |= SOP_DSN_RETURN_FULL;
1846 * Set global header pointer so post_rfc822_output can get at it when
1847 * it's called back from c-client's sending routine...
1849 send_header = header;
1852 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1853 * The purpose is to give smtp_mail the list for SMTP RCPT when
1854 * there are recipients in pine's METAENV that are outside c-client's
1855 * envelope.
1857 * NOTE: If there aren't any, don't bother. Dealt with it below.
1859 for(pf = header->local; pf && pf->name; pf = pf->next)
1860 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1861 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1862 || *pf->addr == header->env->bcc))
1863 break;
1865 if(pf && pf->name){
1866 ADDRESS **tail;
1868 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1869 memset(fake_env, 0, sizeof(ENVELOPE));
1870 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1871 tail = &(fake_env->to);
1872 for(pf = header->local; pf && pf->name; pf = pf->next)
1873 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1874 *tail = rfc822_cpy_adr(*pf->addr);
1875 while(*tail)
1876 tail = &((*tail)->next);
1881 * Install our rfc822 output routine
1883 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1884 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1887 * Allow for verbose posting
1889 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1890 (void *) pine_smtp_verbose_out);
1893 * We do this because we want mm_log to put the error message into
1894 * c_client_error instead of showing it itself.
1896 ps_global->noshow_error = 1;
1899 * OK, who posts what? We tried an mta_handoff above, but there
1900 * was either none specified or we decided not to use it. So,
1901 * if there's an smtp-server defined anywhere,
1903 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1904 /*---------- SMTP ----------*/
1905 dprint((4, "call_mailer: via TCP (%s)\n",
1906 alt_smtp_servers[0]));
1907 TIME_STAMP("smtp-open start (tcp)", 1);
1908 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1910 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1911 && ps_global->VAR_SMTP_SERVER[0][0]){
1912 /*---------- SMTP ----------*/
1913 dprint((4, "call_mailer: via TCP\n"));
1914 TIME_STAMP("smtp-open start (tcp)", 1);
1915 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1917 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1918 char *cmdlist[2];
1920 /*----- Send via LOCAL SMTP agent ------*/
1921 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1923 TIME_STAMP("smtp-open start (pipe)", 1);
1924 fs_give((void **) &postcmd);
1925 cmdlist[0] = "localhost";
1926 cmdlist[1] = NULL;
1927 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1928 SMTPTCPPORT, smtp_opts);
1929 /* BUG: should provide separate stderr output! */
1932 ps_global->noshow_error = 0;
1934 TIME_STAMP("smtp open", 1);
1935 if(sending_stream){
1936 unsigned short save_encoding, added_encoding;
1938 dprint((1, "Opened SMTP server \"%s\"\n",
1939 net_host(sending_stream->netstream)
1940 ? net_host(sending_stream->netstream) : "?"));
1942 if(flags & CM_VERBOSE){
1943 TIME_STAMP("verbose start", 1);
1944 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1945 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1946 if(!smtp_verbose(sending_stream)){
1947 snprintf(error_mess = error_buf, sizeof(error_buf),
1948 "Mail not sent. VERBOSE mode error%s%.50s.",
1949 (sending_stream && sending_stream->reply)
1950 ? ": ": "",
1951 (sending_stream && sending_stream->reply)
1952 ? sending_stream->reply : "");
1953 error_buf[sizeof(error_buf)-1] = '\0';
1956 else{
1957 our_unlink(verbose_file);
1958 strncpy(error_mess = error_buf,
1959 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1960 error_buf[sizeof(error_buf)-1] = '\0';
1963 else{
1964 strncpy(error_mess = error_buf,
1965 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1966 error_buf[sizeof(error_buf)-1] = '\0';
1969 TIME_STAMP("verbose end", 1);
1973 * Before we actually send data, see if we have to protect
1974 * the first text body part from getting encoded. We protect
1975 * it from getting encoded in "pine_rfc822_output_body" by
1976 * temporarily inventing a synonym for ENC8BIT...
1977 * This works like so:
1978 * Suppose bp->encoding is set to ENC8BIT.
1979 * We change that here to some unused value (added_encoding) and
1980 * set body_encodings[added_encoding] to "8BIT".
1981 * Then post_rfc822_output is called which calls
1982 * pine_rfc822_output_body. Inside that routine
1983 * pine_write_body_header writes out the encoding for the
1984 * part. Normally it would see encoding == ENC8BIT and it would
1985 * change that to QUOTED-PRINTABLE, but since encoding has been
1986 * set to added_encoding it uses body_encodings[added_encoding]
1987 * which is "8BIT" instead. Then the actual body is written by
1988 * pine_write_body_header which does not do the gf_8bit_qp
1989 * filtering because encoding != ENC8BIT (instead it's equal
1990 * to added_encoding).
1992 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1993 && sending_stream->protocol.esmtp.eightbit.want){
1994 int i;
1996 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
1999 if(i > ENCMAX){ /* no empty encoding slots! */
2000 bp = NULL;
2002 else {
2003 added_encoding = i;
2004 body_encodings[added_encoding] = body_encodings[ENC8BIT];
2005 save_encoding = bp->encoding;
2006 bp->encoding = added_encoding;
2007 #ifdef SMIME
2008 if(ps_global->smime && ps_global->smime->do_sign
2009 && body->nested.part->next
2010 && body->nested.part->next->body.contents.text.data
2011 && body->nested.part->next->body.mime.text.data){
2012 STORE_S *so;
2014 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
2015 so_give(&so);
2016 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
2017 body->nested.part->next->body.mime.text.data = NULL;
2019 #endif /* SMIME */
2023 if(sending_stream->protocol.esmtp.ok
2024 && sending_stream->protocol.esmtp.dsn.want
2025 && !sending_stream->protocol.esmtp.dsn.ok)
2026 q_status_message(SM_ORDER,3,3,
2027 _("Delivery Status Notification not available from this server."));
2029 TIME_STAMP("smtp start", 1);
2030 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2031 fake_env ? fake_env : header->env, body)){
2033 snprintf(error_buf, sizeof(error_buf),
2034 _("Mail not sent. Sending error%s%s"),
2035 (sending_stream && sending_stream->reply) ? ": ": ".",
2036 (sending_stream && sending_stream->reply)
2037 ? sending_stream->reply : "");
2038 error_buf[sizeof(error_buf)-1] = '\0';
2039 dprint((1, error_buf));
2040 addr_error_count = 0;
2041 if(fake_env){
2042 for(a = fake_env->to; a != NULL; a = a->next)
2043 if(a->error != NULL){
2044 if(addr_error_count++ < MAX_ADDR_ERROR){
2047 * Too complicated to figure out which header line
2048 * has the error in the fake_env case, so just
2049 * leave cursor at default.
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));
2065 else{
2066 for(pf = header->local; pf && pf->name; pf = pf->next)
2067 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2068 for(a = *pf->addr; a != NULL; a = a->next)
2069 if(a->error != NULL){
2070 if(addr_error_count++ < MAX_ADDR_ERROR){
2072 if(error_mess) /* previous error? */
2073 q_status_message(SM_ORDER, 4, 7, error_mess);
2075 error_mess = tidy_smtp_mess(a->error,
2076 _("Mail not sent: %.80s"),
2077 error_buf, sizeof(error_buf));
2080 dprint((1, "Send Error: \"%s\"\n",
2081 a->error));
2085 if(!error_mess)
2086 error_mess = error_buf;
2089 /* repair modified "body_encodings" array? */
2090 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2091 && sending_stream->protocol.esmtp.eightbit.want){
2092 body_encodings[added_encoding] = NULL;
2093 bp->encoding = save_encoding;
2096 TIME_STAMP("smtp closing", 1);
2097 smtp_close(sending_stream);
2098 sending_stream = NULL;
2099 TIME_STAMP("smtp done", 1);
2101 else if(!error_mess){
2102 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2103 ps_global->c_client_error[0] ? ": " : "",
2104 ps_global->c_client_error);
2105 error_buf[sizeof(error_buf)-1] = '\0';
2108 if(verbose_file){
2109 if(verbose_send_output){
2110 TIME_STAMP("verbose start", 1);
2111 fclose(verbose_send_output);
2112 verbose_send_output = NULL;
2113 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2115 if(bigresult_f)
2116 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2118 TIME_STAMP("verbose end", 1);
2121 fs_give((void **)&verbose_file);
2125 * Restore original 822 emitter...
2127 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2129 if(fake_env)
2130 mail_free_envelope(&fake_env);
2132 done:
2134 #ifdef SMIME
2135 /* Free replacement encrypted body */
2136 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2138 if(body->type == TYPEMULTIPART){
2139 /* Just get rid of first part, it's actually origBody */
2140 void *x = body->nested.part;
2142 body->nested.part = body->nested.part->next;
2144 fs_give(&x);
2147 pine_free_body(&body);
2149 #endif
2151 if(we_cancel)
2152 cancel_busy_cue(0);
2154 TIME_STAMP("call_mailer done", 1);
2155 /*-------- Did message make it ? ----------*/
2156 if(error_mess){
2157 /*---- Error sending mail -----*/
2158 if(lmc.so && !lmc.all_written)
2159 so_give(&lmc.so);
2161 if(error_mess){
2162 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2163 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2166 return(-1);
2168 else{
2169 lmc.all_written = 1;
2170 return(1);
2176 * write_postponed - exported method to write the given message
2177 * to the postponed folder
2180 write_postponed(METAENV *header, struct mail_bodystruct *body)
2182 char **pp, *folder;
2183 int rv = 0, sz;
2184 CONTEXT_S *fcc_cntxt = NULL;
2185 PINEFIELD *pf;
2186 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2188 if(!ps_global->VAR_POSTPONED_FOLDER
2189 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2190 q_status_message(SM_ORDER | SM_DING, 3, 3,
2191 _("No postponed file defined"));
2192 return(-1);
2195 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2197 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2199 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2201 if(lmc.so){
2202 /* BUG: writem sufficient ? */
2203 for(pf = header->local; pf && pf->name; pf = pf->next)
2204 for(pp = writem; *pp; pp++)
2205 if(!strucmp(pf->name, *pp)){
2206 pf->localcopy = 1;
2207 pf->writehdr = 1;
2208 break;
2212 * Work around c-client reply-to bug. C-client will
2213 * return a reply_to in an envelope even if there is
2214 * no reply-to header field. We want to note here whether
2215 * the reply-to is real or not.
2217 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2218 for(pf = header->local; pf; pf = pf->next)
2219 if(!strcmp(pf->name, "Reply-To")){
2220 pf->writehdr = 1;
2221 pf->localcopy = 1;
2222 if(header->env->reply_to)
2223 pf->textbuf = cpystr("Full");
2224 else
2225 pf->textbuf = cpystr("Empty");
2229 * Write the list of custom headers to the
2230 * X-Our-Headers header so that we can recover the
2231 * list in redraft.
2233 sz = 0;
2234 for(pf = header->custom; pf && pf->name; pf = pf->next)
2235 sz += strlen(pf->name) + 1;
2237 if(sz){
2238 int i;
2239 char *pstart, *pend;
2241 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2244 pf->writehdr = 1;
2245 pf->localcopy = 1;
2246 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2247 pf->text = &pf->textbuf;
2248 pf->textbuf[sz] = '\0'; /* tie off overflow */
2249 /* note: "pf" overloaded */
2250 for(pf = header->custom; pf && pf->name; pf = pf->next){
2251 int r = sz - (pend - pstart); /* remaining buffer */
2253 if(r > 0 && r != sz){
2254 r--;
2255 *pend++ = ',';
2258 sstrncpy(&pend, pf->name, r);
2262 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2263 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2264 rv = -1;
2266 so_give(&lmc.so);
2268 else {
2269 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2270 "Can't allocate internal storage: %s ",
2271 error_description(errno));
2272 rv = -1;
2275 fs_give((void **) &folder);
2276 return(rv);
2281 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2283 if(fcc && *fcc){
2284 lmc.all_written = lmc.text_written = 0;
2285 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2286 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2288 else
2289 lmc.so = NULL;
2291 return(TRUE);
2296 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2298 int rv = TRUE;
2300 if(lmc.so){
2301 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2302 char label[50];
2304 strncpy(label, "Fcc", sizeof(label));
2305 label[sizeof(label)-1] = '\0';
2306 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2307 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2308 label[sizeof(label)-1] = '\0';
2311 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2313 else{
2314 rv = FALSE;
2317 so_give(&lmc.so);
2320 return(rv);
2324 /*----------------------------------------------------------------------
2325 Checks to make sure the fcc is available and can be opened
2327 Args: fcc -- the name of the fcc to create. It can't be NULL.
2328 fcc_cntxt -- Returns the context the fcc is in.
2329 force -- supress user option prompt
2331 Returns allocated storage object on success, NULL on failure
2332 ----*/
2333 STORE_S *
2334 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2336 int exists, ok = 0;
2338 ps_global->mm_log_error = 0;
2341 * check for fcc's existance...
2343 TIME_STAMP("open_fcc start", 1);
2344 if(!is_absolute_path(fcc) && context_isambig(fcc)
2345 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2346 int flip_dot = 0;
2349 * Don't want to preclude a user from Fcc'ing a .name'd folder
2351 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2352 flip_dot = 1;
2353 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2357 * We only want to set the "context" if fcc is an ambiguous
2358 * name. Otherwise, our "relativeness" rules for contexts
2359 * (implemented in context.c) might cause the name to be
2360 * interpreted in the wrong context...
2362 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2363 *fcc_cntxt = ps_global->context_list;
2365 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2366 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2367 if(force
2368 || (pith_opt_save_create_prompt
2369 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2371 ps_global->noshow_error = 1;
2373 if(context_create(*fcc_cntxt, NULL, fcc))
2374 ok++;
2376 ps_global->noshow_error = 0;
2378 else
2379 ok--; /* declined! */
2381 else
2382 ok++; /* found! */
2384 if(flip_dot)
2385 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2387 free_folder_list(*fcc_cntxt);
2389 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2390 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2391 ok++;
2393 else{
2394 if(force
2395 || (pith_opt_save_create_prompt
2396 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2398 ps_global->mm_log_error = 0;
2399 ps_global->noshow_error = 1;
2401 ok = pine_mail_create(NULL, fcc) != 0L;
2403 ps_global->noshow_error = 0;
2405 else
2406 ok--; /* declined! */
2410 TIME_STAMP("open_fcc done.", 1);
2411 if(ok > 0){
2412 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2414 else{
2415 int l1, l2, l3, wid, w;
2416 char *errstr, tmp[MAILTMPLEN];
2417 char *s1, *s2;
2419 if(ok == 0){
2420 if(ps_global->mm_log_error){
2421 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2422 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2424 l1 = strlen(s1);
2425 l2 = strlen(s2);
2426 l3 = strlen(ps_global->c_client_error);
2427 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2428 ? ps_global->ttyo->screen_cols : 80;
2429 w = wid - l1 - l2 - 5;
2431 snprintf(errstr = tmp, sizeof(tmp),
2432 "%.99s\"%.*s%.99s\".%.99s",
2434 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2435 ps_global->c_client_error,
2436 (l3 > w) ? "..." : "",
2437 s2);
2438 tmp[sizeof(tmp)-1] = '\0';
2441 else
2442 errstr = _("Fcc creation error. Message NOT sent or copied.");
2444 else
2445 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2447 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2450 return(NULL);
2454 /*----------------------------------------------------------------------
2455 mail_append() the fcc accumulated in temp_storage to proper destination
2457 Args: fcc -- name of folder
2458 fcc_cntxt -- context for folder
2459 temp_storage -- String of file where Fcc has been accumulated
2461 This copies the string of file to the actual folder, which might be IMAP
2462 or a disk folder. The temp_storage is freed after it is written.
2463 An error message is produced if this fails.
2464 ----*/
2466 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2467 MAILSTREAM *stream, char *label, char *flags)
2469 STRING msg;
2470 CONTEXT_S *cntxt;
2471 int we_cancel = 0;
2473 if(!tmp_storage)
2474 return(0);
2476 TIME_STAMP("write_fcc start.", 1);
2477 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2478 if(label && *label){
2479 char msg_buf[80];
2481 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2482 msg_buf[sizeof(msg_buf)-1] = '\0';
2483 strncat(msg_buf, label, sizeof(msg_buf)-10);
2484 we_cancel = busy_cue(msg_buf, NULL, 0);
2486 else
2487 we_cancel = busy_cue(NULL, NULL, 1);
2489 so_seek(tmp_storage, 0L, 0);
2492 * Before changing this note that these lines depend on the
2493 * definition of FCC_SOURCE.
2495 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2496 strlen((char *)so_text(tmp_storage)));
2498 cntxt = fcc_cntxt;
2500 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2501 cancel_busy_cue(-1);
2502 we_cancel = 0;
2504 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2505 "Write to \"%s\" FAILED!!!", fcc);
2506 dprint((1, "ERROR appending %s in \"%s\"",
2507 fcc ? fcc : "?",
2508 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2509 return(0);
2512 if(we_cancel)
2513 cancel_busy_cue(label ? 0 : -1);
2515 dprint((4, "done.\n"));
2516 TIME_STAMP("write_fcc done.", 1);
2517 return(1);
2522 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2523 * contained.
2525 BODY *
2526 first_text_8bit(struct mail_bodystruct *body)
2528 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2529 body = &body->nested.part->body;
2531 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2532 ? body : NULL);
2537 * Build and return the "From:" address for outbound messages from
2538 * global data...
2540 ADDRESS *
2541 generate_from(void)
2543 ADDRESS *addr = mail_newaddr();
2544 if(ps_global->VAR_PERSONAL_NAME){
2545 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2546 removing_leading_and_trailing_white_space(addr->personal);
2547 if(addr->personal[0] == '\0')
2548 fs_give((void **)&addr->personal);
2551 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2552 addr->host = cpystr(ps_global->maildomain);
2553 removing_leading_and_trailing_white_space(addr->mailbox);
2554 removing_leading_and_trailing_white_space(addr->host);
2555 return(addr);
2560 * set_mime_type_by_grope - sniff the given storage object to determine its
2561 * type, subtype, encoding, and charset
2563 * "Type" and "encoding" must be set before calling this routine.
2564 * If "type" is set to something other than TYPEOTHER on entry,
2565 * then that is the "type" we wish to use. Same for "encoding"
2566 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2567 * figure them out here. If "type" is already set, we also
2568 * leave subtype alone. If not, we figure out subtype here.
2569 * There is a chance that we will upgrade encoding to a "higher"
2570 * level. For example, if it comes in as 7BIT we may change
2571 * that to 8BIT if we find a From_ we want to escape.
2572 * We may also set the charset attribute if the type is TEXT.
2574 * NOTE: this is rather inefficient if the store object is a CharStar
2575 * but the win is all types are handled the same
2577 void
2578 set_mime_type_by_grope(struct mail_bodystruct *body)
2580 #define RBUFSZ (8193)
2581 unsigned char *buf, *p, *bol;
2582 register size_t n;
2583 long max_line = 0L,
2584 eight_bit_chars = 0L,
2585 line_so_far = 0L,
2586 len = 0L;
2587 STORE_S *so = (STORE_S *)body->contents.text.data;
2588 unsigned short new_encoding = ENCOTHER;
2589 int we_cancel = 0;
2590 #ifdef ENCODE_FROMS
2591 short froms = 0, dots = 0,
2592 bmap = 0x1, dmap = 0x1;
2593 #endif
2595 we_cancel = busy_cue(NULL, NULL, 1);
2597 buf = (unsigned char *)fs_get(RBUFSZ);
2598 so_seek(so, 0L, 0);
2600 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2603 buf[n] = '\0';
2605 if(n){ /* check first few bytes to look for magic numbers */
2606 if(body->type == TYPEOTHER){
2607 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2608 body->type = TYPEIMAGE;
2609 body->subtype = cpystr("GIF");
2611 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2612 && buf[2] == 0xFF && buf[3] == 0xE0
2613 && !strncmp((char *)&buf[6], "JFIF", 4)){
2614 body->type = TYPEIMAGE;
2615 body->subtype = cpystr("JPEG");
2617 else if((buf[0] == 'M' && buf[1] == 'M')
2618 || (buf[0] == 'I' && buf[1] == 'I')){
2619 body->type = TYPEIMAGE;
2620 body->subtype = cpystr("TIFF");
2622 else if((buf[0] == '%' && buf[1] == '!')
2623 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2624 body->type = TYPEAPPLICATION;
2625 body->subtype = cpystr("PostScript");
2627 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2628 body->type = TYPEAPPLICATION;
2629 body->subtype = cpystr("PDF");
2631 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2632 body->type = TYPEAUDIO;
2633 body->subtype = cpystr("Basic");
2635 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2636 && buf[2] == 0x16 && buf[3] == 0x00){
2637 body->type = TYPEAPPLICATION;
2638 body->subtype = cpystr("APPLEFILE");
2640 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2641 && buf[2] == 0x03 && buf[3] == 0x04){
2642 body->type = TYPEAPPLICATION;
2643 body->subtype = cpystr("ZIP");
2647 * if type was set above, but no encoding specified, go
2648 * ahead and make it BASE64...
2650 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2651 body->encoding = ENCBINARY;
2654 else{
2655 /* PROBLEM !!! */
2656 if(body->type == TYPEOTHER){
2657 body->type = TYPEAPPLICATION;
2658 body->subtype = cpystr("octet-stream");
2659 if(body->encoding == ENCOTHER)
2660 body->encoding = ENCBINARY;
2664 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2665 #if defined(DOS) || defined(OS2) /* for binary file detection */
2666 int lastchar = '\0';
2667 #define BREAKOUT 300 /* a value that a character can't be */
2668 #endif
2670 p = bol = buf;
2671 len = n;
2672 while (n--){
2673 /* Some people don't like quoted-printable caused by leading Froms */
2674 #ifdef ENCODE_FROMS
2675 Find_Froms(froms, dots, bmap, dmap, *p);
2676 #endif
2677 if(*p == '\n'){
2678 max_line = MAX(max_line, line_so_far + p - bol);
2679 bol = NULL; /* clear beginning of line */
2680 line_so_far = 0L; /* clear line count */
2681 #if defined(DOS) || defined(OS2)
2682 /* LF with no CR!! */
2683 if(lastchar != '\r') /* must be non-text data! */
2684 lastchar = BREAKOUT;
2685 #endif
2687 else if(*p & 0x80){
2688 eight_bit_chars++;
2690 else if(!*p){
2691 /* NULL found. Unless we're told otherwise, must be binary */
2692 if(body->type == TYPEOTHER){
2693 body->type = TYPEAPPLICATION;
2694 body->subtype = cpystr("octet-stream");
2698 * The "TYPETEXT" here handles the case that the NULL
2699 * comes from imported text generated by some external
2700 * editor that permits or inserts NULLS. Otherwise,
2701 * assume it's a binary segment...
2703 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2706 * Since we've already set encoding, count this as a
2707 * hi bit char and continue. The reason is that if this
2708 * is text, there may be a high percentage of encoded
2709 * characters, so base64 may get set below...
2711 if(body->type == TYPETEXT)
2712 eight_bit_chars++;
2713 else
2714 break;
2717 #if defined(DOS) || defined(OS2) /* for binary file detection */
2718 if(lastchar != BREAKOUT)
2719 lastchar = *p;
2720 #endif
2722 /* read another buffer in */
2723 if(n == 0){
2724 if(bol)
2725 line_so_far += p - bol;
2727 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2730 len += n;
2731 p = buf;
2733 else
2734 p++;
2737 * If there's no beginning-of-line pointer, then we must
2738 * have seen an end-of-line. Set bol to the start of the
2739 * new line...
2741 if(!bol)
2742 bol = p;
2744 #if defined(DOS) || defined(OS2) /* for binary file detection */
2745 /* either a lone \r or lone \n indicate binary file */
2746 if(lastchar == '\r' || lastchar == BREAKOUT){
2747 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2748 if(body->type == TYPEOTHER){
2749 body->type = TYPEAPPLICATION;
2750 body->subtype = cpystr("octet-stream");
2753 new_encoding = ENCBINARY;
2754 break;
2757 #endif
2761 /* stash away for later */
2762 so_attr(so, "maxline", long2string(max_line));
2764 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2766 * Since the type or encoding aren't set yet, fall thru a
2767 * series of tests to make sure an adequate type and
2768 * encoding are set...
2771 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2772 if(body->type == TYPEOTHER){
2774 * Since the types not set, then we didn't find a NULL.
2775 * If there's no NULL, then this is likely text. However,
2776 * since we can't be *completely* sure, we set it to
2777 * the generic type.
2779 body->type = TYPEAPPLICATION;
2780 body->subtype = cpystr("octet-stream");
2783 if(new_encoding != ENCBINARY)
2785 * As with NULL handling, if we're told it's text,
2786 * qp-encode it, else it gets base 64...
2788 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2791 if(eight_bit_chars == 0L){
2792 if(body->type == TYPEOTHER)
2793 body->type = TYPETEXT;
2795 if(new_encoding == ENCOTHER)
2796 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2798 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2800 * The 30% threshold is based on qp encoded readability
2801 * on non-MIME UA's.
2803 if(body->type == TYPEOTHER)
2804 body->type = TYPETEXT;
2806 if(new_encoding != ENCBINARY)
2807 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2809 else{
2810 if(body->type == TYPEOTHER){
2811 body->type = TYPEAPPLICATION;
2812 body->subtype = cpystr("octet-stream");
2816 * Apply maximal encoding regardless of previous
2817 * setting. This segment's either not text, or is
2818 * unlikely to be readable with > 30% of the
2819 * text encoded anyway, so we might as well save space...
2821 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2825 #ifdef ENCODE_FROMS
2826 /* If there were From_'s at the beginning of a line or standalone dots */
2827 if((froms || dots) && new_encoding != ENCBINARY)
2828 new_encoding = ENC8BIT;
2829 #endif
2831 /* Set the subtype */
2832 if(body->subtype == NULL)
2833 body->subtype = cpystr(rfc822_default_subtype(body->type));
2835 if(body->encoding == ENCOTHER)
2836 body->encoding = new_encoding;
2838 fs_give((void **)&buf);
2840 if(we_cancel)
2841 cancel_busy_cue(-1);
2846 * Call this to set the charset of an attachment we have
2847 * created. If the attachment contains any non-ascii characters
2848 * then we'll set the charset to the passed in charset, otherwise
2849 * we'll make it us-ascii.
2851 void
2852 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2854 unsigned char c;
2855 int can_be_ascii = 1;
2856 STORE_S *so = (STORE_S *)body->contents.text.data;
2857 int we_cancel = 0;
2859 if(!body || body->type != TYPETEXT)
2860 return;
2862 we_cancel = busy_cue(NULL, NULL, 1);
2864 so_seek(so, 0L, 0);
2866 while(can_be_ascii && so_readc(&c, so))
2867 if(!c || c & 0x80)
2868 can_be_ascii--;
2870 if(can_be_ascii)
2871 set_parameter(&body->parameter, "charset", "US-ASCII");
2872 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2873 set_parameter(&body->parameter, "charset", charset);
2874 else{
2876 * Else we don't know. There are non ascii characters but we either
2877 * don't have a charset to set it to or that charset is just us_ascii,
2878 * which is impossible. So we label it unknown. An alternative would
2879 * have been to strip the high bits instead and label it ascii.
2881 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2884 if(we_cancel)
2885 cancel_busy_cue(-1);
2890 * since encoding happens on the way out the door, this is basically
2891 * just needed to handle TYPEMULTIPART
2893 void
2894 pine_encode_body (struct mail_bodystruct *body)
2896 PART *part;
2898 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2899 if (body) switch (body->type) {
2900 char *freethis;
2902 case TYPEMULTIPART: /* multi-part */
2903 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2904 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2906 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2907 (long) getpid ());
2908 tmp[sizeof(tmp)-1] = '\0';
2909 set_parameter(&body->parameter, "boundary", tmp);
2912 if(freethis)
2913 fs_give((void **) &freethis);
2915 part = body->nested.part; /* encode body parts */
2916 do pine_encode_body (&part->body);
2917 while ((part = part->next) != NULL); /* until done */
2918 break;
2920 case TYPETEXT :
2922 * If the part is text we edited, then it is UTF-8.
2923 * The user may be asking us to send it as something else
2924 * or we may want to downconvert to a more-specific characterset.
2925 * Mark it for conversion here so the right MIME header's written.
2926 * Do conversion pine_rfc822_output_body.
2927 * Attachments are left as is.
2929 if(body->contents.text.data
2930 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2931 char *charset, *posting_charset, *lp;
2933 if(!((charset = parameter_val(body->parameter, "charset"))
2934 && !strucmp(charset, UNKNOWN_CHARSET))
2935 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2937 set_parameter(&body->parameter, "charset", posting_charset);
2940 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2941 * and doesn't use anything but ASCII characters.
2942 * Why is it not ENC7BIT already? Because when we set the encoding
2943 * in set_mime_type_by_grope we were groping through UTF-8 text
2944 * not 2022 text. Not only that, but we didn't know at that point
2945 * that it wouldn't stay UTF-8 when we sent it, which would require
2946 * encoding.
2948 if(!strucmp(posting_charset, "iso-2022-jp")
2949 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2950 && strlen(lp) < 4)
2951 body->encoding = ENC7BIT;
2954 if(charset)
2955 fs_give((void **)&charset);
2958 break;
2960 /* case MESSAGE: */ /* here for documentation */
2961 /* Encapsulated messages are always treated as text objects at this point.
2962 This means that you must replace body->contents.msg with
2963 body->contents.text, which probably involves copying
2964 body->contents.msg.text to body->contents.text */
2965 default: /* all else has some encoding */
2967 * but we'll delay encoding it until the message is on the way
2968 * into the mail slot...
2970 break;
2976 * pine_header_line - simple wrapper around c-client call to contain
2977 * repeated code, and to write fcc if required.
2980 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2981 int writehdr, int localcopy)
2983 int ret = 1;
2984 int flags;
2985 int big = 10000;
2986 char *value, *folded = NULL, *cs;
2987 char *converted;
2989 if(!text)
2990 return 1;
2992 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2994 if(converted){
2995 if(cs && !strucmp(cs, "us-ascii"))
2996 value = converted;
2997 else
2998 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
2999 (unsigned char *) converted, cs,
3000 encode_whole_header(field, header));
3002 if(value && value == converted){ /* no encoding was done, have to fold */
3003 int fold_by, len;
3004 char *actual_field;
3006 len = ((header && header->env && header->env->remail)
3007 ? strlen("ReSent-") : 0) +
3008 (field ? strlen(field) : 0) + 2;
3010 actual_field = (char *)fs_get((len+1) * sizeof(char));
3011 snprintf(actual_field, len+1, "%s%s: ",
3012 (header && header->env && header->env->remail) ? "ReSent-" : "",
3013 field ? field : "");
3014 actual_field[len] = '\0';
3017 * We were folding everything except message-id, but that wasn't
3018 * sufficient. Since 822 only allows folding where linear-white-space
3019 * is allowed we'd need a smarter folder than "fold" to do it. So,
3020 * instead of inventing that smarter folder (which would have to
3021 * know 822 syntax)
3023 * We could just alloc space and copy the actual_field followed by
3024 * the value into it, but since that's what fold does anyway we'll
3025 * waste some cpu time and use fold with a big fold parameter.
3027 * We upped the references folding from 75 to 256 because we were
3028 * encountering longer-than-75 message ids, and to break one line
3029 * in references is to break them all.
3031 * Also, some users are adding long text without spaces to the subject
3032 * line (e.g. URLs) so we up that number too. For the moment this is
3033 * going to 512 (a url that is about 6 lines long.)
3035 flags = FLD_CRLF;
3036 if(field && !strucmp("Subject", field)){
3037 fold_by = 75;
3038 flags |= FLD_NEXTSPC;
3040 else if(field && !strucmp("References", field))
3041 fold_by = 256;
3042 else
3043 fold_by = big;
3045 folded = fold(value, fold_by, big, actual_field, " ", flags);
3047 if(actual_field)
3048 fs_give((void **)&actual_field);
3050 else if(value){ /* encoding was done */
3051 RFC822BUFFER rbuf;
3052 size_t ll;
3055 * rfc1522_encode already inserted continuation lines and did
3056 * the necessary folding so we don't have to do it. Let
3057 * rfc822_header_line add the trailing crlf and the resent- if
3058 * necessary. The 20 could actually be a 12.
3060 ll = strlen(field) + strlen(value) + 20;
3061 folded = (char *) fs_get(ll * sizeof(char));
3062 *folded = '\0';
3063 rbuf.f = dummy_soutr;
3064 rbuf.s = NULL;
3065 rbuf.beg = folded;
3066 rbuf.cur = folded;
3067 rbuf.end = folded+ll-1;
3068 rfc822_output_header_line(&rbuf, field,
3069 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3070 *rbuf.cur = '\0';
3073 if(value && folded){
3074 if(writehdr && f)
3075 ret = (*f)(s, folded);
3077 if(ret && localcopy && lmc.so && !lmc.all_written)
3078 ret = so_puts(lmc.so, folded);
3081 if(folded)
3082 fs_give((void **)&folded);
3084 if(converted && converted != text)
3085 fs_give((void **) &converted);
3087 else
3088 ret = 0;
3090 return(ret);
3095 * Do appropriate encoding of text header lines.
3096 * For some field types (those that consist of 822 *text) we just encode
3097 * the whole thing. For structured fields we encode only within comments
3098 * if possible.
3100 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3101 * s -- Source string.
3102 * charset -- Charset to encode with.
3103 * encode_all -- If set, encode the whole string. If not, try to encode
3104 * only within comments if possible.
3106 * Returns S is returned if no encoding is done. D is returned if encoding
3107 * was needed.
3109 char *
3110 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3112 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3113 int in_comment = 0;
3115 if(!s)
3116 return((char *)s);
3118 if(dlen < SIZEOF_20KBUF)
3119 alpine_panic("bad call to encode_header_value");
3121 if(!encode_all){
3123 * We don't have to worry about keeping track of quoted-strings because
3124 * none of these fields which aren't addresses contain quoted-strings.
3125 * We do keep track of escaped parens inside of comments and comment
3126 * nesting.
3128 p = d+7000;
3129 for(q = (char *)s; *q; q++){
3130 switch(*q){
3131 case LPAREN:
3132 if(in_comment++ == 0)
3133 start_of_comment = q;
3135 break;
3137 case RPAREN:
3138 if(--in_comment == 0){
3139 /* encode the comment, excluding the outer parens */
3140 if(p-d < dlen-1)
3141 *p++ = LPAREN;
3143 *q = '\0';
3144 r = rfc1522_encode(d+14000, dlen-14000,
3145 (unsigned char *)start_of_comment+1,
3146 charset);
3147 if(r != start_of_comment+1)
3148 value = d+7000; /* some encoding was done */
3150 start_of_comment = NULL;
3151 if(r)
3152 sstrncpy(&p, r, dlen-1-(p-d));
3154 *q = RPAREN;
3155 if(p-d < dlen-1)
3156 *p++ = *q;
3158 else if(in_comment < 0){
3159 in_comment = 0;
3160 if(p-d < dlen-1)
3161 *p++ = *q;
3164 break;
3166 case BSLASH:
3167 if(!in_comment && *(q+1)){
3168 if(p-d < dlen-2){
3169 *p++ = *q++;
3170 *p++ = *q;
3174 break;
3176 default:
3177 if(!in_comment && p-d < dlen-1)
3178 *p++ = *q;
3180 break;
3184 if(value){
3185 /* Unterminated comment (wasn't really a comment) */
3186 if(start_of_comment)
3187 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3189 *p = '\0';
3194 * We have to check if there is anything that needs to be encoded that
3195 * wasn't in a comment. If there is, we'd better just start over and
3196 * encode the whole thing. So, if no encoding has been done within
3197 * comments, or if encoding is needed both within and outside of
3198 * comments, then we encode the whole thing. Otherwise, go with
3199 * the version that has only comments encoded.
3201 if(!value || rfc1522_encode(d, dlen,
3202 (unsigned char *)value, charset) != value)
3203 return(rfc1522_encode(d, dlen, s, charset));
3204 else{
3205 strncpy(d, value, dlen-1);
3206 d[dlen-1] = '\0';
3207 return(d);
3213 * pine_address_line - write a header field containing addresses,
3214 * one by one (so there's no buffer limit), and
3215 * wrapping where necessary.
3216 * Note: we use c-client functions to properly build the text string,
3217 * but have to screw around with pointers to fool c-client functions
3218 * into not blatting all the text into a single buffer. Yeah, I know.
3221 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3222 soutr_t f, void *s, int writehdr, int localcopy)
3224 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3225 size_t alloced = 0, sz;
3226 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3227 char *converted, *cs;
3228 ADDRESS *atmp;
3229 int i, count;
3230 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3231 RFC822BUFFER rbuf;
3232 static char comma[] = ", ";
3233 static char end_group[] = ";";
3234 #define no_comma (&comma[1])
3236 if(!alist) /* nothing in field! */
3237 return(1);
3239 if(!alist->host && alist->mailbox){ /* c-client group convention */
3240 in_group++;
3241 was_start_of_group++;
3242 /* encode mailbox of group */
3243 mtmp = alist->mailbox;
3244 if(mtmp){
3245 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3246 buftmp[sizeof(buftmp)-1] = '\0';
3247 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3248 if(converted){
3249 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3250 (unsigned char *) converted, cs));
3251 if(converted && converted != buftmp)
3252 fs_give((void **) &converted);
3254 else{
3255 failed++;
3256 goto bail_out;
3260 else
3261 mtmp = NULL;
3263 ptmp = alist->personal; /* remember personal name */
3264 /* make sure personal name is encoded */
3265 if(ptmp){
3266 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3267 buftmp[sizeof(buftmp)-1] = '\0';
3268 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3269 if(converted){
3270 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3271 (unsigned char *) converted, cs));
3272 if(converted && converted != buftmp)
3273 fs_give((void **) &converted);
3275 else{
3276 failed++;
3277 goto bail_out;
3281 atmp = alist->next;
3282 alist->next = NULL; /* digest only first address! */
3284 /* use automatic buffer unless it isn't big enough */
3285 if((alloced = est_size(alist)) > sizeof(tmp)){
3286 tmpptr = (char *)fs_get(alloced);
3287 sz = alloced;
3289 else{
3290 tmpptr = tmp;
3291 sz = sizeof(tmp);
3294 rbuf.f = dummy_soutr;
3295 rbuf.s = NULL;
3296 rbuf.beg = tmpptr;
3297 rbuf.cur = tmpptr;
3298 rbuf.end = tmpptr+sz-1;
3299 rfc822_output_address_line(&rbuf, field,
3300 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3301 *rbuf.cur = '\0';
3303 alist->next = atmp; /* restore pointer to next addr */
3305 if(alist->personal && alist->personal != ptmp)
3306 fs_give((void **) &alist->personal);
3308 alist->personal = ptmp; /* in case it changed, restore name */
3310 if(mtmp){
3311 if(alist->mailbox && alist->mailbox != mtmp)
3312 fs_give((void **) &alist->mailbox);
3314 alist->mailbox = mtmp;
3317 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3318 count -= 2;
3319 tmpptr[count] = '\0';
3323 * If there is no sending_stream and we are writing the Lcc header,
3324 * then we are piping it to sendmail -t which expects it to be a bcc,
3325 * not lcc.
3327 * When we write it to the fcc or postponed (the lmc.so),
3328 * we want it to be lcc, not bcc, so we put it back.
3330 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3331 fix_lcc = 1;
3333 if(writehdr && f && *tmpptr){
3334 if(fix_lcc)
3335 tmpptr[0] = 'b';
3337 failed = !(*f)(s, tmpptr);
3338 if(fix_lcc)
3339 tmpptr[0] = 'L';
3341 if(failed)
3342 goto bail_out;
3345 if(localcopy && lmc.so &&
3346 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3347 goto bail_out;
3349 for(alist = atmp; alist; alist = alist->next){
3350 delim = comma;
3351 /* account for c-client's representation of group names */
3352 if(in_group){
3353 if(!alist->host){ /* end of group */
3354 in_group = 0;
3355 was_start_of_group = 0;
3357 * Rfc822_write_address no longer writes out the end of group
3358 * unless the whole group address is passed to it, so we do
3359 * it ourselves.
3361 delim = end_group;
3363 else if(!localcopy || !lmc.so || lmc.all_written)
3364 continue;
3366 /* start of new group, print phrase below */
3367 else if(!alist->host && alist->mailbox){
3368 in_group++;
3369 was_start_of_group++;
3372 /* no comma before first address in group syntax */
3373 if(was_start_of_group && alist->host){
3374 delim = no_comma;
3375 was_start_of_group = 0;
3378 /* write delimiter */
3379 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3380 || (localcopy && lmc.so && !lmc.all_written
3381 && !so_puts(lmc.so, delim)))
3382 goto bail_out;
3384 ptmp = alist->personal; /* remember personal name */
3385 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3386 buftmp[sizeof(buftmp)-1] = '\0';
3387 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3388 if(converted){
3389 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3390 (unsigned char *) converted, cs));
3391 if(converted && converted != buftmp)
3392 fs_give((void **) &converted);
3394 else{
3395 failed++;
3396 goto bail_out;
3399 atmp = alist->next;
3400 alist->next = NULL; /* tie off linked list */
3401 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3402 alloced = i;
3403 sz = alloced;
3404 fs_resize((void **)&tmpptr, alloced);
3407 *tmpptr = '\0';
3408 /* make sure we don't write out group end with rfc822_write_address */
3409 if(alist->host || alist->mailbox){
3410 rbuf.f = dummy_soutr;
3411 rbuf.s = NULL;
3412 rbuf.beg = tmpptr;
3413 rbuf.cur = tmpptr;
3414 rbuf.end = tmpptr+sz-1;
3415 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3416 *rbuf.cur = '\0';
3419 alist->next = atmp; /* restore next pointer */
3421 if(alist->personal && alist->personal != ptmp)
3422 fs_give((void **) &alist->personal);
3424 alist->personal = ptmp; /* in case it changed, restore name */
3427 * BUG
3428 * With group syntax addresses we no longer have two identical
3429 * streams of output. Instead, for the fcc/postpone copy we include
3430 * all of the addresses inside the :; of the group, and for the
3431 * mail we're sending we don't include them. That means we aren't
3432 * correctly keeping track of the column to wrap in, below. That is,
3433 * we are keeping track of the fcc copy but we aren't keeping track
3434 * of the regular copy. It could result in too long or too short
3435 * lines. Should almost never come up since group addresses are almost
3436 * never followed by other addresses in the same header, and even
3437 * when they are, you have to go out of your way to get the headers
3438 * messed up.
3440 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3441 count = i + 4;
3442 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3443 || (localcopy && lmc.so && !lmc.all_written &&
3444 !so_puts(lmc.so, "\015\012 ")))
3445 goto bail_out;
3447 else
3448 count += i + 2;
3450 if(((!in_group || was_start_of_group)
3451 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3452 || (localcopy && lmc.so && !lmc.all_written
3453 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3454 goto bail_out;
3457 bail_out:
3458 if(tmpptr && tmpptr != tmp)
3459 fs_give((void **)&tmpptr);
3461 if(failed)
3462 return(0);
3464 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3465 && ((localcopy && lmc.so
3466 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3471 * mutated pine version of c-client's rfc822_header() function.
3472 * changed to call pine-wrapped header and address functions
3473 * so we don't have to limit the header size to a fixed buffer.
3474 * This function also calls pine's body_header write function
3475 * because encoding is delayed until output_body() is called.
3477 long
3478 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3480 PINEFIELD *pf;
3481 int j;
3483 if(header->env->remail){ /* if remailing */
3484 long i = strlen (header->env->remail);
3485 if(i > 4 && header->env->remail[i-4] == '\015')
3486 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3488 if((f && !(*f)(s, header->env->remail))
3489 || (lmc.so && !lmc.all_written
3490 && !so_puts(lmc.so, header->env->remail)))
3491 return(0L); /* start with remail header */
3494 j = 0;
3495 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3496 switch(pf->type){
3498 * Warning: This is confusing. The 2nd to last argument used to
3499 * be just pf->writehdr. We want Bcc lines to be written out
3500 * if we are handing off to a sendmail temp file but not if we
3501 * are talking smtp, so bcc's writehdr is set to 0 and
3502 * pine_address_line was sending if writehdr OR !sending_stream.
3503 * That works as long as we want to write everything when
3504 * !sending_stream (an mta handoff to sendmail). But then we
3505 * added the undisclosed recipients line which should only get
3506 * written if writehdr is set, and not when we pass to a
3507 * sendmail temp file. So pine_address_line has been changed
3508 * so it bases its decision solely on the writehdr passed to it,
3509 * and the logic that worries about Bcc and sending_stream
3510 * was moved up to the caller (here) to decide when to set it.
3512 * So we have:
3513 * undisclosed recipients:; This will just be written
3514 * if writehdr was set and not
3515 * otherwise, nothing magical.
3516 *** We may want to change this, because sendmail -t doesn't handle
3517 *** the empty group syntax well unless it has been configured to
3518 *** do so. It isn't configured by default, or in any of the
3519 *** sendmail v8 configs. So we may want to not write this line
3520 *** if we're doing an mta_handoff (!sending_stream).
3522 * !sending_stream (which means a handoff to a sendmail -t)
3523 * bcc or lcc both set the arg so they'll get written
3524 * (There is also Lcc hocus pocus in pine_address_line
3525 * which converts the Lcc: to Bcc: for sendmail
3526 * processing.)
3527 * sending_stream (which means an smtp handoff)
3528 * bcc and lcc will never have writehdr set, so
3529 * will never be written (They both do have rcptto set,
3530 * so they both do cause RCPT TO commands.)
3532 * The localcopy is independent of sending_stream and is just
3533 * written if it is set for all of these.
3535 case Address:
3536 if(!pine_address_line(pf->name,
3537 header,
3538 pf->addr ? *pf->addr : NULL,
3541 (!strucmp("bcc",pf->name ? pf->name : "")
3542 || !strucmp("Lcc",pf->name ? pf->name : ""))
3543 ? !sending_stream
3544 : pf->writehdr,
3545 pf->localcopy))
3546 return(0L);
3548 break;
3550 case Fcc:
3551 case FreeText:
3552 case Subject:
3553 if(!pine_header_line(pf->name, header,
3554 pf->text ? *pf->text : NULL,
3555 f, s, pf->writehdr, pf->localcopy))
3556 return(0L);
3558 break;
3560 default:
3561 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3562 pf->name);
3563 break;
3568 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3570 * Add comforting "X-" header line indicating what sort of
3571 * authenticity the receiver can expect...
3573 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3574 NETMBX netmbox;
3575 char sstring[MAILTMPLEN], *label; /* place to write */
3576 MAILSTREAM *m;
3577 int i, anonymous = 1;
3579 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3580 m = ps_global->s_pool.streams[i];
3581 if(m && sp_flagged(m, SP_LOCKED)
3582 && mail_valid_net_parse(m->mailbox, &netmbox)
3583 && !netmbox.anoflag)
3584 anonymous = 0;
3587 if(!anonymous){
3588 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3589 *user = (*netmbox.user)
3590 ? netmbox.user
3591 : cached_user_name(netmbox.mailbox);
3592 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3593 isdigit((unsigned char)last_char) ? "[" : "",
3594 netmbox.host,
3595 isdigit((unsigned char) last_char) ? "]" : "");
3596 sstring[sizeof(sstring)-1] = '\0';
3597 label = "X-X-Sender"; /* Jeez. */
3598 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3599 label += 4;
3601 else{
3602 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3603 sstring[sizeof(sstring)-1] = '\0';
3604 label = "X-Warning";
3607 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3608 return(0L);
3610 #endif
3612 if(body && !header->env->remail){ /* not if remail or no body */
3613 if((f && !(*f)(s, MIME_VER))
3614 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3615 || !pine_write_body_header(body, f, s))
3616 return(0L);
3618 else{ /* write terminating newline */
3619 if((f && !(*f)(s, "\015\012"))
3620 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3621 return(0L);
3624 return(1L);
3629 * pine_rfc822_output - pine's version of c-client call. Necessary here
3630 * since we're not using its structures as intended!
3632 long
3633 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3635 int we_cancel = 0;
3636 long retval;
3638 dprint((4, "-- pine_rfc822_output\n"));
3640 we_cancel = busy_cue(NULL, NULL, 1);
3641 pine_encode_body(body); /* encode body as necessary */
3642 /* build and output RFC822 header, output body */
3643 retval = pine_rfc822_header(header, body, f, s)
3644 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3646 if(we_cancel)
3647 cancel_busy_cue(-1);
3649 return(retval);
3654 * post_rfc822_output - cloak for pine's 822 output routine. Since
3655 * we can't pass opaque envelope thru c-client posting
3656 * logic, we need to wrap the real output inside
3657 * something that c-client knows how to call.
3659 long
3660 post_rfc822_output(char *tmp,
3661 ENVELOPE *env,
3662 struct mail_bodystruct *body,
3663 soutr_t f,
3664 void *s,
3665 long int ok8bit)
3667 return(pine_rfc822_output(send_header, body, f, s));
3672 * posting_characterset- determine what transliteration is reasonable
3673 * for posting the given non-ascii messsage data.
3675 * preferred_charset is the charset the original data was labeled in.
3676 * If we can keep that we do.
3678 * Returns: always returns the preferred character set.
3680 char *
3681 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3683 unsigned long *charsetmap = NULL;
3684 unsigned long validbitmap;
3685 static char *ascii = "US-ASCII";
3686 static char *utf8 = "UTF-8";
3687 int notcjk = 0;
3689 if(!ps_global->post_utf8){
3690 validbitmap = 0;
3692 if(mp == HdrText){
3693 char *text = NULL;
3694 UCS *ucs = NULL, *ucsp;
3696 text = (char *) data;
3698 /* convert text in header to UCS characters */
3699 if(text)
3700 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3702 if(!(ucs && *ucs))
3703 return(ascii);
3706 * After the while loop is done the validbitmap has
3707 * a 1 bit for all the character sets that can
3708 * represent all of the characters of this header.
3710 charsetmap = init_charsetchecker(preferred_charset);
3712 if(!charsetmap)
3713 return(utf8);
3715 validbitmap = ~0;
3716 while((validbitmap & ~0x1) && (*ucsp)){
3717 if(*ucsp > 0xffff){
3718 fs_give((void **) &ucs);
3719 return(utf8);
3722 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3725 fs_give((void **) &ucs);
3727 notcjk = validbitmap & 0x1;
3728 validbitmap &= ~0x1;
3730 if(!validbitmap)
3731 return(utf8);
3733 else{
3734 struct mail_bodystruct *body = NULL;
3735 STORE_S *the_text = NULL;
3736 int outchars;
3737 unsigned char c;
3738 UCS ucs;
3739 CBUF_S cbuf;
3741 cbuf.cbuf[0] = '\0';
3742 cbuf.cbufp = cbuf.cbuf;
3743 cbuf.cbufend = cbuf.cbuf;
3745 body = (struct mail_bodystruct *) data;
3747 if(body && body->type == TYPEMULTIPART)
3748 body = &body->nested.part->body;
3750 if(body && body->type == TYPETEXT)
3751 the_text = (STORE_S *) body->contents.text.data;
3753 if(!the_text)
3754 return(ascii);
3756 so_seek(the_text, 0L, 0); /* rewind */
3758 charsetmap = init_charsetchecker(preferred_charset);
3760 if(!charsetmap)
3761 return(utf8);
3763 validbitmap = ~0;
3766 * Read a stream of UTF-8 characters from the_text
3767 * and convert them to UCS-4 characters for the translatable
3768 * test.
3770 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3771 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3772 /* got a ucs character */
3773 if(ucs > 0xffff)
3774 return(utf8);
3776 validbitmap &= charsetmap[(unsigned long) ucs];
3780 notcjk = validbitmap & 0x1;
3781 validbitmap &= ~0x1;
3783 if(!validbitmap)
3784 return(utf8);
3787 /* user chooses something other than UTF-8 */
3788 if(strucmp(ps_global->posting_charmap, utf8)){
3790 * If we're to post in other than UTF-8, and it can be
3791 * transliterated without losing fidelity, do it.
3792 * Else, use UTF-8.
3795 /* if ascii works, always use that */
3796 if(representable_in_charset(validbitmap, ascii))
3797 return(ascii);
3799 /* does the user's posting character set work? */
3800 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3801 return(ps_global->posting_charmap);
3803 /* this is the charset the message we are replying to was in */
3804 if(preferred_charset
3805 && strucmp(preferred_charset, ascii)
3806 && representable_in_charset(validbitmap, preferred_charset))
3807 return(preferred_charset);
3809 /* else, use UTF-8 */
3812 /* user chooses nothing, going with the default */
3813 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3814 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3815 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3816 char *most_preferred;
3819 * In this case the user didn't specify a posting character set
3820 * and we will choose the most-specific one from our list.
3823 /* ascii is best */
3824 if(representable_in_charset(validbitmap, ascii))
3825 return(ascii);
3827 /* Can we keep the original from the message we're replying to? */
3828 if(preferred_charset
3829 && strucmp(preferred_charset, ascii)
3830 && representable_in_charset(validbitmap, preferred_charset))
3831 return(preferred_charset);
3833 /* choose the best of the rest */
3834 most_preferred = most_preferred_charset(validbitmap);
3835 if(!most_preferred)
3836 return(utf8);
3839 * If the text we're labeling contains something like
3840 * smart quotes but no CJK characters, then instead of
3841 * labeling it as ISO-2022-JP we want to use UTF-8.
3843 if(notcjk){
3844 const CHARSET *cs;
3846 cs = utf8_charset(most_preferred);
3847 if(!cs
3848 || cs->script == SC_CHINESE_SIMPLIFIED
3849 || cs->script == SC_CHINESE_TRADITIONAL
3850 || cs->script == SC_JAPANESE
3851 || cs->script == SC_KOREAN)
3852 return(utf8);
3855 return(most_preferred);
3857 /* user explicitly chooses UTF-8 */
3858 else{
3859 /* if ascii works, always use that */
3860 if(representable_in_charset(validbitmap, ascii))
3861 return(ascii);
3863 /* else, use UTF-8 */
3868 return(utf8);
3872 static char **charsetlist = NULL;
3873 static int items_in_charsetlist = 0;
3874 static unsigned long *charsetmap = NULL;
3876 static char *downgrades[] = {
3877 "US-ASCII",
3878 "ISO-8859-15",
3879 "ISO-8859-1",
3880 "ISO-8859-2",
3881 "VISCII",
3882 "KOI8-R",
3883 "KOI8-U",
3884 "ISO-8859-7",
3885 "ISO-8859-6",
3886 "ISO-8859-8",
3887 "TIS-620",
3888 "ISO-2022-JP",
3889 "GB2312",
3890 "BIG5",
3891 "EUC-KR"
3895 unsigned long *
3896 init_charsetchecker(char *preferred_charset)
3898 int i, count = 0, reset = 0;
3899 char *ascii = "US-ASCII";
3900 char *utf8 = "UTF-8";
3903 * When user doesn't set a posting character set posting_charmap ends up
3904 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3905 * That's where the strange set of if-else's come from.
3908 /* user chooses something other than UTF-8 */
3909 if(strucmp(ps_global->posting_charmap, utf8)){
3910 count++; /* US-ASCII */
3911 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3912 reset++;
3914 /* if posting_charmap is valid, include it in list */
3915 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3916 && strucmp(ps_global->posting_charmap, ascii)
3917 && strucmp(ps_global->posting_charmap, utf8)
3918 && utf8_charset(ps_global->posting_charmap)){
3919 count++;
3920 if(!reset
3921 && (items_in_charsetlist < count
3922 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3923 reset++;
3926 if(preferred_charset && preferred_charset[0]
3927 && strucmp(preferred_charset, ascii)
3928 && strucmp(preferred_charset, utf8)
3929 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3930 count++;
3931 if(!reset
3932 && (items_in_charsetlist < count
3933 || strucmp(charsetlist[count-1], preferred_charset)))
3934 reset++;
3937 if(items_in_charsetlist != count)
3938 reset++;
3940 if(reset){
3941 if(charsetlist)
3942 free_list_array(&charsetlist);
3944 items_in_charsetlist = count;
3945 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3947 i = 0;
3948 charsetlist[i++] = cpystr(ascii);
3950 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3951 && strucmp(ps_global->posting_charmap, ascii)
3952 && strucmp(ps_global->posting_charmap, utf8)
3953 && utf8_charset(ps_global->posting_charmap))
3954 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3956 if(preferred_charset && preferred_charset[0]
3957 && strucmp(preferred_charset, ascii)
3958 && strucmp(preferred_charset, utf8)
3959 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3960 charsetlist[i++] = cpystr(preferred_charset);
3962 charsetlist[i] = NULL;
3965 /* user chooses nothing, going with the default */
3966 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3967 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3968 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3969 int add_preferred = 0;
3971 /* does preferred_charset have to be added to the list? */
3972 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3973 add_preferred = 1;
3974 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3975 if(!strucmp(downgrades[i], preferred_charset))
3976 add_preferred = 0;
3979 if(add_preferred){
3980 /* existing list is right size already */
3981 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3982 /* just check to see if last list item is the preferred_charset */
3983 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3984 /* no, fix it */
3985 reset++;
3986 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3987 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3990 else{
3991 reset++;
3992 if(charsetlist)
3993 free_list_array(&charsetlist);
3995 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3996 items_in_charsetlist = count;
3997 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3998 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3999 charsetlist[i] = cpystr(downgrades[i]);
4001 charsetlist[i++] = cpystr(preferred_charset);
4002 charsetlist[i] = NULL;
4005 else{
4006 /* if list is same size as downgrades, consider it good */
4007 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
4008 reset++;
4010 if(reset){
4011 if(charsetlist)
4012 free_list_array(&charsetlist);
4014 count = sizeof(downgrades)/sizeof(downgrades[0]);
4015 items_in_charsetlist = count;
4016 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4017 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4018 charsetlist[i] = cpystr(downgrades[i]);
4020 charsetlist[i] = NULL;
4024 /* user explicitly chooses UTF-8 */
4025 else{
4026 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4027 count++; /* US-ASCII */
4028 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4029 reset++;
4031 if(items_in_charsetlist != count)
4032 reset++;
4034 if(reset){
4035 if(charsetlist)
4036 free_list_array(&charsetlist);
4038 /* the list is just ascii and nothing else */
4039 items_in_charsetlist = count;
4040 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4042 i = 0;
4043 charsetlist[i++] = cpystr(ascii);
4044 charsetlist[i] = NULL;
4049 if(reset){
4050 if(charsetmap)
4051 fs_give((void **) &charsetmap);
4053 if(charsetlist)
4054 charsetmap = utf8_csvalidmap(charsetlist);
4057 return(charsetmap);
4061 /* total reset */
4062 void
4063 free_charsetchecker(void)
4065 if(charsetlist)
4066 free_list_array(&charsetlist);
4068 items_in_charsetlist = 0;
4070 if(charsetmap)
4071 fs_give((void **) &charsetmap);
4076 representable_in_charset(unsigned long validbitmap, char *charset)
4078 int i, done = 0, ret = 0;
4079 unsigned long j;
4081 if(!(charset && charset[0]))
4082 return ret;
4084 if(!strucmp(charset, "UTF-8"))
4085 return 1;
4087 for(i = 0; !done && i < items_in_charsetlist; i++){
4088 if(!strucmp(charset, charsetlist[i])){
4089 j = 1;
4090 j <<= (i+1);
4091 done++;
4092 if(validbitmap & j)
4093 ret = 1;
4097 return ret;
4101 char *
4102 most_preferred_charset(unsigned long validbitmap)
4104 unsigned long bm;
4105 unsigned long rm;
4106 int index;
4108 if(!(validbitmap && items_in_charsetlist > 0))
4109 return("UTF-8");
4111 /* careful, find_rightmost_bit modifies the bitmap */
4112 bm = validbitmap;
4113 rm = find_rightmost_bit(&bm);
4114 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4116 return(charsetlist[index]);
4121 * Set parameter to new value.
4123 void
4124 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4126 PARAMETER *pm;
4128 if(!param || !(paramname && *paramname))
4129 return;
4131 if(*param == NULL){
4132 pm = (*param) = mail_newbody_parameter();
4133 pm->attribute = cpystr(paramname);
4135 else{
4136 int nomatch;
4138 for(pm = *param;
4139 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4140 pm = pm->next)
4141 ;/* searching for paramname parameter */
4143 if(nomatch){ /* add charset parameter */
4144 pm->next = mail_newbody_parameter();
4145 pm = pm->next;
4146 pm->attribute = cpystr(paramname);
4148 /* else pm is existing paramname parameter */
4151 if(pm){
4152 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4153 if(pm->value)
4154 fs_give((void **) &pm->value);
4156 if(new_value)
4157 pm->value = cpystr(new_value);
4163 /*----------------------------------------------------------------------
4164 Remove the leading digits from SMTP error messages
4165 -----*/
4166 char *
4167 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4169 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4170 (*error == '.' && isdigit((unsigned char)*(error+1))))
4171 error++;
4173 snprintf(outbuf, outbuflen, printstring, error);
4174 outbuf[outbuflen-1] = '\0';
4175 return(outbuf);
4180 * Local globals pine's body output routine needs
4182 static soutr_t l_f;
4183 static TCPSTREAM *l_stream;
4184 static unsigned c_in_buf = 0;
4187 * def to make our pipe write's more friendly
4189 #ifdef PIPE_MAX
4190 #if PIPE_MAX > 20000
4191 #undef PIPE_MAX
4192 #endif
4193 #endif
4195 #ifndef PIPE_MAX
4196 #define PIPE_MAX 1024
4197 #endif
4201 * l_flust_net - empties gf_io terminal function's buffer
4204 l_flush_net(int force)
4206 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4207 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4209 tmp_20k_buf[c_in_buf] = '\0';
4210 if(!force){
4212 * The start of each write is expected to be the start of a
4213 * "record" (i.e., a CRLF terminated line). Make sure that is true
4214 * else we might screw up SMTP dot quoting...
4216 for(p = tmp_20k_buf, lp = NULL;
4217 (p = strstr(p, "\015\012")) != NULL;
4218 lp = (p += 2))
4222 if(!lp && c_in_buf > 2) /* no CRLF! */
4223 for(p = &tmp_20k_buf[c_in_buf] - 2;
4224 p > &tmp_20k_buf[0] && *p == '.';
4225 p--) /* find last non-dot */
4228 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4229 /* snippet remains */
4230 c = *lp;
4231 *lp = '\0';
4235 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4236 || (lmc.so && !lmc.all_written
4237 && !(lmc.text_only && lmc.text_written)
4238 && !so_puts(lmc.so, tmp_20k_buf)))
4239 return(0);
4241 c_in_buf = 0;
4242 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4243 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4244 c_in_buf++, lp++;
4247 return(1);
4252 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4256 l_putc(int c)
4258 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4259 tmp_20k_buf[c_in_buf++] = (char) c;
4261 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4267 * pine_rfc822_output_body - pine's version of c-client call. Again,
4268 * necessary since c-client doesn't know about how
4269 * we're treating attachments
4271 long
4272 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4274 STORE_S *bodyso;
4275 PART *part;
4276 PARAMETER *param;
4277 char *t, *cookie = NIL, *encode_error;
4278 char tmp[MAILTMPLEN];
4279 int add_trailing_crlf;
4280 LOC_2022_JP ljp;
4281 gf_io_t gc;
4283 dprint((4, "-- pine_rfc822_output_body: %d\n",
4284 body ? body->type : 0));
4286 bodyso = (STORE_S *) body->contents.text.data;
4288 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4289 part = body->nested.part; /* first body part */
4290 /* find cookie */
4291 for (param = body->parameter; param && !cookie; param = param->next)
4292 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4293 if (!cookie) cookie = "-"; /* yucky default */
4296 * Output a bit of text before the first multipart delimiter
4297 * to warn unsuspecting users of non-mime-aware ua's that
4298 * they should expect weirdness. We do not add this when signing a
4299 * message, though...
4301 #ifdef SMIME
4302 if(ps_global->smime && !ps_global->smime->do_sign)
4303 #endif
4304 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"))
4305 return(0);
4307 do { /* for each part */
4308 /* build cookie */
4309 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4310 tmp[sizeof(tmp)-1] = '\0';
4311 /* append cookie,mini-hdr,contents */
4312 if((f && !(*f)(s, tmp))
4313 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4314 || !pine_write_body_header(&part->body,f,s)
4315 || !pine_rfc822_output_body (&part->body,f,s))
4316 return(0);
4317 } while ((part = part->next) != NULL); /* until done */
4318 /* output trailing cookie */
4319 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4320 tmp[sizeof(tmp)-1] = '\0';
4321 #ifdef SMIME
4322 if(ps_global->smime && ps_global->smime->do_sign
4323 && strlen(tmp) < sizeof(tmp)-2)
4324 strncat(tmp, "\r\n", 2);
4325 #endif
4326 if(lmc.so && !lmc.all_written){
4327 so_puts(lmc.so, t);
4328 so_puts(lmc.so, "\015\012");
4331 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4334 l_f = f; /* set up for writing chars... */
4335 l_stream = s; /* out other end of pipe... */
4336 gf_filter_init();
4337 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4338 body->size.bytes));
4340 if(bodyso)
4341 gf_set_so_readc(&gc, bodyso);
4342 else
4343 return(1);
4346 * Don't add trailing line if it is ExternalText, which already guarantees
4347 * a trailing newline.
4349 add_trailing_crlf = !(bodyso->src == ExternalText);
4351 so_seek(bodyso, 0L, 0);
4353 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4354 char *charset;
4356 if(body->type == TYPETEXT
4357 && so_attr(bodyso, "edited", NULL)
4358 && (charset = parameter_val(body->parameter, "charset"))){
4359 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4360 if(!strucmp(charset, "iso-2022-jp")){
4361 ljp.report_err = 0;
4362 gf_link_filter(gf_line_test,
4363 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4365 else{
4366 void *table = utf8_rmap(charset);
4368 if(table){
4369 gf_link_filter(gf_convert_utf8_charset,
4370 gf_convert_utf8_charset_opt(table,0));
4372 else{
4373 /* else, just send it? */
4374 set_parameter(&body->parameter, "charset", "UTF-8");
4379 fs_give((void **)&charset);
4383 * Convert text pieces to canonical form
4384 * BEFORE applying any encoding (rfc1341: appendix G)...
4385 * NOTE: almost all filters expect CRLF newlines
4387 if(body->type == TYPETEXT
4388 && body->encoding != ENCBASE64
4389 && !so_attr(bodyso, "rawbody", NULL)){
4390 gf_link_filter(gf_local_nvtnl, NULL);
4393 switch (body->encoding) { /* all else needs filtering */
4394 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4395 gf_link_filter(gf_8bit_qp, NULL);
4396 break;
4398 case ENCBINARY: /* encode binary into BASE64 */
4399 gf_link_filter(gf_binary_b64, NULL);
4400 break;
4402 default: /* otherwise text */
4403 break;
4407 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4408 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4409 _("Encoding Error \"%s\""), encode_error);
4410 display_message('x');
4413 gf_clear_so_readc(bodyso);
4415 if(encode_error || !l_flush_net(TRUE))
4416 return(0);
4418 send_bytes_sent += gf_bytes_piped();
4419 so_release((STORE_S *)body->contents.text.data);
4421 if(lmc.so && !lmc.all_written && lmc.text_only){
4422 if(lmc.text_written){ /* we have some splainin' to do */
4423 char tmp[MAILTMPLEN];
4424 char *name = NULL;
4426 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4427 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4428 return(0);
4431 * BUG: If this name is not ascii it's going to cause trouble.
4433 name = parameter_val(body->parameter, "name");
4434 snprintf(tmp, sizeof(tmp),
4435 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4436 body_type_names(body->type),
4437 body->subtype ? body->subtype : "Unknown",
4438 name ? " (Name=\"" : "",
4439 name ? name : "",
4440 name ? "\")" : "",
4441 comatose(body->size.bytes));
4442 tmp[sizeof(tmp)-1] = '\0';
4443 if(name)
4444 fs_give((void **)&name);
4446 if(!so_puts(lmc.so, tmp))
4447 return(0);
4449 else /* suppress everything after first text part */
4450 lmc.text_written = (body->type == TYPETEXT
4451 && (!body->subtype
4452 || !strucmp(body->subtype, "plain")));
4455 if(add_trailing_crlf)
4456 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4457 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4458 else
4459 return(1);
4462 char *
4463 ToLower(char *s, char *t)
4465 int i;
4467 for(i = 0; s != NULL && s[i] != '\0'; i++)
4468 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4469 t[i] = '\0';
4471 return t;
4475 * pine_write_body_header - another c-client clone. This time
4476 * so the final encoding labels get set
4477 * correctly since it hasn't happened yet,
4478 * and to be paranoid about line lengths.
4480 * Returns: TRUE/nonzero on success, zero on error
4483 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4485 char tmp[MAILTMPLEN];
4486 RFC822BUFFER rbuf;
4487 int i;
4488 unsigned char c;
4489 STRINGLIST *stl;
4490 STORE_S *so;
4491 extern const char *tspecials;
4493 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4494 if(!(so_puts(so, "Content-Type: ")
4495 && so_puts(so, ToLower(body_types[body->type], tmp))
4496 && so_puts(so, "/")
4497 && so_puts(so, ToLower(body->subtype
4498 ? body->subtype
4499 : rfc822_default_subtype (body->type),tmp))))
4500 return(pwbh_finish(0, so));
4502 if(body->parameter){
4503 if(!pine_write_params(body->parameter, so))
4504 return(pwbh_finish(0, so));
4506 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4507 return(pwbh_finish(0, so));
4509 if(!so_puts(so, "\015\012"))
4510 return(pwbh_finish(0, so));
4512 if ((body->encoding /* note: encoding 7BIT never output! */
4513 && !(so_puts(so, "Content-Transfer-Encoding: ")
4514 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4515 ? ENCBASE64
4516 : (body->encoding == ENC8BIT)
4517 ? ENCQUOTEDPRINTABLE
4518 : (body->encoding <= ENCMAX)
4519 ? body->encoding
4520 : ENCOTHER])
4521 && so_puts(so, "\015\012")))
4523 * If requested, strip Content-ID headers that don't look like they
4524 * are needed. Microsoft's Outlook XP has a bug that causes it to
4525 * not show that there is an attachment when there is a Content-ID
4526 * header present on that attachment.
4528 * If user has quell-content-id turned on, don't output content-id
4529 * unless it is of type message/external-body.
4530 * Since this code doesn't look inside messages being forwarded
4531 * type message content-ids will remain as is and type multipart
4532 * alternative will remain as is. We don't create those on our
4533 * own. If we did, we'd have to worry about getting this right.
4535 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4536 || (body->type == TYPEMESSAGE
4537 && body->subtype
4538 && !strucmp(body->subtype, "external-body")))
4539 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4540 && so_puts(so, "\015\012")))
4541 || (body->description
4542 && strlen(body->description) < 5000 /* arbitrary! */
4543 && !pine_write_header_line("Content-Description: ", body->description, so))
4544 || (body->md5
4545 && !(so_puts(so, "Content-MD5: ")
4546 && so_puts(so, body->md5)
4547 && so_puts(so, "\015\012"))))
4548 return(pwbh_finish(0, so));
4550 if ((stl = body->language) != NULL) {
4551 if(!so_puts(so, "Content-Language: "))
4552 return(pwbh_finish(0, so));
4554 do {
4555 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4556 return(pwbh_finish(0, so));
4558 tmp[0] = '\0';
4559 rbuf.f = dummy_soutr;
4560 rbuf.s = NULL;
4561 rbuf.beg = tmp;
4562 rbuf.cur = tmp;
4563 rbuf.end = tmp+sizeof(tmp)-1;
4564 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4565 *rbuf.cur = '\0';
4567 if(!so_puts(so, tmp)
4568 || ((stl = stl->next) && !so_puts(so, ", ")))
4569 return(pwbh_finish(0, so));
4571 while (stl);
4573 if(!so_puts(so, "\015\012"))
4574 return(pwbh_finish(0, so));
4577 if (body->disposition.type) {
4578 if(!(so_puts(so, "Content-Disposition: ")
4579 && so_puts(so, body->disposition.type)))
4580 return(pwbh_finish(0, so));
4582 if(!pine_write_params(body->disposition.parameter, so))
4583 return(pwbh_finish(0, so));
4585 if(!so_puts(so, "\015\012"))
4586 return(pwbh_finish(0, so));
4589 /* copy out of so, a line at a time (or less than a K)
4590 * and send it down the pike
4592 so_seek(so, 0L, 0);
4593 i = 0;
4594 while(so_readc(&c, so))
4595 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4596 tmp[i] = '\0';
4597 if((f && !(*f)(s, tmp))
4598 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4599 return(pwbh_finish(0, so));
4601 i = 0;
4604 /* Finally, write blank line */
4605 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4606 return(pwbh_finish(0, so));
4608 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4611 return(0);
4616 * pine_write_header - convert, encode (if needed) and
4617 * write "header-name: field-body"
4620 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4622 char *cv, *cs, *vp;
4623 int rv;
4625 cs = posting_characterset(val, NULL, HdrText);
4626 cv = utf8_to_charset(val, cs, 0);
4627 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4628 (unsigned char *) cv, cs);
4630 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4632 if(cv && cv != val)
4633 fs_give((void **) &cv);
4636 return(rv);
4641 * pine_write_param - convert, encode and write MIME header-field parameters
4644 pine_write_params(PARAMETER *param, STORE_S *so)
4646 for(; param; param = param->next){
4647 int rv;
4648 char *cv, *cs;
4649 extern const char *tspecials;
4651 cs = posting_characterset(param->value, NULL, HdrText);
4652 cv = utf8_to_charset(param->value, cs, 0);
4653 rv = (so_puts(so, "; ")
4654 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4656 if(cv && cv != param->value)
4657 fs_give((void **) &cv);
4659 if(!rv)
4660 return(0);
4663 return(1);
4669 lmc_body_header_line(char *line, int beginning)
4671 if(lmc.so && !lmc.all_written){
4672 if(beginning && lmc.text_only && lmc.text_written
4673 && (!struncmp(line, "content-type:", 13)
4674 || !struncmp(line, "content-transfer-encoding:", 26)
4675 || !struncmp(line, "content-disposition:", 20))){
4677 * "comment out" the real values since our comment isn't
4678 * likely the same type, disposition nor encoding...
4680 if(!so_puts(lmc.so, "X-"))
4681 return(FALSE);
4684 return(so_puts(lmc.so, line));
4687 return(TRUE);
4692 lmc_body_header_finish(void)
4694 if(lmc.so && !lmc.all_written){
4695 if(lmc.text_only && lmc.text_written
4696 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4697 return(FALSE);
4699 return(so_puts(lmc.so, "\015\012"));
4702 return(TRUE);
4708 pwbh_finish(int rv, STORE_S *so)
4710 if(so)
4711 so_give(&so);
4713 return(rv);
4718 * pine_free_body - c-client call wrapper so the body data pointer we
4719 * we're using in a way c-client doesn't know about
4720 * gets free'd appropriately.
4722 void
4723 pine_free_body(struct mail_bodystruct **body)
4726 * Preempt c-client's contents.text.data clean up since we've
4727 * usurped it's meaning for our own purposes...
4729 pine_free_body_data (*body);
4731 /* Then let c-client handle the rest... */
4732 mail_free_body(body);
4737 * pine_free_body_data - free pine's interpretations of the body part's
4738 * data pointer.
4740 void
4741 pine_free_body_data(struct mail_bodystruct *body)
4743 if(body){
4744 if(body->type == TYPEMULTIPART){
4745 PART *part = body->nested.part;
4746 do /* for each part */
4747 pine_free_body_data(&part->body);
4748 while ((part = part->next) != NULL); /* until done */
4750 else if(body->contents.text.data)
4751 so_give((STORE_S **) &body->contents.text.data);
4756 long
4757 send_body_size(struct mail_bodystruct *body)
4759 long l = 0L;
4760 PART *part;
4762 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4763 part = body->nested.part; /* first body part */
4764 do /* for each part */
4765 l += send_body_size(&part->body);
4766 while ((part = part->next) != NULL); /* until done */
4767 return(l);
4770 return(l + body->size.bytes);
4775 sent_percent(void)
4777 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4778 / send_bytes_to_send);
4779 return(MIN(i, 100));
4784 * pine_smtp_verbose_out - write
4786 void
4787 pine_smtp_verbose_out(char *s)
4789 #ifdef _WINDOWS
4790 LPTSTR slpt;
4791 #endif
4792 if(verbose_send_output && s){
4793 char *p, last = '\0';
4795 for(p = s; *p; p++)
4796 if(*p == '\015')
4797 *p = ' ';
4798 else
4799 last = *p;
4801 #ifdef _WINDOWS
4803 * The stream is opened in Unicode mode, so we need to fix the
4804 * argument to fputs.
4806 slpt = utf8_to_lptstr((LPSTR) s);
4807 if(slpt){
4808 _fputts(slpt, verbose_send_output);
4809 fs_give((void **) &slpt);
4812 if(last != '\012')
4813 _fputtc(L'\n', verbose_send_output);
4814 #else
4815 fputs(s, verbose_send_output);
4816 if(last != '\012')
4817 fputc('\n', verbose_send_output);
4818 #endif
4825 * pine_header_forbidden - is this name a "forbidden" header?
4827 * name - the header name to check
4828 * We don't allow user to change these.
4831 pine_header_forbidden(char *name)
4833 char **p;
4834 static char *forbidden_headers[] = {
4835 "sender",
4836 "x-sender",
4837 "x-x-sender",
4838 "date",
4839 "received",
4840 "message-id",
4841 "in-reply-to",
4842 "path",
4843 "resent-message-id",
4844 "resent-date",
4845 "resent-from",
4846 "resent-sender",
4847 "resent-to",
4848 "resent-cc",
4849 "resent-reply-to",
4850 "mime-version",
4851 "content-type",
4852 "x-priority",
4853 "user-agent",
4854 "list-help", /* rfc 2369, section 3 */
4855 "list-unsubscribe",
4856 "list-subscribe",
4857 "list-post",
4858 "list-owner",
4859 "list-archive",
4860 NULL
4863 for(p = forbidden_headers; *p; p++)
4864 if(!strucmp(name, *p))
4865 break;
4867 return((*p) ? 1 : 0);
4872 * hdr_is_in_list - is there a custom value for this header?
4874 * hdr - the header name to check
4875 * custom - the list to check in
4876 * Returns 1 if there is a custom value, 0 otherwise.
4879 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4881 PINEFIELD *pf;
4883 for(pf = custom; pf && pf->name; pf = pf->next)
4884 if(strucmp(pf->name, hdr) == 0)
4885 return 1;
4887 return 0;
4892 * count_custom_hdrs_pf - returns number of custom headers in arg
4893 * custom -- the list to be counted
4894 * only_nonstandard -- only count headers which aren't standard pine headers
4897 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4899 int ret = 0;
4901 for(; custom && custom->name; custom = custom->next)
4902 if(!only_nonstandard || !custom->standard)
4903 ret++;
4905 return(ret);
4910 * count_custom_hdrs_list - returns number of custom headers in arg
4913 count_custom_hdrs_list(char **list)
4915 char **p;
4916 char *q = NULL;
4917 char *name;
4918 char *t;
4919 char save;
4920 int ret = 0;
4922 if(list){
4923 for(p = list; (q = *p) != NULL; p++){
4924 if(q[0]){
4925 /* remove leading whitespace */
4926 name = skip_white_space(q);
4928 /* look for colon or space or end */
4929 for(t = name;
4930 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4931 ;/* do nothing */
4933 save = *t;
4934 *t = '\0';
4935 if(!pine_header_forbidden(name))
4936 ret++;
4938 *t = save;
4943 return(ret);
4948 * set_default_hdrval - put the user's default value for this header
4949 * into pf->textbuf.
4950 * setthis - the pinefield to be set
4951 * custom - where to look for the default
4953 CustomType
4954 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4956 PINEFIELD *pf;
4957 CustomType ret = NoMatch;
4959 if(!setthis || !setthis->name){
4960 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4961 return(ret);
4964 setthis->textbuf = NULL;
4966 for(pf = custom; pf && pf->name; pf = pf->next){
4967 if(strucmp(pf->name, setthis->name) != 0)
4968 continue;
4970 ret = pf->cstmtype;
4972 /* turn on editing */
4973 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4974 setthis->canedit = 1;
4976 if(pf->val)
4977 setthis->textbuf = cpystr(pf->val);
4980 if(!setthis->textbuf)
4981 setthis->textbuf = cpystr("");
4983 return(ret);
4988 * pine_header_standard - is this name a "standard" header?
4990 * name - the header name to check
4992 FieldType
4993 pine_header_standard(char *name)
4995 int i;
4997 /* check to see if this is a standard header */
4998 for(i = 0; pf_template[i].name; i++)
4999 if(!strucmp(name, pf_template[i].name))
5000 return(pf_template[i].type);
5002 return(TypeUnknown);
5007 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
5008 * Allocates space for each name and addr ptr.
5009 * Allocates space for default in textbuf, even if empty.
5011 * head - the first PINEFIELD to fill in
5012 * list - the list to parse
5014 void
5015 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
5017 char **p, *q, *t, *name, *value, save;
5018 PINEFIELD *pf;
5020 pf = head;
5022 if(list){
5023 for(p = list; (q = *p) != NULL; p++){
5025 if(q[0]){
5027 /* anything after leading whitespace? */
5028 if(!*(name = skip_white_space(q)))
5029 continue;
5031 /* look for colon or space or end */
5032 for(t = name;
5033 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5034 ;/* do nothing */
5036 /* if there is a space in the field-name, skip it */
5037 if(isspace((unsigned char)*t)){
5038 q_status_message1(SM_ORDER, 3, 3,
5039 _("Space not allowed in header name (%s)"),
5040 name);
5041 continue;
5044 save = *t;
5045 *t = '\0';
5047 /* Don't allow any of the forbidden headers. */
5048 if(pine_header_forbidden(name)){
5049 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5050 _("Not allowed to change header \"%s\""),
5051 name);
5053 *t = save;
5054 continue;
5057 if(pf){
5058 if(pine_header_standard(name) != TypeUnknown)
5059 pf->standard = 1;
5061 pf->name = cpystr(name);
5062 pf->type = FreeText;
5063 pf->cstmtype = cstmtype;
5064 pf->next = pf+1;
5066 #ifdef OLDWAY
5068 * Some mailers apparently break if we change
5069 * user@domain into Fred <user@domain> for
5070 * return-receipt-to,
5071 * so we'll just call this a FreeText field, too.
5074 * For now, all custom headers are FreeText except for
5075 * this one that we happen to know about. We might
5076 * have to add some syntax to the config option so that
5077 * people can tell us their custom header takes addresses.
5079 if(!strucmp(pf->name, "Return-Receipt-to")){
5080 pf->type = Address;
5081 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5082 *pf->addr = (ADDRESS *)NULL;
5084 #endif /* OLDWAY */
5086 *t = save;
5088 /* remove space between name and colon */
5089 value = skip_white_space(t);
5091 /* give them an alloc'd default, even if empty */
5092 pf->textbuf = cpystr((*value == ':')
5093 ? skip_white_space(++value) : "");
5094 if(pf->textbuf && pf->textbuf[0])
5095 pf->val = cpystr(pf->textbuf);
5097 pf++;
5099 else
5100 *t = save;
5105 /* fix last next pointer */
5106 if(head && pf != head)
5107 (pf-1)->next = NULL;
5112 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5113 * setup except that it doesn't have default values.
5114 * Add those defaults if they exist in "list".
5116 * head - the first PINEFIELD to add a default to
5117 * list - the list to get the defaults from
5119 void
5120 add_defaults_from_list(PINEFIELD *head, char **list)
5122 char **p, *q, *t, *name, *value, save;
5123 PINEFIELD *pf;
5125 for(pf = head; pf && list; pf = pf->next){
5126 if(!pf->name)
5127 continue;
5129 for(p = list; (q = *p) != NULL; p++){
5131 if(q[0]){
5133 /* anything after leading whitespace? */
5134 if(!*(name = skip_white_space(q)))
5135 continue;
5137 /* look for colon or space or end */
5138 for(t = name;
5139 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5140 ;/* do nothing */
5142 /* if there is a space in the field-name, skip it */
5143 if(isspace((unsigned char)*t))
5144 continue;
5146 save = *t;
5147 *t = '\0';
5149 if(strucmp(name, pf->name) != 0){
5150 *t = save;
5151 continue;
5154 *t = save;
5157 * Found the right header. See if it has a default
5158 * value set.
5161 /* remove space between name and colon */
5162 value = skip_white_space(t);
5164 if(*value == ':'){
5165 char *defval;
5167 defval = skip_white_space(++value);
5168 if(defval && *defval){
5169 if(pf->val)
5170 fs_give((void **)&pf->val);
5172 pf->val = cpystr(defval);
5176 break; /* on to next pf */
5184 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5185 * fill in the defaults
5186 * Args - list -- The list to parse.
5187 * cstmtype -- Fill the in cstmtype field with this value
5189 PINEFIELD *
5190 parse_custom_hdrs(char **list, CustomType cstmtype)
5192 PINEFIELD *pfields;
5193 int i;
5196 * add one for possible use by fcc
5197 * What is this "possible use"? I don't see it. I don't
5198 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5200 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5201 pfields = (PINEFIELD *)fs_get((size_t) i);
5202 memset(pfields, 0, (size_t) i);
5204 /* set up the custom header pfields */
5205 customized_hdr_setup(pfields, list, cstmtype);
5207 return(pfields);
5212 * Combine the two lists of headers into one list which is allocated here
5213 * and freed by the caller. Eliminate duplicates with values from the role
5214 * taking precedence over values from the default.
5216 PINEFIELD *
5217 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5219 PINEFIELD *pfields, *pf, *pf2;
5220 int max_hdrs, i;
5222 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5223 count_custom_hdrs_pf(dflthdrs,0);
5225 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5226 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5228 pf = pfields;
5229 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5230 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5231 pf->type = pf2->type;
5232 pf->cstmtype = pf2->cstmtype;
5233 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5234 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5235 pf->standard = pf2->standard;
5236 pf->next = pf+1;
5237 pf++;
5240 /* if these aren't already there, add them */
5241 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5242 /* check for already there */
5243 for(i = 0;
5244 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5245 i++)
5248 if(!pfields[i].name){ /* this is a new one */
5249 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5250 pf->type = pf2->type;
5251 pf->cstmtype = pf2->cstmtype;
5252 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5253 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5254 pf->standard = pf2->standard;
5255 pf->next = pf+1;
5256 pf++;
5260 /* fix last next pointer */
5261 if(pf != pfields)
5262 (pf-1)->next = NULL;
5264 return(pfields);
5269 * free_customs - free misc. resources associated with custom header fields
5271 * pf - pointer to first custom field
5273 void
5274 free_customs(PINEFIELD *head)
5276 PINEFIELD *pf;
5278 for(pf = head; pf && pf->name; pf = pf->next){
5280 fs_give((void **)&pf->name);
5282 if(pf->val)
5283 fs_give((void **)&pf->val);
5285 /* only true for FreeText */
5286 if(pf->textbuf)
5287 fs_give((void **)&pf->textbuf);
5289 /* only true for Address */
5290 if(pf->addr && *pf->addr)
5291 mail_free_address(pf->addr);
5294 fs_give((void **)&head);
5299 * encode_whole_header
5301 * Returns 1 if whole value should be encoded
5302 * 0 to encode only within comments
5305 encode_whole_header(char *field, METAENV *header)
5307 int retval = 0;
5308 PINEFIELD *pf;
5310 if(field && (!strucmp(field, "Subject") ||
5311 !strucmp(field, "Comment") ||
5312 !struncmp(field, "X-", 2)))
5313 retval++;
5314 else if(field && *field && header && header->custom){
5315 for(pf = header->custom; pf && pf->name; pf = pf->next){
5317 if(!pf->standard && !strucmp(pf->name, field)){
5318 retval++;
5319 break;
5324 return(retval);
5328 /*----------------------------------------------------------------------
5329 Post news via NNTP or inews
5331 Args: env -- envelope of message to post
5332 body -- body of message to post
5334 Returns: -1 if failed or cancelled, 1 if succeeded
5336 WARNING: This call function has the side effect of writing the message
5337 to the lmc.so object.
5338 ----*/
5340 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5341 void (*pipecb_f)(PIPE_S *, int, void *))
5343 char *error_mess, error_buf[200], **news_servers;
5344 char **servers_to_use;
5345 int we_cancel = 0, server_no = 0, done_posting = 0;
5346 void *orig_822_output;
5347 BODY *bp = NULL;
5349 error_buf[0] = '\0';
5350 we_cancel = busy_cue("Posting news", NULL, 0);
5352 dprint((4, "Posting: [%s]\n",
5353 (header && header->env && header->env->newsgroups)
5354 ? header->env->newsgroups : "?"));
5356 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5357 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5358 && ps_global->VAR_NNTP_SERVER[0][0])){
5359 /*---------- NNTP server defined ----------*/
5360 error_mess = NULL;
5361 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5362 && alt_nntp_servers[0][0])
5363 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5366 * Install our rfc822 output routine
5368 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5369 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5370 (void *)post_rfc822_output);
5372 server_no = 0;
5373 news_servers = (char **)fs_get(2 * sizeof(char *));
5374 news_servers[1] = NULL;
5375 while(!done_posting && servers_to_use[server_no] &&
5376 servers_to_use[server_no][0]){
5377 news_servers[0] = servers_to_use[server_no];
5378 ps_global->noshow_error = 1;
5379 #ifdef DEBUG
5380 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5381 #else
5382 sending_stream = nntp_open(news_servers, 0L);
5383 #endif
5384 ps_global->noshow_error = 0;
5386 if(sending_stream != NULL) {
5387 unsigned short save_encoding, added_encoding;
5390 * Fake that we've got clearance from the transport agent
5391 * for 8bit transport for the benefit of our output routines...
5393 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5394 && (bp = first_text_8bit(body))){
5395 int i;
5397 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5400 if(i > ENCMAX){ /* no empty encoding slots! */
5401 bp = NULL;
5403 else {
5404 added_encoding = i;
5405 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5406 save_encoding = bp->encoding;
5407 bp->encoding = added_encoding;
5412 * Set global header pointer so we can get at it later...
5414 send_header = header;
5415 ps_global->noshow_error = 1;
5416 if(nntp_mail(sending_stream, header->env, body) == 0)
5417 snprintf(error_mess = error_buf, sizeof(error_buf),
5418 _("Error posting message: %s"),
5419 sending_stream->reply);
5420 else{
5421 done_posting = 1;
5422 error_buf[0] = '\0';
5423 error_mess = NULL;
5426 error_buf[sizeof(error_buf)-1] = '\0';
5427 smtp_close(sending_stream);
5428 ps_global->noshow_error = 0;
5429 sending_stream = NULL;
5430 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5431 body_encodings[added_encoding] = NULL;
5432 bp->encoding = save_encoding;
5435 } else {
5436 /*---- Open of NNTP connection failed ------ */
5437 snprintf(error_mess = error_buf, sizeof(error_buf),
5438 _("Error connecting to news server: %s"),
5439 ps_global->c_client_error);
5440 error_buf[sizeof(error_buf)-1] = '\0';
5441 dprint((1, error_buf));
5443 server_no++;
5445 fs_give((void **)&news_servers);
5446 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5447 } else {
5448 /*----- Post via local mechanism -------*/
5449 #ifdef _WINDOWS
5450 snprintf(error_mess = error_buf, sizeof(error_buf),
5451 _("Can't post, NNTP-server must be defined!"));
5452 #else /* UNIX */
5453 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5454 pipecb_f);
5455 #endif
5458 if(we_cancel)
5459 cancel_busy_cue(0);
5461 if(error_mess){
5462 if(lmc.so && !lmc.all_written)
5463 so_give(&lmc.so); /* clean up any fcc data */
5465 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5466 return(-1);
5469 lmc.all_written = 1;
5470 return(1);
5474 /* ----------------------------------------------------------------------
5475 Figure out command to start local SMTP agent
5477 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5479 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5481 ----*/
5482 char *
5483 smtp_command(char *errbuf, size_t errbuflen)
5485 #ifdef _WINDOWS
5486 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5487 && ps_global->VAR_SMTP_SERVER[0][0]))
5488 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5490 errbuf[errbuflen-1] = '\0';
5491 #else /* UNIX */
5492 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5493 #define SENDTMPLEN 256
5494 char tmp[SENDTMPLEN];
5495 /* SENDTMPLEN == sizeof(tmp) */
5496 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
5497 (SENDTMPLEN-3)/2, SENDMAILFLAGS);
5498 return(cpystr(tmp));
5499 # else
5500 strncpy(errbuf, _("No default posting command."), errbuflen);
5501 errbuf[errbuflen-1] = '\0';
5502 # endif
5503 #endif
5505 return(NULL);
5510 #ifndef _WINDOWS
5511 /*----------------------------------------------------------------------
5512 Hand off given message to local posting agent
5514 Args: envelope -- The envelope for the BCC and debugging
5515 header -- The text of the message header
5516 errbuf -- buffer for reporting errors (assumed non-NULL)
5517 len -- Length of errbuf
5519 ----*/
5521 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5522 char *errbuf, size_t len,
5523 void (*bigresult_f) (char *, int),
5524 void (*pipecb_f)(PIPE_S *, int, void *))
5526 #ifdef DF_SENDMAIL_PATH
5527 char cmd_buf[256];
5528 #endif
5529 char *cmd = NULL;
5532 * A bit of complicated policy implemented here.
5533 * There are two posting variables sendmail-path and smtp-server.
5534 * Precedence is in that order.
5535 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5536 * Precedence is in that order.
5537 * Said differently, the order goes something like what's below.
5539 * NOTE: the fixed/command-line/user precendence handling is also
5540 * indicated by what's pointed to by ps_global->VAR_*, but since
5541 * that also includes the global defaults, it's not sufficient.
5544 if(ps_global->FIX_SENDMAIL_PATH
5545 && ps_global->FIX_SENDMAIL_PATH[0]){
5546 cmd = ps_global->FIX_SENDMAIL_PATH;
5548 else if(!(ps_global->FIX_SMTP_SERVER
5549 && ps_global->FIX_SMTP_SERVER[0])){
5550 if(ps_global->COM_SENDMAIL_PATH
5551 && ps_global->COM_SENDMAIL_PATH[0]){
5552 cmd = ps_global->COM_SENDMAIL_PATH;
5554 else if(!(ps_global->COM_SMTP_SERVER
5555 && ps_global->COM_SMTP_SERVER[0])){
5556 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5557 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5558 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5559 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5560 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5561 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5562 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5563 else
5564 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5566 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5567 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5568 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5569 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5570 if(ps_global->GLO_SENDMAIL_PATH
5571 && ps_global->GLO_SENDMAIL_PATH[0]){
5572 cmd = ps_global->GLO_SENDMAIL_PATH;
5574 #ifdef DF_SENDMAIL_PATH
5576 * This defines the default method of posting. So,
5577 * unless we're told otherwise use it...
5579 else if(!(ps_global->GLO_SMTP_SERVER
5580 && ps_global->GLO_SMTP_SERVER[0])){
5581 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5582 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5584 #endif
5589 *errbuf = '\0';
5590 if(cmd){
5591 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5593 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5594 return(1);
5596 else
5597 return(0);
5602 /*----------------------------------------------------------------------
5603 Hand off given message to local posting agent
5605 Args: envelope -- The envelope for the BCC and debugging
5606 header -- The text of the message header
5607 errbuf -- buffer for reporting errors (assumed non-NULL)
5608 errbuflen -- Length of errbuf
5610 Fork off mailer process and pipe the message into it
5611 Called to post news via Inews when NNTP is unavailable
5613 ----*/
5614 char *
5615 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5616 size_t errbuflen,
5617 void (*bigresult_f) (char *, int),
5618 void (*pipecb_f)(PIPE_S *, int, void *))
5620 char *err = NULL;
5621 #ifdef SENDNEWS
5622 char *s;
5623 char tmp[200];
5625 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5626 *s = '\0';
5628 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5629 strncpy(tmp, err, sizeof(tmp)-1);
5630 tmp[sizeof(tmp)-1] = '\0';
5631 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5632 SENDNEWS, tmp);
5635 if(s)
5636 *s = ' '; /* restore the date */
5638 #else /* !SENDNEWS */ /* this is the default case */
5639 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5640 err[errbuflen-1] = '\0';
5641 #endif /* !SENDNEWS */
5642 return(err);
5647 /*----------------------------------------------------------------------
5648 Hand off message to local MTA; it parses recipients from 822 header
5650 Args: header -- struct containing header data
5651 body -- struct containing message body data
5652 cmd -- command to use for handoff (%s says where file should go)
5653 errs -- pointer to buf to hold errors
5655 ----*/
5656 char *
5657 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5658 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5659 void (*pipecb_f)(PIPE_S *, int, void *))
5661 char *result = NULL;
5662 PIPE_S *pipe;
5664 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5666 if((pipe = open_system_pipe(cmd, &result, NULL,
5667 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5668 0, pipecb_f, pipe_report_error)) != NULL){
5669 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5670 (TCPSTREAM *) pipe)){
5671 strncpy(errs, _("Error posting."), errslen-1);
5672 errs[errslen-1] = '\0';
5675 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5676 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5677 if(result && bigresult_f)
5678 (*bigresult_f)(result, CM_BR_ERROR);
5681 else
5682 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5684 if(result){
5685 our_unlink(result);
5686 fs_give((void **)&result);
5689 return(*errs ? errs : NULL);
5694 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5695 * pipes rather than a tcp stream
5697 long
5698 pine_pipe_soutr_nl (void *stream, char *s)
5700 long rv = T;
5701 char *p;
5702 size_t n;
5704 while(*s && rv){
5705 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0){
5706 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5707 if(rv < 0){
5708 if(errno != EINTR){
5709 rv = 0;
5710 break;
5713 else{
5714 s += rv;
5715 n -= rv;
5719 if(p && rv){
5720 s = p + 2; /* write UNIX EOL */
5721 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5722 if(rv < 0 && errno != EINTR){
5723 rv = 0;
5724 break;
5727 else
5728 break;
5731 return(rv);
5733 #endif
5736 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5740 * helpful def's
5742 #define S(X) ((PIPE_S *)(X))
5743 #define GETBUFLEN (4 * MAILTMPLEN)
5746 void *
5747 piped_smtp_open (char *host, char *service, long unsigned int port)
5749 char *postcmd;
5750 void *rv = NULL;
5752 if(strucmp(host, "localhost")){
5753 char tmp[MAILTMPLEN];
5754 /* MAILTMPLEN = sizeof(tmp) */
5755 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5756 MAILTMPLEN-50, host);
5757 tmp[sizeof(tmp)-1] = '\0';
5758 mm_log(tmp, ERROR);
5760 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5761 rv = open_system_pipe(postcmd, NULL, NULL,
5762 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5763 0, NULL, pipe_report_error);
5764 fs_give((void **) &postcmd);
5766 else
5767 mm_log(ps_global->c_client_error, ERROR);
5769 return(rv);
5773 void *
5774 piped_aopen (NETMBX *mb, char *service, char *user)
5776 return(NULL);
5781 * piped_soutr - Replacement for tcp_soutr that writes one of our
5782 * pipes rather than a tcp stream
5784 long
5785 piped_soutr (void *stream, char *s)
5787 return(piped_sout(stream, s, strlen(s)));
5792 * piped_sout - Replacement for tcp_soutr that writes one of our
5793 * pipes rather than a tcp stream
5795 long
5796 piped_sout (void *stream, char *s, long unsigned int size)
5798 int i, o;
5800 if(S(stream)->out.d < 0)
5801 return(0L);
5803 if((i = (int) size) != 0){
5804 while((o = write(S(stream)->out.d, s, i)) != i)
5805 if(o < 0){
5806 if(errno != EINTR){
5807 piped_abort(stream);
5808 return(0L);
5811 else{
5812 s += o; /* try again, fix up counts */
5813 i -= o;
5817 return(1L);
5822 * piped_getline - Replacement for tcp_getline that reads one
5823 * of our pipes rather than a tcp pipe
5825 * C-client expects that the \r\n will be stripped off.
5827 char *
5828 piped_getline (void *stream)
5830 static int cnt;
5831 static char *ptr;
5832 int n, m;
5833 char *ret, *s, *sp, c = '\0', d;
5835 if(S(stream)->in.d < 0)
5836 return(NULL);
5838 if(!S(stream)->tmp){ /* initialize! */
5839 /* alloc space to collect input (freed in close_system_pipe) */
5840 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5841 memset(S(stream)->tmp, 0, GETBUFLEN);
5842 cnt = -1;
5845 while(cnt < 0){
5846 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5847 if(errno != EINTR){
5848 piped_abort(stream);
5849 return(NULL);
5852 if(cnt == 0){
5853 piped_abort(stream);
5854 return(NULL);
5857 ptr = S(stream)->tmp;
5860 s = ptr;
5861 n = 0;
5862 while(cnt--){
5863 d = *ptr++;
5864 if((c == '\015') && (d == '\012')){
5865 ret = (char *)fs_get (n--);
5866 memcpy(ret, s, n);
5867 ret[n] = '\0';
5868 return(ret);
5871 n++;
5872 c = d;
5874 /* copy partial string from buffer */
5875 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5876 /* get more data */
5877 while(cnt < 0){
5878 memset(S(stream)->tmp, 0, GETBUFLEN);
5879 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5880 if(errno != EINTR){
5881 fs_give((void **) &ret);
5882 piped_abort(stream);
5883 return(NULL);
5886 if(cnt == 0){
5887 if(n > 0)
5888 ret[n-1] = '\0'; /* to try to get error message logged */
5889 else{
5890 piped_abort(stream);
5891 return(NULL);
5895 ptr = S(stream)->tmp;
5898 if(c == '\015' && *ptr == '\012'){
5899 ptr++;
5900 cnt--;
5901 ret[n - 1] = '\0'; /* tie off string with null */
5903 else if ((s = piped_getline(stream)) != NULL) {
5904 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5905 memcpy(ret, sp, n); /* copy first part */
5906 memcpy(ret + n, s, m); /* and second part */
5907 fs_give((void **) &sp); /* flush first part */
5908 fs_give((void **) &s); /* flush second part */
5909 ret[n + m] = '\0'; /* tie off string with null */
5912 return(ret);
5917 * piped_close - Replacement for tcp_close that closes pipes to our
5918 * child rather than a tcp connection
5920 void
5921 piped_close(void *stream)
5924 * Uninstall our hooks into smtp_send since it's being used by
5925 * the nntp driver as well...
5927 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5932 * piped_abort - close down the pipe we were using to post
5934 void
5935 piped_abort(void *stream)
5937 if(S(stream)->in.d >= 0){
5938 close(S(stream)->in.d);
5939 S(stream)->in.d = -1;
5942 if(S(stream)->out.d){
5943 close(S(stream)->out.d);
5944 S(stream)->out.d = -1;
5949 char *
5950 piped_host(void *stream)
5952 return(ps_global->hostname ? ps_global->hostname : "localhost");
5956 unsigned long
5957 piped_port(void *stream)
5959 return(0L);