* Release memory of a conversion table to UTF-8, if this was created by
[alpine.git] / pith / send.c
blobff48de2d010cacfe3153bbcb8a16c0b2f4c9ed87
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/send.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/store.h"
24 #include "../pith/mimedesc.h"
25 #include "../pith/context.h"
26 #include "../pith/status.h"
27 #include "../pith/folder.h"
28 #include "../pith/bldaddr.h"
29 #include "../pith/pipe.h"
30 #include "../pith/mailview.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/list.h"
33 #include "../pith/filter.h"
34 #include "../pith/reply.h"
35 #include "../pith/addrstring.h"
36 #include "../pith/rfc2231.h"
37 #include "../pith/stream.h"
38 #include "../pith/util.h"
39 #include "../pith/adrbklib.h"
40 #include "../pith/options.h"
41 #include "../pith/busy.h"
42 #include "../pith/text.h"
43 #include "../pith/imap.h"
44 #include "../pith/ablookup.h"
45 #include "../pith/sort.h"
46 #include "../pith/smime.h"
48 #include "../c-client/smtp.h"
49 #include "../c-client/nntp.h"
52 /* this is used in pine_send and pine_simple_send */
53 /* name::type::canedit::writehdr::localcopy::rcptto */
54 PINEFIELD pf_template[] = {
55 {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
56 {"From", Address, 0, 1, 1, 0},
57 {"Reply-To", Address, 0, 1, 1, 0},
58 {TONAME, Address, 1, 1, 1, 1},
59 {CCNAME, Address, 1, 1, 1, 1},
60 {"bcc", Address, 1, 0, 1, 1},
61 {"Newsgroups", FreeText, 1, 1, 1, 0},
62 {"Fcc", Fcc, 1, 0, 0, 0},
63 {"Lcc", Address, 1, 0, 1, 1},
64 {"Attchmnt", Attachment, 1, 1, 1, 0},
65 {SUBJNAME, Subject, 1, 1, 1, 0},
66 {"References", FreeText, 0, 1, 1, 0},
67 {"Date", FreeText, 0, 1, 1, 0},
68 {"In-Reply-To", FreeText, 0, 1, 1, 0},
69 {"Message-ID", FreeText, 0, 1, 1, 0},
70 {PRIORITYNAME, FreeText, 0, 1, 1, 0},
71 {"User-Agent", FreeText, 0, 1, 1, 0},
72 {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
73 {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
74 {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
75 {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
76 {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
77 {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
78 {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
79 {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
80 {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
81 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
82 {"X-X-Sender", Address, 0, 1, 1, 0},
83 #endif
84 {NULL, FreeText}
88 PRIORITY_S priorities[] = {
89 {1, "Highest"},
90 {2, "High"},
91 {3, "Normal"},
92 {4, "Low"},
93 {5, "Lowest"},
94 {0, NULL}
98 #define ctrl(c) ((c) & 0x1f)
100 /* which message part to test for xliteration */
101 typedef enum {MsgBody, HdrText} MsgPart;
105 * Internal prototypes
107 long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
108 int l_flush_net(int);
109 int l_putc(int);
110 int pine_write_header_line(char *, char *, STORE_S *);
111 int pine_write_params(PARAMETER *, STORE_S *);
112 char *tidy_smtp_mess(char *, char *, char *, size_t);
113 int lmc_body_header_line(char *, int);
114 int lmc_body_header_finish(void);
115 int pwbh_finish(int, STORE_S *);
116 int sent_percent(void);
117 unsigned short *setup_avoid_table(void);
118 #ifndef _WINDOWS
119 int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
120 void (*)(PIPE_S *, int, void *));
121 char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
122 void (*)(PIPE_S *, int, void *));
123 char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
124 void (*)(PIPE_S *, int, void *));
125 long pine_pipe_soutr_nl(void *, char *);
126 #endif
127 char *smtp_command(char *, size_t);
128 void *piped_smtp_open(char *, char *, unsigned long);
129 void *piped_aopen(NETMBX *, char *, char *);
130 long piped_soutr(void *, char *);
131 long piped_sout(void *, char *, unsigned long);
132 char *piped_getline(void *);
133 void piped_close(void *);
134 void piped_abort(void *);
135 char *piped_host(void *);
136 unsigned long piped_port(void *);
137 char *posting_characterset(void *, char *, MsgPart);
138 int body_is_translatable(void *, char *);
139 int text_is_translatable(void *, char *);
140 int dummy_putc(int);
141 unsigned long *init_charsetchecker(char *);
142 int representable_in_charset(unsigned long, char *);
143 char *most_preferred_charset(unsigned long);
146 * Storage object where the FCC (or postponed msg) is to be written.
147 * This is amazingly bogus. Much work was done to put messages
148 * together and encode them as they went to the tmp file for sendmail
149 * or into the SMTP slot (especially for for DOS, to prevent a temporary
150 * file (and needlessly copying the message).
152 * HOWEVER, since there's no piping into c-client routines
153 * (particularly mail_append() which copies the fcc), the fcc will have
154 * to be copied to disk. This global tells pine's copy of the rfc822
155 * output functions where to also write the message bytes for the fcc.
156 * With piping in the c-client we could just have two pipes to shove
157 * down rather than messing with damn copies. FIX THIS!
159 * The function open_fcc, locates the actual folder and creates it if
160 * requested before any mailing or posting is done.
162 struct local_message_copy lmc;
166 * Locally global pointer to stream used for sending/posting.
167 * It's also used to indicate when/if we write the Bcc: field in
168 * the header.
170 static SENDSTREAM *sending_stream = NULL;
173 static struct hooks {
174 void *rfc822_out; /* Message outputter */
175 } sending_hooks;
178 static FILE *verbose_send_output = NULL;
179 static long send_bytes_sent, send_bytes_to_send;
180 static METAENV *send_header = NULL;
183 * Hooks for prompts and confirmations
185 int (*pith_opt_daemon_confirm)(void);
188 static NETDRIVER piped_io = {
189 piped_smtp_open, /* open a connection */
190 piped_aopen, /* open an authenticated connection */
191 piped_getline, /* get a line */
192 NULL, /* get a buffer */
193 piped_soutr, /* output pushed data */
194 piped_sout, /* output string */
195 piped_close, /* close connection */
196 piped_host, /* return host name */
197 piped_host, /* remotehost */
198 piped_port, /* return port number */
199 piped_host /* return local host (NOTE: same as host!) */
204 * Since c-client preallocates, it's necessary here to define a limit
205 * such that we don't blow up in c-client (see rfc822_address_line()).
207 #define MAX_SINGLE_ADDR MAILTMPLEN
209 #define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
213 * Phone home hash controls
215 #define PH_HASHBITS 24
216 #define PH_MAXHASH (1<<(PH_HASHBITS))
220 * postponed_stream - return stream associated with postponed messages
221 * in argument.
224 postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
226 MAILSTREAM *stream = NULL;
227 CONTEXT_S *p_cntxt = NULL;
228 char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
229 int exists;
231 if(!(streamp && mbox))
232 return(0);
234 *streamp = NULL;
237 * find default context to look for folder...
239 * The "mbox" is assumed to be local if we're given what looks
240 * like an absolute path. This is different from Goto/Save
241 * where we do alot of work to interpret paths relative to the
242 * server. This reason is to support all the pre-4.00 pinerc'
243 * that specified a path and because there's yet to be a way
244 * in c-client to specify otherwise in the face of a remote
245 * context.
247 if(!is_absolute_path(mbox)
248 && !(p_cntxt = default_save_context(ps_global->context_list)))
249 p_cntxt = ps_global->context_list;
251 /* check to see if the folder exists, the user wants to continue
252 * and that we can actually read something in...
254 exists = folder_name_exists(p_cntxt, mbox, &fullname);
255 if(fullname)
256 mbox = fullname;
258 if(exists & FEX_ISFILE){
259 context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
260 if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
262 * The mbox is relative to the home directory.
263 * Make it absolute so we can compare it to
264 * stream->mailbox.
266 build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
267 SIZEOF_20KBUF);
268 strncpy(tmp, tmp_20k_buf, sizeof(tmp));
269 tmp[sizeof(tmp)-1] = '\0';
272 if((stream = ps_global->mail_stream)
273 && !(stream->mailbox
274 && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
275 || (*tmp == '{'
276 && same_stream(tmp, stream)
277 && (p = strchr(tmp, '}'))
278 && (q = strchr(stream->mailbox,'}'))
279 && !strcmp(p + 1, q + 1)))))
280 stream = NULL;
282 if(!stream){
283 stream = context_open(p_cntxt, NULL, mbox,
284 SP_USEPOOL|SP_TEMPUSE, NULL);
285 if(stream && !stream->halfopen){
286 if(stream->nmsgs > 0)
287 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
289 if(checknmsgs && stream->nmsgs < 1){
290 pine_mail_close(stream);
291 exists = 0;
292 stream = NULL;
295 else{
296 q_status_message2(SM_ORDER | SM_DING, 3, 3,
297 _("Can't open %s mailbox: %s"), type, mbox);
298 if(stream)
299 pine_mail_close(stream);
301 exists = 0;
302 stream = NULL;
306 else{
307 if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
308 q_status_message1(SM_ORDER | SM_DING, 3, 3,
309 _("%s message folder doesn't exist!"), type);
313 if(fullname)
314 fs_give((void **) &fullname);
316 *streamp = stream;
318 return(exists);
323 redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
324 struct mail_bodystruct **body, char **fcc, char **lcc,
325 REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
326 ACTION_S **role, int flags, STORE_S *so)
328 MAILSTREAM *stream;
329 ENVELOPE *e = NULL;
330 BODY *b;
331 PART *part;
332 PINEFIELD *pf;
333 gf_io_t pc;
334 char *extras, **fields, **values, *p;
335 char *hdrs[2], *h, *charset;
336 char **smtp_servers = NULL, **nntp_servers = NULL;
337 int i, pine_generated = 0, our_replyto = 0;
338 int added_to_role = 0;
339 unsigned gbpt_flags = GBPT_NONE;
340 MESSAGECACHE *mc;
342 if(!(streamp && *streamp))
343 return(redraft_cleanup(streamp, TRUE, flags));
345 stream = *streamp;
347 if(flags & REDRAFT_HTML)
348 gbpt_flags |= GBPT_HTML_OK;
350 /* grok any user-defined or non-c-client headers */
351 if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
354 * The custom headers to look for in the suspended message should
355 * have been stored in the X-Our-Headers header. So first we get
356 * that list. If we can't find it (version that stored the
357 * message < 4.30) then we use the global custom list.
359 hdrs[0] = OUR_HDRS_LIST;
360 hdrs[1] = NULL;
361 if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
362 int commas = 0;
363 char **list;
364 char *hdrval = NULL;
366 if((hdrval = strindex(h, ':')) != NULL){
367 for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
368 hdrval++)
372 /* count elements in list */
373 for(p = hdrval; p && *p; p++)
374 if(*p == ',')
375 commas++;
377 if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
379 *custom = parse_custom_hdrs(list, Replace);
380 add_defaults_from_list(*custom,
381 ps_global->VAR_CUSTOM_HDRS);
382 free_list_array(&list);
385 if(*custom && !(*custom)->name){
386 free_customs(*custom);
387 *custom = NULL;
390 fs_give((void **)&h);
393 if(!*custom)
394 *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
396 #define INDEX_FCC 0
397 #define INDEX_POSTERR 1
398 #define INDEX_REPLYUID 2
399 #define INDEX_REPLYMBOX 3
400 #define INDEX_SMTP 4
401 #define INDEX_NNTP 5
402 #define INDEX_CURSORPOS 6
403 #define INDEX_OUR_REPLYTO 7
404 #define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
405 #define FIELD_COUNT 9
407 i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
410 * Having these two fields separated isn't the slickest, but
411 * converting the pointer array for fetchheader_lines() to
412 * a list of structures or some such for simple_header_parse()
413 * is too goonie. We could do something like re-use c-client's
414 * PARAMETER struct which is a simple char * pairing, but that
415 * doesn't make sense to pass to fetchheader_lines()...
417 fields = (char **) fs_get((size_t) i * sizeof(char *));
418 values = (char **) fs_get((size_t) i * sizeof(char *));
419 memset(fields, 0, (size_t) i * sizeof(char *));
420 memset(values, 0, (size_t) i * sizeof(char *));
422 fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
423 fields[++i] = "X-Post-Error"; /* posting errors too */
424 fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
425 fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
426 fields[++i] = "X-SMTP-Server";/* SMTP server to use */
427 fields[++i] = "X-NNTP-Server";/* NNTP server to use */
428 fields[++i] = "X-Cursor-Pos"; /* Cursor position */
429 fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
430 fields[++i] = "Lcc"; /* Lcc: too... */
431 if(++i != FIELD_COUNT)
432 alpine_panic("Fix FIELD_COUNT");
434 for(pf = *custom; pf && pf->name; pf = pf->next)
435 if(!pf->standard)
436 fields[i++] = pf->name; /* assign custom fields */
438 if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
439 simple_header_parse(extras, fields, values);
440 fs_give((void **) &extras);
443 * translate RFC 1522 strings,
444 * starting with "Lcc" field
446 for(i = INDEX_LCC; fields[i]; i++)
447 if(values[i]){
448 size_t len;
449 char *bufp, *biggerbuf = NULL;
451 if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
452 len++;
453 biggerbuf = (char *)fs_get(len * sizeof(char));
454 bufp = biggerbuf;
456 else{
457 bufp = tmp_20k_buf;
458 len = SIZEOF_20KBUF;
461 p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
463 if(p == tmp_20k_buf){
464 fs_give((void **)&values[i]);
465 values[i] = cpystr(p);
468 if(biggerbuf)
469 fs_give((void **)&biggerbuf);
472 for(pf = *custom, i = FIELD_COUNT;
473 pf && pf->name;
474 pf = pf->next){
475 if(pf->standard){
477 * Because the value is already in the envelope.
479 pf->cstmtype = NoMatch;
480 continue;
483 if(values[i]){ /* use this instead of default */
484 if(pf->textbuf)
485 fs_give((void **)&pf->textbuf);
487 pf->textbuf = values[i]; /* freed in pine_send! */
489 else if(pf->textbuf) /* was erased before postpone */
490 fs_give((void **)&pf->textbuf);
492 i++;
495 if(values[INDEX_FCC]) /* If "Fcc:" was there... */
496 pine_generated = 1; /* we put it there? */
499 * Since c-client fills in the reply_to field in the envelope
500 * even if there isn't a Reply-To header in the message we
501 * have to work around that. When we postpone we add
502 * a second header that has value "Empty" if there really
503 * was a Reply-To and it was empty. It has the
504 * value "Full" if we put the Reply-To contents there
505 * intentionally (and it isn't empty).
507 if(values[INDEX_OUR_REPLYTO]){
508 if(values[INDEX_OUR_REPLYTO][0] == 'E')
509 our_replyto = 'E'; /* we put an empty one there */
510 else if(values[INDEX_OUR_REPLYTO][0] == 'F')
511 our_replyto = 'F'; /* we put it there */
513 fs_give((void **) &values[INDEX_OUR_REPLYTO]);
516 if(fcc) /* fcc: special case... */
517 *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
518 else if(values[INDEX_FCC])
519 fs_give((void **) &values[INDEX_FCC]);
521 if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
522 q_status_message(SM_ORDER|SM_DING, 4, 4,
523 values[INDEX_POSTERR]);
524 fs_give((void **) &values[INDEX_POSTERR]);
527 if(values[INDEX_REPLYUID]){
528 if(reply)
529 *reply = build_reply_uid(values[INDEX_REPLYUID]);
531 fs_give((void **) &values[INDEX_REPLYUID]);
533 if(values[INDEX_REPLYMBOX] && reply && *reply)
534 (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
536 if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
537 (*reply)->origmbox = cpystr((*reply)->mailbox);
540 if(values[INDEX_REPLYMBOX])
541 fs_give((void **) &values[INDEX_REPLYMBOX]);
543 if(values[INDEX_SMTP]){
544 char *q;
545 size_t cnt = 0;
548 * Turn the space delimited list of smtp servers into
549 * a char ** list.
551 p = values[INDEX_SMTP];
553 if(!*p || isspace((unsigned char) *p))
554 cnt++;
555 } while(*p++);
557 smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
558 memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
560 cnt = 0;
561 q = p = values[INDEX_SMTP];
563 if(!*p || isspace((unsigned char) *p)){
564 if(*p){
565 *p = '\0';
566 smtp_servers[cnt++] = cpystr(q);
567 *p = ' ';
568 q = p+1;
570 else
571 smtp_servers[cnt++] = cpystr(q);
573 } while(*p++);
575 fs_give((void **) &values[INDEX_SMTP]);
578 if(values[INDEX_NNTP]){
579 char *q;
580 size_t cnt = 0;
583 * Turn the space delimited list of smtp nntp into
584 * a char ** list.
586 p = values[INDEX_NNTP];
588 if(!*p || isspace((unsigned char) *p))
589 cnt++;
590 } while(*p++);
592 nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
593 memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
595 cnt = 0;
596 q = p = values[INDEX_NNTP];
598 if(!*p || isspace((unsigned char) *p)){
599 if(*p){
600 *p = '\0';
601 nntp_servers[cnt++] = cpystr(q);
602 *p = ' ';
603 q = p+1;
605 else
606 nntp_servers[cnt++] = cpystr(q);
608 } while(*p++);
610 fs_give((void **) &values[INDEX_NNTP]);
613 if(values[INDEX_CURSORPOS]){
615 * The redraft cursor position is written as two fields
616 * separated by a space. First comes the name of the
617 * header field we're in, or just a ":" if we're in the
618 * body. Then comes the offset into that header or into
619 * the body.
621 if(redraft_pos){
622 char *q1, *q2;
624 *redraft_pos
625 = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
626 (*redraft_pos)->offset = 0L;
628 q1 = skip_white_space(values[INDEX_CURSORPOS]);
629 if(*q1 && (q2 = strindex(q1, SPACE))){
630 *q2 = '\0';
631 (*redraft_pos)->hdrname = cpystr(q1);
632 q1 = skip_white_space(q2+1);
633 if(*q1)
634 (*redraft_pos)->offset = atol(q1);
636 else
637 (*redraft_pos)->hdrname = cpystr(":");
640 fs_give((void **) &values[INDEX_CURSORPOS]);
643 if(lcc)
644 *lcc = values[INDEX_LCC];
645 else
646 fs_give((void **) &values[INDEX_LCC]);
649 fs_give((void **)&fields);
650 fs_give((void **)&values);
652 *outgoing = copy_envelope(e);
655 * If the postponed message has a From which is different from
656 * the default, it is either because allow-changing-from is on
657 * or because there was a role with a from that allowed it to happen.
658 * If allow-changing-from is not on, put this back in a role
659 * so that it will be allowed again in pine_send.
661 if(role && *role == NULL &&
662 !ps_global->never_allow_changing_from &&
663 *outgoing){
665 * Now check to see if the from is different from default from.
667 ADDRESS *deffrom;
669 deffrom = generate_from();
670 if(!((*outgoing)->from &&
671 address_is_same(deffrom, (*outgoing)->from) &&
672 ((!(deffrom->personal && deffrom->personal[0]) &&
673 !((*outgoing)->from->personal &&
674 (*outgoing)->from->personal[0])) ||
675 (deffrom->personal && (*outgoing)->from->personal &&
676 !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
678 *role = (ACTION_S *)fs_get(sizeof(**role));
679 memset((void *)*role, 0, sizeof(**role));
680 if(!(*outgoing)->from)
681 (*outgoing)->from = mail_newaddr();
683 (*role)->from = (*outgoing)->from;
684 (*outgoing)->from = NULL;
685 added_to_role++;
688 mail_free_address(&deffrom);
692 * Look at each empty address and see if the user has specified
693 * a default for that field or not. If they have, that means
694 * they have erased it before postponing, so they won't want
695 * the default to come back. If they haven't specified a default,
696 * then the default should be generated in pine_send. We prevent
697 * the default from being assigned by assigning an empty address
698 * to the variable here.
700 * BUG: We should do this for custom Address headers, too, but
701 * there isn't such a thing yet.
703 if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
704 (*outgoing)->to = mail_newaddr();
705 if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
706 (*outgoing)->cc = mail_newaddr();
707 if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
708 (*outgoing)->bcc = mail_newaddr();
710 if(our_replyto == 'E'){
711 /* user erased reply-to before postponing */
712 if((*outgoing)->reply_to)
713 mail_free_address(&(*outgoing)->reply_to);
716 * If empty is not the normal default, make the outgoing
717 * reply_to be an emtpy address. If it is default, leave it
718 * as NULL and the default will be used.
720 if(hdr_is_in_list("reply-to", *custom)){
721 PINEFIELD pf;
723 pf.name = "reply-to";
724 set_default_hdrval(&pf, *custom);
725 if(pf.textbuf){
726 if(pf.textbuf[0]) /* empty is not default */
727 (*outgoing)->reply_to = mail_newaddr();
729 fs_give((void **)&pf.textbuf);
733 else if(our_replyto == 'F'){
734 int add_to_role = 0;
737 * The reply-to is real. If it is different from the default
738 * reply-to, put it in the role so that it will show up when
739 * the user edits.
741 if(hdr_is_in_list("reply-to", *custom)){
742 PINEFIELD pf;
743 char *str;
745 pf.name = "reply-to";
746 set_default_hdrval(&pf, *custom);
747 if(pf.textbuf && pf.textbuf[0]){
748 if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
749 if(!strcmp(str, pf.textbuf)){
750 /* standard value, leave it alone */
753 else /* not standard, put in role */
754 add_to_role++;
756 fs_give((void **)&str);
759 else /* not standard, put in role */
760 add_to_role++;
762 if(pf.textbuf)
763 fs_give((void **)&pf.textbuf);
765 else /* not standard, put in role */
766 add_to_role++;
768 if(add_to_role && role && (*role == NULL || added_to_role)){
769 if(*role == NULL){
770 added_to_role++;
771 *role = (ACTION_S *)fs_get(sizeof(**role));
772 memset((void *)*role, 0, sizeof(**role));
775 (*role)->replyto = (*outgoing)->reply_to;
776 (*outgoing)->reply_to = NULL;
779 else{
780 /* this is a bogus c-client generated replyto */
781 if((*outgoing)->reply_to)
782 mail_free_address(&(*outgoing)->reply_to);
785 if((smtp_servers || nntp_servers)
786 && role && (*role == NULL || added_to_role)){
787 if(*role == NULL){
788 *role = (ACTION_S *)fs_get(sizeof(**role));
789 memset((void *)*role, 0, sizeof(**role));
792 if(smtp_servers)
793 (*role)->smtp = smtp_servers;
794 if(nntp_servers)
795 (*role)->nntp = nntp_servers;
798 if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
799 (*outgoing)->subject = cpystr("");
801 if(!pine_generated){
803 * Now, this is interesting. We should have found
804 * the "fcc:" field if pine wrote the message being
805 * redrafted. Hence, we probably can't trust the
806 * "originator" type fields, so we'll blast them and let
807 * them get set later in pine_send. This should allow
808 * folks with custom or edited From's and such to still
809 * use redraft reasonably, without inadvertently sending
810 * messages that appear to be "From" others...
812 if((*outgoing)->from)
813 mail_free_address(&(*outgoing)->from);
816 * Ditto for Reply-To and Sender...
818 if((*outgoing)->reply_to)
819 mail_free_address(&(*outgoing)->reply_to);
821 if((*outgoing)->sender)
822 mail_free_address(&(*outgoing)->sender);
825 if(!pine_generated || !(flags & REDRAFT_DEL)){
828 * Generate a fresh message id for pretty much the same
829 * reason From and such got wacked...
830 * Also, if we're coming from a form letter, we need to
831 * generate a different id each time.
833 if((*outgoing)->message_id)
834 fs_give((void **)&(*outgoing)->message_id);
836 (*outgoing)->message_id = generate_message_id();
839 if(b && b->type != TYPETEXT){
840 if(b->type == TYPEMULTIPART){
841 if(strucmp(b->subtype, "mixed")){
842 q_status_message1(SM_INFO, 3, 4,
843 "Converting Multipart/%s to Multipart/Mixed",
844 b->subtype);
845 fs_give((void **)&b->subtype);
846 b->subtype = cpystr("mixed");
849 else{
850 q_status_message2(SM_ORDER | SM_DING, 3, 4,
851 "Unable to resume type %s/%s message",
852 body_types[b->type], b->subtype);
853 return(redraft_cleanup(streamp, TRUE, flags));
857 gf_set_so_writec(&pc, so);
859 if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
860 *body = copy_body(NULL, b);
861 part = (*body)->nested.part;
862 part->body.contents.text.data = (void *)so;
863 set_mime_type_by_grope(&part->body);
864 if(part->body.type != TYPETEXT){
865 q_status_message2(SM_ORDER | SM_DING, 3, 4,
866 "Unable to resume; first part is non-text: %s/%s",
867 body_types[part->body.type],
868 part->body.subtype);
869 return(redraft_cleanup(streamp, TRUE, flags));
872 if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
873 /* let outgoing routines decide on charset */
874 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
875 set_parameter(&part->body.parameter, "charset", NULL);
877 fs_give((void **) &charset);
880 ps_global->postpone_no_flow = 1;
882 get_body_part_text(stream, &b->nested.part->body,
883 cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
884 ps_global->postpone_no_flow = 0;
886 if(!fetch_contents(stream, cont_msg, NULL, *body))
887 q_status_message(SM_ORDER | SM_DING, 3, 4,
888 _("Error including all message parts"));
890 else{
891 *body = mail_newbody();
892 (*body)->type = TYPETEXT;
893 if(b->subtype /* these types are transformed to text/plain */
894 && strucmp(b->subtype, "richtext")
895 && strucmp(b->subtype, "enriched")
896 && strucmp(b->subtype, "html"))
897 (*body)->subtype = cpystr(b->subtype);
899 if((charset = parameter_val(b->parameter,"charset")) != NULL){
900 /* let outgoing routines decide on charset */
901 if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
902 fs_give((void **) &charset);
903 else{
904 (*body)->parameter = mail_newbody_parameter();
905 (*body)->parameter->attribute = cpystr("charset");
906 if(utf8_charset(charset)){
907 fs_give((void **) &charset);
908 (*body)->parameter->value = cpystr("UTF-8");
910 else
911 (*body)->parameter->value = charset;
915 (*body)->contents.text.data = (void *)so;
916 ps_global->postpone_no_flow = 1;
917 get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
918 NULL, NULL, gbpt_flags);
919 ps_global->postpone_no_flow = 0;
922 gf_clear_so_writec(so);
924 /* We have what we want, blast this message... */
925 if((flags & REDRAFT_DEL)
926 && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
927 && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
928 mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
930 else
931 return(redraft_cleanup(streamp, TRUE, flags));
933 return(redraft_cleanup(streamp, FALSE, flags));
937 /*----------------------------------------------------------------------
938 Clear deleted messages from given stream and expunge if necessary
940 Args: stream --
941 problem --
943 ----*/
945 redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
947 MAILSTREAM *stream;
949 if(!(streamp && *streamp))
950 return(0);
952 if(!problem && streamp && (stream = *streamp)){
953 if(stream->nmsgs){
954 ps_global->expunge_in_progress = 1;
955 mail_expunge(stream); /* clean out deleted */
956 ps_global->expunge_in_progress = 0;
959 if(!stream->nmsgs){ /* close and delete folder */
960 int do_the_broach = 0;
961 char *mbox = NULL;
963 if(stream){
964 if(stream->original_mailbox && stream->original_mailbox[0])
965 mbox = cpystr(stream->original_mailbox);
966 else if(stream->mailbox && stream->mailbox[0])
967 mbox = cpystr(stream->mailbox);
970 /* if it is current, we have to change folders */
971 if(stream == ps_global->mail_stream)
972 do_the_broach++;
975 * This is the stream to the empty postponed-msgs folder.
976 * We are going to delete the folder in a second. It is
977 * probably preferable to unselect the mailbox and leave
978 * this stream open for re-use instead of actually closing it,
979 * so we do that if possible.
981 if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
983 * This does the UNSELECT on the stream. A NULL
984 * return should mean that something went wrong and
985 * a mail_close already happened, so that should have
986 * cleaned things up in the callback.
988 if((stream=mail_open(stream, stream->mailbox,
989 OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
990 /* now close it so it is put into the stream cache */
991 sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
992 pine_mail_close(stream);
995 else
996 pine_mail_actually_close(stream);
998 *streamp = NULL;
1000 if(do_the_broach){
1001 ps_global->mail_stream = NULL; /* already closed above */
1004 if(mbox && !pine_mail_delete(NULL, mbox))
1005 q_status_message1(SM_ORDER|SM_DING, 3, 3,
1006 /* TRANSLATORS: Arg is a mailbox name */
1007 _("Can't delete %s"), mbox);
1009 if(mbox)
1010 fs_give((void **) &mbox);
1014 return(!problem);
1018 /*----------------------------------------------------------------------
1019 Parse the given header text for any given fields
1021 Args: text -- Text to parse for fcc and attachments refs
1022 fields -- array of field names to look for
1023 values -- array of pointer to save values to, returned NULL if
1024 fields isn't in text.
1026 This function simply looks for the given fields in the given header
1027 text string.
1028 NOTE: newlines are expected CRLF, and we'll ignore continuations
1029 ----*/
1030 void
1031 simple_header_parse(char *text, char **fields, char **values)
1033 int i, n;
1034 char *p, *t;
1036 for(i = 0; fields[i]; i++)
1037 values[i] = NULL; /* clear values array */
1039 /*---- Loop until the end of the header ----*/
1040 for(p = text; *p; ){
1041 for(i = 0; fields[i]; i++) /* find matching field? */
1042 if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
1043 for(p += n + 1; *p; p++){ /* find start of value */
1044 if(*p == '\015' && *(p+1) == '\012'
1045 && !isspace((unsigned char) *(p+2)))
1046 break;
1048 if(!isspace((unsigned char) *p))
1049 break; /* order here is key... */
1052 if(!values[i]){ /* if we haven't already */
1053 values[i] = fs_get(strlen(text) + 1);
1054 values[i][0] = '\0'; /* alloc space for it */
1057 if(*p && *p != '\015'){ /* non-blank value. */
1058 t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
1059 while(*p){ /* check for cont'n lines */
1060 if(*p == '\015' && *(p+1) == '\012'){
1061 if(isspace((unsigned char) *(p+2))){
1062 p += 2;
1063 continue;
1065 else
1066 break;
1069 *t++ = *p++;
1072 *t = '\0';
1075 break;
1078 /* Skip to end of line, what ever it was */
1079 for(; *p ; p++)
1080 if(*p == '\015' && *(p+1) == '\012'){
1081 p += 2;
1082 break;
1088 /*----------------------------------------------------------------------
1089 build a fresh REPLY_S from the given string (see pine_send for format)
1091 Args: s -- "X-Reply-UID" header value
1093 Returns: filled in REPLY_S or NULL on parse error
1094 ----*/
1095 REPLY_S *
1096 build_reply_uid(char *s)
1098 char *p, *prefix = NULL, *val, *seq, *mbox;
1099 int i, nseq, forwarded = 0;
1100 REPLY_S *reply = NULL;
1102 /* FORMAT: (n prefix)(n validity uidlist)mailbox */
1103 /* if 'n prefix' is empty, uid list represents forwarded msgs */
1104 if(*s == '('){
1105 if(*(p = s + 1) == ')'){
1106 forwarded = 1;
1108 else{
1109 for(; isdigit(*p); p++)
1112 if(*p == ' '){
1113 *p++ = '\0';
1115 if((i = atoi(s+1)) && i < strlen(p)){
1116 prefix = p;
1117 *(p += i) = '\0';
1120 else
1121 return(NULL);
1124 if(*++p == '(' && *++p){
1125 for(seq = p; isdigit(*p); p++)
1128 if(*p == ' '){
1129 *p++ = '\0';
1130 for(val = p; isdigit(*p); p++)
1133 if(*p == ' '){
1134 *p++ = '\0';
1136 if((nseq = atoi(seq)) && isdigit(*(seq = p))
1137 && (p = strchr(p, ')')) && *(mbox = ++p)){
1138 imapuid_t *uidl;
1140 uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
1141 for(i = 0; i < nseq; i++)
1142 if((p = strchr(seq,',')) != NULL){
1143 *p = '\0';
1144 if((uidl[i]= strtoul(seq,NULL,10)) != 0)
1145 seq = ++p;
1146 else
1147 break;
1149 else if((p = strchr(seq, ')')) != NULL){
1150 if((uidl[i] = strtoul(seq,NULL,10)) != 0)
1151 i++;
1153 break;
1156 if(i == nseq){
1157 reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
1158 memset(reply, 0, sizeof(REPLY_S));
1159 reply->uid = 1;
1160 reply->data.uid.validity = strtoul(val, NULL, 10);
1161 if(forwarded)
1162 reply->forwarded = 1;
1163 else
1164 reply->prefix = cpystr(prefix);
1166 reply->mailbox = cpystr(mbox);
1167 uidl[nseq] = 0;
1168 reply->data.uid.msgs = uidl;
1170 else
1171 fs_give((void **) &uidl);
1178 return(reply);
1183 * pine_new_env - allocate a new METAENV and fill it in.
1185 METAENV *
1186 pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
1188 int cnt, i, stdcnt;
1189 char *p;
1190 PINEFIELD *pfields, *pf, **sending_order;
1191 METAENV *header;
1193 header = (METAENV *) fs_get(sizeof(METAENV));
1195 /* how many fields are there? */
1196 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1199 stdcnt = cnt;
1201 for(pf = custom; pf; pf = pf->next)
1202 cnt++;
1204 /* temporary PINEFIELD array */
1205 i = (cnt + 1) * sizeof(PINEFIELD);
1206 pfields = (PINEFIELD *)fs_get((size_t) i);
1207 memset(pfields, 0, (size_t) i);
1209 i = (cnt + 1) * sizeof(PINEFIELD *);
1210 sending_order = (PINEFIELD **)fs_get((size_t) i);
1211 memset(sending_order, 0, (size_t) i);
1213 header->env = outgoing;
1214 header->local = pfields;
1215 header->custom = custom;
1216 header->sending_order = sending_order;
1218 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1219 # define NN 4
1220 #else
1221 # define NN 3
1222 #endif
1224 /* initialize pfield */
1225 pf = pfields;
1226 for(i=0; i < stdcnt; i++, pf++){
1228 pf->name = cpystr(pf_template[i].name);
1229 if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global)){
1230 /* slide string over so it is Sender instead of X-X-Sender */
1231 for(p=pf->name+4; *p != '\0'; p++)
1232 *(p-4) = *p;
1233 *(p-4) = '\0';
1236 pf->type = pf_template[i].type;
1237 pf->canedit = pf_template[i].canedit;
1238 pf->rcptto = pf_template[i].rcptto;
1239 pf->writehdr = pf_template[i].writehdr;
1240 pf->localcopy = pf_template[i].localcopy;
1241 pf->extdata = NULL; /* unused */
1242 pf->next = pf + 1;
1244 switch(pf->type){
1245 case FreeText:
1246 switch(i){
1247 case N_AUTHRCVD:
1248 sending_order[0] = pf;
1249 break;
1251 case N_NEWS:
1252 pf->text = &outgoing->newsgroups;
1253 sending_order[1] = pf;
1254 break;
1256 case N_DATE:
1257 pf->text = (char **) &outgoing->date;
1258 sending_order[2] = pf;
1259 break;
1261 case N_INREPLY:
1262 pf->text = &outgoing->in_reply_to;
1263 sending_order[NN+9] = pf;
1264 break;
1266 case N_MSGID:
1267 pf->text = &outgoing->message_id;
1268 sending_order[NN+10] = pf;
1269 break;
1271 case N_REF: /* won't be used here */
1272 sending_order[NN+11] = pf;
1273 break;
1275 case N_PRIORITY:
1276 sending_order[NN+12] = pf;
1277 break;
1279 case N_USERAGENT:
1280 pf->text = &pf->textbuf;
1281 pf->textbuf = generate_user_agent();
1282 sending_order[NN+13] = pf;
1283 break;
1285 case N_POSTERR: /* won't be used here */
1286 sending_order[NN+14] = pf;
1287 break;
1289 case N_RPLUID: /* won't be used here */
1290 sending_order[NN+15] = pf;
1291 break;
1293 case N_RPLMBOX: /* won't be used here */
1294 sending_order[NN+16] = pf;
1295 break;
1297 case N_SMTP: /* won't be used here */
1298 sending_order[NN+17] = pf;
1299 break;
1301 case N_NNTP: /* won't be used here */
1302 sending_order[NN+18] = pf;
1303 break;
1305 case N_CURPOS: /* won't be used here */
1306 sending_order[NN+19] = pf;
1307 break;
1309 case N_OURREPLYTO: /* won't be used here */
1310 sending_order[NN+20] = pf;
1311 break;
1313 case N_OURHDRS: /* won't be used here */
1314 sending_order[NN+21] = pf;
1315 break;
1317 default:
1318 q_status_message1(SM_ORDER,3,3,
1319 "Internal error: 1)FreeText header %s", comatose(i));
1320 break;
1323 break;
1325 case Attachment:
1326 break;
1328 case Address:
1329 switch(i){
1330 case N_FROM:
1331 sending_order[3] = pf;
1332 pf->addr = &outgoing->from;
1333 break;
1335 case N_TO:
1336 sending_order[NN+2] = pf;
1337 pf->addr = &outgoing->to;
1338 if(tobufpp)
1339 (*tobufpp) = &pf->scratch;
1341 break;
1343 case N_CC:
1344 sending_order[NN+3] = pf;
1345 pf->addr = &outgoing->cc;
1346 break;
1348 case N_BCC:
1349 sending_order[NN+4] = pf;
1350 pf->addr = &outgoing->bcc;
1351 break;
1353 case N_REPLYTO:
1354 sending_order[NN+1] = pf;
1355 pf->addr = &outgoing->reply_to;
1356 break;
1358 case N_LCC: /* won't be used here */
1359 sending_order[NN+7] = pf;
1360 break;
1362 #if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
1363 case N_SENDER:
1364 sending_order[4] = pf;
1365 pf->addr = &outgoing->sender;
1366 break;
1367 #endif
1369 case N_NOBODY: /* won't be used here */
1370 sending_order[NN+5] = pf;
1371 break;
1373 default:
1374 q_status_message1(SM_ORDER,3,3,
1375 "Internal error: Address header %s", comatose(i));
1376 break;
1378 break;
1380 case Fcc:
1381 sending_order[NN+8] = pf;
1382 pf->text = fccp;
1383 break;
1385 case Subject:
1386 sending_order[NN+6] = pf;
1387 pf->text = &outgoing->subject;
1388 break;
1390 default:
1391 q_status_message1(SM_ORDER,3,3,
1392 "Unknown header type %d in pine_new_send", (void *)pf->type);
1393 break;
1397 if(((--pf)->next = custom) != NULL){
1398 i--;
1401 * NOTE: "i" is assumed to now index first custom field in sending
1402 * order.
1404 for(pf = pf->next; pf && pf->name; pf = pf->next){
1405 if(pf->standard)
1406 continue;
1408 pf->canedit = 1;
1409 pf->rcptto = 0;
1410 pf->writehdr = 1;
1411 pf->localcopy = 1;
1413 switch(pf->type){
1414 case Address:
1415 if(pf->addr){ /* better be set */
1416 char *addr = NULL;
1417 BuildTo bldto;
1419 bldto.type = Str;
1420 bldto.arg.str = pf->textbuf;
1422 sending_order[i++] = pf;
1423 /* change default text into an ADDRESS */
1424 /* strip quotes around whole default */
1425 removing_trailing_white_space(pf->textbuf);
1426 (void)removing_double_quotes(pf->textbuf);
1427 build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
1428 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
1429 fs_give((void **)&addr);
1430 if(pf->textbuf)
1431 fs_give((void **)&pf->textbuf);
1434 break;
1436 case FreeText:
1437 sending_order[i++] = pf;
1438 pf->text = &pf->textbuf;
1439 break;
1441 default:
1442 q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
1443 (void *)pf->type);
1444 break;
1450 return(header);
1454 void
1455 pine_free_env(METAENV **menv)
1457 int cnt;
1460 if((*menv)->local){
1461 for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
1464 for(; cnt >= 0; cnt--){
1465 if((*menv)->local[cnt].textbuf)
1466 fs_give((void **) &(*menv)->local[cnt].textbuf);
1468 fs_give((void **) &(*menv)->local[cnt].name);
1471 fs_give((void **) &(*menv)->local);
1474 if((*menv)->sending_order)
1475 fs_give((void **) &(*menv)->sending_order);
1477 fs_give((void **) menv);
1481 /*----------------------------------------------------------------------
1482 Check for addresses the user is not permitted to send to, or probably
1483 doesn't want to send to
1485 Returns: 0 if OK
1486 1 if there are only empty groups
1487 -1 if the message shouldn't be sent
1489 Queues a message indicating what happened
1490 ---*/
1492 check_addresses(METAENV *header)
1494 PINEFIELD *pf;
1495 ADDRESS *a;
1496 int send_daemon = 0, rv = CA_EMPTY;
1498 /*---- Is he/she trying to send mail to the mailer-daemon ----*/
1499 for(pf = header->local; pf && pf->name; pf = pf->next)
1500 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1501 for(a = *pf->addr; a != NULL; a = a->next){
1502 if(a->host && (a->host[0] == '.'
1503 || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
1504 && a->host[0] == '@'))){
1505 q_status_message2(SM_ORDER, 4, 7,
1506 /* TRANSLATORS: First arg is the address we can't
1507 send to, second arg is "not in addressbook". */
1508 _("Can't send to address %s: %s"),
1509 a->mailbox,
1510 (a->host[0] == '.')
1511 ? a->host
1512 : _("not in addressbook"));
1513 return(CA_BAD);
1515 else if(ps_global->restricted
1516 && !address_is_us(*pf->addr, ps_global)){
1517 q_status_message(SM_ORDER, 3, 3,
1518 "Restricted demo version of Alpine. You may only send mail to yourself");
1519 return(CA_BAD);
1521 else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
1522 send_daemon = 1;
1523 rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
1525 else if(a->mailbox && a->host){
1526 rv = CA_OK;
1530 return(rv);
1535 * If this isn't general enough we can modify it. The value passed in
1536 * is expected to be one of the desc settings from the priorities array,
1537 * like "High". The header value is X-Priority: 2 (High)
1538 * or something similar. If value doesn't match any of the values then
1539 * the actual value is used instead.
1541 PINEFIELD *
1542 set_priority_header(METAENV *header, char *value)
1544 PINEFIELD *pf;
1546 for(pf = header->local; pf && pf->name; pf = pf->next)
1547 if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
1548 break;
1550 if(pf){
1551 if(pf->textbuf)
1552 fs_give((void **) &pf->textbuf);
1554 if(value){
1555 PRIORITY_S *p;
1557 for(p = priorities; p && p->desc; p++)
1558 if(!strcmp(p->desc, value))
1559 break;
1561 if(p && p->desc){
1562 char buf[100];
1564 snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
1565 pf->textbuf = cpystr(buf);
1567 else
1568 pf->textbuf = cpystr(value);
1571 return pf;
1575 /*----------------------------------------------------------------------
1576 Set answered flags for messages specified by reply structure
1578 Args: reply --
1580 Returns: with appropriate flags set and index cache entries suitably tweeked
1581 ----*/
1582 void
1583 update_answered_flags(REPLY_S *reply)
1585 char *seq = NULL, *p;
1586 long i, ourstream = 0, we_cancel = 0;
1587 MAILSTREAM *stream = NULL;
1589 /* nothing to flip in a pseudo reply */
1590 if(reply && (reply->msgno || reply->uid)){
1591 int j;
1592 MAILSTREAM *m;
1595 * If an established stream will do, use it, else
1596 * build one unless we have an array of msgno's...
1598 * I was just mimicking what was already here. I don't really
1599 * understand why we use strcmp instead of same_stream_and_mailbox().
1600 * Or sp_stream_get(reply->mailbox, SP_MATCH).
1601 * Hubert 2003-07-09
1603 for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
1604 m = ps_global->s_pool.streams[j];
1605 if(m && reply->mailbox && m->mailbox
1606 && !strcmp(reply->mailbox, m->mailbox))
1607 stream = m;
1610 if(!stream && reply->msgno)
1611 return;
1614 * This is here only for people who ran pine4.42 and are
1615 * processing postponed mail from 4.42 now. Pine4.42 saved the
1616 * original mailbox name in the canonical name's position in
1617 * the postponed-msgs folder so it won't match the canonical
1618 * name from the stream.
1620 if(!stream && (!reply->origmbox ||
1621 (reply->mailbox &&
1622 !strcmp(reply->origmbox, reply->mailbox))))
1623 stream = sp_stream_get(reply->mailbox, SP_MATCH);
1625 /* TRANSLATORS: program is busy updating the Answered flags so warns user */
1626 we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
1627 if(!stream){
1628 if((stream = pine_mail_open(NULL,
1629 reply->origmbox ? reply->origmbox
1630 : reply->mailbox,
1631 OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
1632 NULL)) != NULL){
1633 ourstream++;
1635 else{
1636 if(we_cancel)
1637 cancel_busy_cue(0);
1639 return;
1643 if(stream->uid_validity == reply->data.uid.validity){
1644 for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
1645 if(i){
1646 sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
1647 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1650 sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
1651 SIZEOF_20KBUF-(p-tmp_20k_buf));
1652 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1655 if(reply->forwarded){
1657 * $Forwarded is a regular keyword so we only try to
1658 * set it if the stream allows keywords.
1659 * We could mess up if the stream has keywords but just
1660 * isn't allowing anymore and $Forwarded already exists,
1661 * but what are the odds?
1663 if(stream && stream->kwd_create)
1664 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1665 FORWARDED_FLAG,
1666 ST_SET | ((reply->uid) ? ST_UID : 0L));
1668 else
1669 mail_flag(stream, seq = cpystr(tmp_20k_buf),
1670 "\\ANSWERED",
1671 ST_SET | ((reply->uid) ? ST_UID : 0L));
1673 if(seq)
1674 fs_give((void **)&seq);
1677 if(ourstream)
1678 pine_mail_close(stream); /* clean up dangling stream */
1680 if(we_cancel)
1681 cancel_busy_cue(0);
1687 * phone_home_from - make phone home request's from address IMpersonal.
1688 * Doesn't include user's personal name.
1690 ADDRESS *
1691 phone_home_from(void)
1693 ADDRESS *addr = mail_newaddr();
1694 char tmp[64];
1696 /* garble up mailbox name */
1697 snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
1698 tmp[sizeof(tmp)-1] = '\0';
1699 addr->mailbox = cpystr(tmp);
1700 addr->host = cpystr(ps_global->maildomain);
1701 return(addr);
1706 * one-way-hash a username into an 8-digit decimal number
1708 * Corey Satten, corey@cac.washington.edu, 7/15/98
1710 unsigned int
1711 phone_home_hash(char *s)
1713 unsigned int h;
1715 for (h=0; *s; ++s) {
1716 if (h & 1)
1717 h = (h>>1) | (PH_MAXHASH/2);
1718 else
1719 h = (h>>1);
1721 h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
1724 return (h);
1728 /*----------------------------------------------------------------------
1729 Call the mailer, SMTP, sendmail or whatever
1731 Args: header -- full header (envelope and local parts) of message to send
1732 body -- The full body of the message including text
1733 alt_smtp_servers --
1734 verbosefile -- non-null means caller wants verbose interaction and the resulting
1735 output file name to be returned
1737 Returns: -1 if failed, 1 if succeeded
1738 ----*/
1740 call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
1741 int flags, void (*bigresult_f)(char *, int),
1742 void (*pipecb_f)(PIPE_S *, int, void *))
1744 char error_buf[200], *error_mess = NULL, *postcmd;
1745 ADDRESS *a;
1746 ENVELOPE *fake_env = NULL;
1747 int addr_error_count, we_cancel = 0;
1748 long smtp_opts = 0L;
1749 char *verbose_file = NULL;
1750 BODY *bp = NULL;
1751 PINEFIELD *pf;
1752 BODY *origBody = body;
1754 dprint((4, "Sending mail...\n"));
1756 /* Check for any recipients */
1757 for(pf = header->local; pf && pf->name; pf = pf->next)
1758 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
1759 break;
1761 if(!pf){
1762 q_status_message(SM_ORDER,3,3,
1763 _("Can't send message. No recipients specified!"));
1764 return(0);
1767 #ifdef SMIME
1768 if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
1769 int result;
1771 STORE_S *so = lmc.so;
1772 lmc.so = NULL;
1774 result = 1;
1776 if(ps_global->smime->do_sign){
1777 bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
1778 result = sign_outgoing_message(header, &body, 0, &bp);
1781 /* need to free new body from encrypt if sign fails? */
1782 if(result && ps_global->smime->do_encrypt)
1783 result = encrypt_outgoing_message(header, &body);
1785 lmc.so = so;
1787 if(!result)
1788 return 0;
1790 #endif
1792 /* set up counts and such to keep track sent percentage */
1793 send_bytes_sent = 0;
1794 gf_filter_init(); /* zero piped byte count, 'n */
1795 send_bytes_to_send = send_body_size(body); /* count body bytes */
1796 ps_global->c_client_error[0] = error_buf[0] = '\0';
1797 we_cancel = busy_cue(_("Sending mail"),
1798 send_bytes_to_send ? sent_percent : NULL, 0);
1800 #ifndef _WINDOWS
1802 /* try posting via local "<mta> <-t>" if specified */
1803 if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
1804 if(error_buf[0])
1805 error_mess = error_buf;
1807 goto done;
1810 #endif
1813 * If the user's asked for it, and we find that the first text
1814 * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
1816 if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
1817 smtp_opts |= SOP_8BITMIME;
1819 #ifdef DEBUG
1820 #ifndef DEBUGJOURNAL
1821 if(debug > 5 || (flags & CM_VERBOSE))
1822 #endif
1823 smtp_opts |= SOP_DEBUG;
1824 #endif
1826 if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
1827 smtp_opts |= SOP_DSN;
1828 if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
1829 if(flags & CM_DSN_DELAY)
1830 smtp_opts |= SOP_DSN_NOTIFY_DELAY;
1831 if(flags & CM_DSN_SUCCESS)
1832 smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
1835 * If it isn't Never, then we're always going to let them
1836 * know about failures. This means we don't allow for the
1837 * possibility of setting delay or success without failure.
1839 smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
1841 if(flags & CM_DSN_FULL)
1842 smtp_opts |= SOP_DSN_RETURN_FULL;
1848 * Set global header pointer so post_rfc822_output can get at it when
1849 * it's called back from c-client's sending routine...
1851 send_header = header;
1854 * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
1855 * The purpose is to give smtp_mail the list for SMTP RCPT when
1856 * there are recipients in pine's METAENV that are outside c-client's
1857 * envelope.
1859 * NOTE: If there aren't any, don't bother. Dealt with it below.
1861 for(pf = header->local; pf && pf->name; pf = pf->next)
1862 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
1863 && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
1864 || *pf->addr == header->env->bcc))
1865 break;
1867 if(pf && pf->name){
1868 ADDRESS **tail;
1870 fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
1871 memset(fake_env, 0, sizeof(ENVELOPE));
1872 fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
1873 tail = &(fake_env->to);
1874 for(pf = header->local; pf && pf->name; pf = pf->next)
1875 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
1876 *tail = rfc822_cpy_adr(*pf->addr);
1877 while(*tail)
1878 tail = &((*tail)->next);
1883 * Install our rfc822 output routine
1885 sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
1886 (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
1889 * Allow for verbose posting
1891 (void) mail_parameters(NULL, SET_SMTPVERBOSE,
1892 (void *) pine_smtp_verbose_out);
1895 * We do this because we want mm_log to put the error message into
1896 * c_client_error instead of showing it itself.
1898 ps_global->noshow_error = 1;
1901 * OK, who posts what? We tried an mta_handoff above, but there
1902 * was either none specified or we decided not to use it. So,
1903 * if there's an smtp-server defined anywhere,
1905 if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
1906 /*---------- SMTP ----------*/
1907 dprint((4, "call_mailer: via TCP (%s)\n",
1908 alt_smtp_servers[0]));
1909 TIME_STAMP("smtp-open start (tcp)", 1);
1910 sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
1912 else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
1913 && ps_global->VAR_SMTP_SERVER[0][0]){
1914 /*---------- SMTP ----------*/
1915 dprint((4, "call_mailer: via TCP\n"));
1916 TIME_STAMP("smtp-open start (tcp)", 1);
1917 sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
1919 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
1920 char *cmdlist[2];
1922 /*----- Send via LOCAL SMTP agent ------*/
1923 dprint((4, "call_mailer: via \"%s\"\n", postcmd));
1925 TIME_STAMP("smtp-open start (pipe)", 1);
1926 fs_give((void **) &postcmd);
1927 cmdlist[0] = "localhost";
1928 cmdlist[1] = NULL;
1929 sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
1930 SMTPTCPPORT, smtp_opts);
1931 /* BUG: should provide separate stderr output! */
1934 ps_global->noshow_error = 0;
1936 TIME_STAMP("smtp open", 1);
1937 if(sending_stream){
1938 unsigned short save_encoding, added_encoding;
1940 dprint((1, "Opened SMTP server \"%s\"\n",
1941 net_host(sending_stream->netstream)
1942 ? net_host(sending_stream->netstream) : "?"));
1944 if(flags & CM_VERBOSE){
1945 TIME_STAMP("verbose start", 1);
1946 if((verbose_file = temp_nam(NULL, "sd")) != NULL){
1947 if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
1948 if(!smtp_verbose(sending_stream)){
1949 snprintf(error_mess = error_buf, sizeof(error_buf),
1950 "Mail not sent. VERBOSE mode error%s%.50s.",
1951 (sending_stream && sending_stream->reply)
1952 ? ": ": "",
1953 (sending_stream && sending_stream->reply)
1954 ? sending_stream->reply : "");
1955 error_buf[sizeof(error_buf)-1] = '\0';
1958 else{
1959 our_unlink(verbose_file);
1960 strncpy(error_mess = error_buf,
1961 "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
1962 error_buf[sizeof(error_buf)-1] = '\0';
1965 else{
1966 strncpy(error_mess = error_buf,
1967 "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
1968 error_buf[sizeof(error_buf)-1] = '\0';
1971 TIME_STAMP("verbose end", 1);
1975 * Before we actually send data, see if we have to protect
1976 * the first text body part from getting encoded. We protect
1977 * it from getting encoded in "pine_rfc822_output_body" by
1978 * temporarily inventing a synonym for ENC8BIT...
1979 * This works like so:
1980 * Suppose bp->encoding is set to ENC8BIT.
1981 * We change that here to some unused value (added_encoding) and
1982 * set body_encodings[added_encoding] to "8BIT".
1983 * Then post_rfc822_output is called which calls
1984 * pine_rfc822_output_body. Inside that routine
1985 * pine_write_body_header writes out the encoding for the
1986 * part. Normally it would see encoding == ENC8BIT and it would
1987 * change that to QUOTED-PRINTABLE, but since encoding has been
1988 * set to added_encoding it uses body_encodings[added_encoding]
1989 * which is "8BIT" instead. Then the actual body is written by
1990 * pine_write_body_header which does not do the gf_8bit_qp
1991 * filtering because encoding != ENC8BIT (instead it's equal
1992 * to added_encoding).
1994 if(bp && sending_stream->protocol.esmtp.eightbit.ok
1995 && sending_stream->protocol.esmtp.eightbit.want){
1996 int i;
1998 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
2001 if(i > ENCMAX){ /* no empty encoding slots! */
2002 bp = NULL;
2004 else {
2005 added_encoding = i;
2006 body_encodings[added_encoding] = body_encodings[ENC8BIT];
2007 save_encoding = bp->encoding;
2008 bp->encoding = added_encoding;
2009 #ifdef SMIME
2010 if(ps_global->smime && ps_global->smime->do_sign
2011 && body->nested.part->next
2012 && body->nested.part->next->body.contents.text.data
2013 && body->nested.part->next->body.mime.text.data){
2014 STORE_S *so;
2016 so = (STORE_S *) body->nested.part->next->body.contents.text.data;
2017 so_give(&so);
2018 body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
2019 body->nested.part->next->body.mime.text.data = NULL;
2021 #endif /* SMIME */
2025 if(sending_stream->protocol.esmtp.ok
2026 && sending_stream->protocol.esmtp.dsn.want
2027 && !sending_stream->protocol.esmtp.dsn.ok)
2028 q_status_message(SM_ORDER,3,3,
2029 _("Delivery Status Notification not available from this server."));
2031 TIME_STAMP("smtp start", 1);
2032 if(!error_mess && !smtp_mail(sending_stream, "MAIL",
2033 fake_env ? fake_env : header->env, body)){
2035 snprintf(error_buf, sizeof(error_buf),
2036 _("Mail not sent. Sending error%s%s"),
2037 (sending_stream && sending_stream->reply) ? ": ": ".",
2038 (sending_stream && sending_stream->reply)
2039 ? sending_stream->reply : "");
2040 error_buf[sizeof(error_buf)-1] = '\0';
2041 dprint((1, error_buf));
2042 addr_error_count = 0;
2043 if(fake_env){
2044 for(a = fake_env->to; a != NULL; a = a->next)
2045 if(a->error != NULL){
2046 if(addr_error_count++ < MAX_ADDR_ERROR){
2049 * Too complicated to figure out which header line
2050 * has the error in the fake_env case, so just
2051 * leave cursor at default.
2055 if(error_mess) /* previous error? */
2056 q_status_message(SM_ORDER, 4, 7, error_mess);
2058 error_mess = tidy_smtp_mess(a->error,
2059 _("Mail not sent: %.80s"),
2060 error_buf, sizeof(error_buf));
2063 dprint((1, "Send Error: \"%s\"\n",
2064 a->error));
2067 else{
2068 for(pf = header->local; pf && pf->name; pf = pf->next)
2069 if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
2070 for(a = *pf->addr; a != NULL; a = a->next)
2071 if(a->error != NULL){
2072 if(addr_error_count++ < MAX_ADDR_ERROR){
2074 if(error_mess) /* previous error? */
2075 q_status_message(SM_ORDER, 4, 7, error_mess);
2077 error_mess = tidy_smtp_mess(a->error,
2078 _("Mail not sent: %.80s"),
2079 error_buf, sizeof(error_buf));
2082 dprint((1, "Send Error: \"%s\"\n",
2083 a->error));
2087 if(!error_mess)
2088 error_mess = error_buf;
2091 /* repair modified "body_encodings" array? */
2092 if(bp && sending_stream->protocol.esmtp.eightbit.ok
2093 && sending_stream->protocol.esmtp.eightbit.want){
2094 body_encodings[added_encoding] = NULL;
2095 bp->encoding = save_encoding;
2098 TIME_STAMP("smtp closing", 1);
2099 smtp_close(sending_stream);
2100 sending_stream = NULL;
2101 TIME_STAMP("smtp done", 1);
2103 else if(!error_mess){
2104 snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
2105 ps_global->c_client_error[0] ? ": " : "",
2106 ps_global->c_client_error);
2107 error_buf[sizeof(error_buf)-1] = '\0';
2110 if(verbose_file){
2111 if(verbose_send_output){
2112 TIME_STAMP("verbose start", 1);
2113 fclose(verbose_send_output);
2114 verbose_send_output = NULL;
2115 q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
2117 if(bigresult_f)
2118 (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
2120 TIME_STAMP("verbose end", 1);
2123 fs_give((void **)&verbose_file);
2127 * Restore original 822 emitter...
2129 (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
2131 if(fake_env)
2132 mail_free_envelope(&fake_env);
2134 done:
2136 #ifdef SMIME
2137 /* Free replacement encrypted body */
2138 if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
2140 if(body->type == TYPEMULTIPART){
2141 /* Just get rid of first part, it's actually origBody */
2142 void *x = body->nested.part;
2144 body->nested.part = body->nested.part->next;
2146 fs_give(&x);
2149 pine_free_body(&body);
2151 #endif
2153 if(we_cancel)
2154 cancel_busy_cue(0);
2156 TIME_STAMP("call_mailer done", 1);
2157 /*-------- Did message make it ? ----------*/
2158 if(error_mess){
2159 /*---- Error sending mail -----*/
2160 if(lmc.so && !lmc.all_written)
2161 so_give(&lmc.so);
2163 if(error_mess){
2164 q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
2165 dprint((1, "call_mailer ERROR: %s\n", error_mess));
2168 return(-1);
2170 else{
2171 lmc.all_written = 1;
2172 return(1);
2178 * write_postponed - exported method to write the given message
2179 * to the postponed folder
2182 write_postponed(METAENV *header, struct mail_bodystruct *body)
2184 char **pp, *folder;
2185 int rv = 0, sz;
2186 CONTEXT_S *fcc_cntxt = NULL;
2187 PINEFIELD *pf;
2188 static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
2190 if(!ps_global->VAR_POSTPONED_FOLDER
2191 || !ps_global->VAR_POSTPONED_FOLDER[0]){
2192 q_status_message(SM_ORDER | SM_DING, 3, 3,
2193 _("No postponed file defined"));
2194 return(-1);
2197 folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
2199 lmc.all_written = lmc.text_written = lmc.text_only = 0;
2201 lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
2203 if(lmc.so){
2204 /* BUG: writem sufficient ? */
2205 for(pf = header->local; pf && pf->name; pf = pf->next)
2206 for(pp = writem; *pp; pp++)
2207 if(!strucmp(pf->name, *pp)){
2208 pf->localcopy = 1;
2209 pf->writehdr = 1;
2210 break;
2214 * Work around c-client reply-to bug. C-client will
2215 * return a reply_to in an envelope even if there is
2216 * no reply-to header field. We want to note here whether
2217 * the reply-to is real or not.
2219 if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
2220 for(pf = header->local; pf; pf = pf->next)
2221 if(!strcmp(pf->name, "Reply-To")){
2222 pf->writehdr = 1;
2223 pf->localcopy = 1;
2224 if(header->env->reply_to)
2225 pf->textbuf = cpystr("Full");
2226 else
2227 pf->textbuf = cpystr("Empty");
2231 * Write the list of custom headers to the
2232 * X-Our-Headers header so that we can recover the
2233 * list in redraft.
2235 sz = 0;
2236 for(pf = header->custom; pf && pf->name; pf = pf->next)
2237 sz += strlen(pf->name) + 1;
2239 if(sz){
2240 int i;
2241 char *pstart, *pend;
2243 for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
2246 pf->writehdr = 1;
2247 pf->localcopy = 1;
2248 pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
2249 pf->text = &pf->textbuf;
2250 pf->textbuf[sz] = '\0'; /* tie off overflow */
2251 /* note: "pf" overloaded */
2252 for(pf = header->custom; pf && pf->name; pf = pf->next){
2253 int r = sz - (pend - pstart); /* remaining buffer */
2255 if(r > 0 && r != sz){
2256 r--;
2257 *pend++ = ',';
2260 sstrncpy(&pend, pf->name, r);
2264 if(pine_rfc822_output(header, body, NULL, NULL) < 0
2265 || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
2266 rv = -1;
2268 so_give(&lmc.so);
2270 else {
2271 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2272 "Can't allocate internal storage: %s ",
2273 error_description(errno));
2274 rv = -1;
2277 fs_give((void **) &folder);
2278 return(rv);
2283 commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
2285 if(fcc && *fcc){
2286 lmc.all_written = lmc.text_written = 0;
2287 lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
2288 return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
2290 else
2291 lmc.so = NULL;
2293 return(TRUE);
2298 wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
2300 int rv = TRUE;
2302 if(lmc.so){
2303 if(!header || pine_rfc822_output(header, body, NULL, NULL)){
2304 char label[50];
2306 strncpy(label, "Fcc", sizeof(label));
2307 label[sizeof(label)-1] = '\0';
2308 if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
2309 snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
2310 label[sizeof(label)-1] = '\0';
2313 rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
2315 else{
2316 rv = FALSE;
2319 so_give(&lmc.so);
2322 return(rv);
2326 /*----------------------------------------------------------------------
2327 Checks to make sure the fcc is available and can be opened
2329 Args: fcc -- the name of the fcc to create. It can't be NULL.
2330 fcc_cntxt -- Returns the context the fcc is in.
2331 force -- supress user option prompt
2333 Returns allocated storage object on success, NULL on failure
2334 ----*/
2335 STORE_S *
2336 open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
2338 int exists, ok = 0;
2340 ps_global->mm_log_error = 0;
2343 * check for fcc's existance...
2345 TIME_STAMP("open_fcc start", 1);
2346 if(!is_absolute_path(fcc) && context_isambig(fcc)
2347 && (strucmp(ps_global->inbox_name, fcc) != 0)){
2348 int flip_dot = 0;
2351 * Don't want to preclude a user from Fcc'ing a .name'd folder
2353 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
2354 flip_dot = 1;
2355 F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
2359 * We only want to set the "context" if fcc is an ambiguous
2360 * name. Otherwise, our "relativeness" rules for contexts
2361 * (implemented in context.c) might cause the name to be
2362 * interpreted in the wrong context...
2364 if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
2365 *fcc_cntxt = ps_global->context_list;
2367 build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
2368 if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
2369 if(force
2370 || (pith_opt_save_create_prompt
2371 && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
2373 ps_global->noshow_error = 1;
2375 if(context_create(*fcc_cntxt, NULL, fcc))
2376 ok++;
2378 ps_global->noshow_error = 0;
2380 else
2381 ok--; /* declined! */
2383 else
2384 ok++; /* found! */
2386 if(flip_dot)
2387 F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
2389 free_folder_list(*fcc_cntxt);
2391 else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
2392 if(exists & (FEX_ISFILE | FEX_ISDIR)){
2393 ok++;
2395 else{
2396 if(force
2397 || (pith_opt_save_create_prompt
2398 && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
2400 ps_global->mm_log_error = 0;
2401 ps_global->noshow_error = 1;
2403 ok = pine_mail_create(NULL, fcc) != 0L;
2405 ps_global->noshow_error = 0;
2407 else
2408 ok--; /* declined! */
2412 TIME_STAMP("open_fcc done.", 1);
2413 if(ok > 0){
2414 return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
2416 else{
2417 int l1, l2, l3, wid, w;
2418 char *errstr, tmp[MAILTMPLEN];
2419 char *s1, *s2;
2421 if(ok == 0){
2422 if(ps_global->mm_log_error){
2423 s1 = err_prefix ? err_prefix : "Fcc Error: ";
2424 s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
2426 l1 = strlen(s1);
2427 l2 = strlen(s2);
2428 l3 = strlen(ps_global->c_client_error);
2429 wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
2430 ? ps_global->ttyo->screen_cols : 80;
2431 w = wid - l1 - l2 - 5;
2433 snprintf(errstr = tmp, sizeof(tmp),
2434 "%.99s\"%.*s%.99s\".%.99s",
2436 (l3 > w) ? MAX(w-3,0) : MAX(w,0),
2437 ps_global->c_client_error,
2438 (l3 > w) ? "..." : "",
2439 s2);
2440 tmp[sizeof(tmp)-1] = '\0';
2443 else
2444 errstr = _("Fcc creation error. Message NOT sent or copied.");
2446 else
2447 errstr = _("Fcc creation rejected. Message NOT sent or copied.");
2449 q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
2452 return(NULL);
2456 /*----------------------------------------------------------------------
2457 mail_append() the fcc accumulated in temp_storage to proper destination
2459 Args: fcc -- name of folder
2460 fcc_cntxt -- context for folder
2461 temp_storage -- String of file where Fcc has been accumulated
2463 This copies the string of file to the actual folder, which might be IMAP
2464 or a disk folder. The temp_storage is freed after it is written.
2465 An error message is produced if this fails.
2466 ----*/
2468 write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
2469 MAILSTREAM *stream, char *label, char *flags)
2471 STRING msg;
2472 CONTEXT_S *cntxt;
2473 int we_cancel = 0;
2475 if(!tmp_storage)
2476 return(0);
2478 TIME_STAMP("write_fcc start.", 1);
2479 dprint((4, "Writing %s\n", (label && *label) ? label : ""));
2480 if(label && *label){
2481 char msg_buf[80];
2483 strncpy(msg_buf, "Writing ", sizeof(msg_buf));
2484 msg_buf[sizeof(msg_buf)-1] = '\0';
2485 strncat(msg_buf, label, sizeof(msg_buf)-10);
2486 we_cancel = busy_cue(msg_buf, NULL, 0);
2488 else
2489 we_cancel = busy_cue(NULL, NULL, 1);
2491 so_seek(tmp_storage, 0L, 0);
2494 * Before changing this note that these lines depend on the
2495 * definition of FCC_SOURCE.
2497 INIT(&msg, mail_string, (void *)so_text(tmp_storage),
2498 strlen((char *)so_text(tmp_storage)));
2500 cntxt = fcc_cntxt;
2502 if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
2503 cancel_busy_cue(-1);
2504 we_cancel = 0;
2506 q_status_message1(SM_ORDER | SM_DING, 3, 5,
2507 "Write to \"%s\" FAILED!!!", fcc);
2508 dprint((1, "ERROR appending %s in \"%s\"",
2509 fcc ? fcc : "?",
2510 (cntxt && cntxt->context) ? cntxt->context : "NULL"));
2511 return(0);
2514 if(we_cancel)
2515 cancel_busy_cue(label ? 0 : -1);
2517 dprint((4, "done.\n"));
2518 TIME_STAMP("write_fcc done.", 1);
2519 return(1);
2524 * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
2525 * contained.
2527 BODY *
2528 first_text_8bit(struct mail_bodystruct *body)
2530 if(body->type == TYPEMULTIPART) /* advance to first contained part */
2531 body = &body->nested.part->body;
2533 return((body->type == TYPETEXT && body->encoding != ENC7BIT)
2534 ? body : NULL);
2539 * Build and return the "From:" address for outbound messages from
2540 * global data...
2542 ADDRESS *
2543 generate_from(void)
2545 ADDRESS *addr = mail_newaddr();
2546 if(ps_global->VAR_PERSONAL_NAME){
2547 addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
2548 removing_leading_and_trailing_white_space(addr->personal);
2549 if(addr->personal[0] == '\0')
2550 fs_give((void **)&addr->personal);
2553 addr->mailbox = cpystr(ps_global->VAR_USER_ID);
2554 addr->host = cpystr(ps_global->maildomain);
2555 removing_leading_and_trailing_white_space(addr->mailbox);
2556 removing_leading_and_trailing_white_space(addr->host);
2557 return(addr);
2562 * set_mime_type_by_grope - sniff the given storage object to determine its
2563 * type, subtype, encoding, and charset
2565 * "Type" and "encoding" must be set before calling this routine.
2566 * If "type" is set to something other than TYPEOTHER on entry,
2567 * then that is the "type" we wish to use. Same for "encoding"
2568 * using ENCOTHER instead of TYPEOTHER. Otherwise, we
2569 * figure them out here. If "type" is already set, we also
2570 * leave subtype alone. If not, we figure out subtype here.
2571 * There is a chance that we will upgrade encoding to a "higher"
2572 * level. For example, if it comes in as 7BIT we may change
2573 * that to 8BIT if we find a From_ we want to escape.
2574 * We may also set the charset attribute if the type is TEXT.
2576 * NOTE: this is rather inefficient if the store object is a CharStar
2577 * but the win is all types are handled the same
2579 void
2580 set_mime_type_by_grope(struct mail_bodystruct *body)
2582 #define RBUFSZ (8193)
2583 unsigned char *buf, *p, *bol;
2584 register size_t n;
2585 long max_line = 0L,
2586 eight_bit_chars = 0L,
2587 line_so_far = 0L,
2588 len = 0L;
2589 STORE_S *so = (STORE_S *)body->contents.text.data;
2590 unsigned short new_encoding = ENCOTHER;
2591 int we_cancel = 0;
2592 #ifdef ENCODE_FROMS
2593 short froms = 0, dots = 0,
2594 bmap = 0x1, dmap = 0x1;
2595 #endif
2597 we_cancel = busy_cue(NULL, NULL, 1);
2599 buf = (unsigned char *)fs_get(RBUFSZ);
2600 so_seek(so, 0L, 0);
2602 for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2605 buf[n] = '\0';
2607 if(n){ /* check first few bytes to look for magic numbers */
2608 if(body->type == TYPEOTHER){
2609 if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
2610 body->type = TYPEIMAGE;
2611 body->subtype = cpystr("GIF");
2613 else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
2614 && buf[2] == 0xFF && buf[3] == 0xE0
2615 && !strncmp((char *)&buf[6], "JFIF", 4)){
2616 body->type = TYPEIMAGE;
2617 body->subtype = cpystr("JPEG");
2619 else if((buf[0] == 'M' && buf[1] == 'M')
2620 || (buf[0] == 'I' && buf[1] == 'I')){
2621 body->type = TYPEIMAGE;
2622 body->subtype = cpystr("TIFF");
2624 else if((buf[0] == '%' && buf[1] == '!')
2625 || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
2626 body->type = TYPEAPPLICATION;
2627 body->subtype = cpystr("PostScript");
2629 else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
2630 body->type = TYPEAPPLICATION;
2631 body->subtype = cpystr("PDF");
2633 else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
2634 body->type = TYPEAUDIO;
2635 body->subtype = cpystr("Basic");
2637 else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
2638 && buf[2] == 0x16 && buf[3] == 0x00){
2639 body->type = TYPEAPPLICATION;
2640 body->subtype = cpystr("APPLEFILE");
2642 else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
2643 && buf[2] == 0x03 && buf[3] == 0x04){
2644 body->type = TYPEAPPLICATION;
2645 body->subtype = cpystr("ZIP");
2649 * if type was set above, but no encoding specified, go
2650 * ahead and make it BASE64...
2652 if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
2653 body->encoding = ENCBINARY;
2656 else{
2657 /* PROBLEM !!! */
2658 if(body->type == TYPEOTHER){
2659 body->type = TYPEAPPLICATION;
2660 body->subtype = cpystr("octet-stream");
2661 if(body->encoding == ENCOTHER)
2662 body->encoding = ENCBINARY;
2666 if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
2667 #if defined(DOS) || defined(OS2) /* for binary file detection */
2668 int lastchar = '\0';
2669 #define BREAKOUT 300 /* a value that a character can't be */
2670 #endif
2672 p = bol = buf;
2673 len = n;
2674 while (n--){
2675 /* Some people don't like quoted-printable caused by leading Froms */
2676 #ifdef ENCODE_FROMS
2677 Find_Froms(froms, dots, bmap, dmap, *p);
2678 #endif
2679 if(*p == '\n'){
2680 max_line = MAX(max_line, line_so_far + p - bol);
2681 bol = NULL; /* clear beginning of line */
2682 line_so_far = 0L; /* clear line count */
2683 #if defined(DOS) || defined(OS2)
2684 /* LF with no CR!! */
2685 if(lastchar != '\r') /* must be non-text data! */
2686 lastchar = BREAKOUT;
2687 #endif
2689 else if(*p & 0x80){
2690 eight_bit_chars++;
2692 else if(!*p){
2693 /* NULL found. Unless we're told otherwise, must be binary */
2694 if(body->type == TYPEOTHER){
2695 body->type = TYPEAPPLICATION;
2696 body->subtype = cpystr("octet-stream");
2700 * The "TYPETEXT" here handles the case that the NULL
2701 * comes from imported text generated by some external
2702 * editor that permits or inserts NULLS. Otherwise,
2703 * assume it's a binary segment...
2705 new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
2708 * Since we've already set encoding, count this as a
2709 * hi bit char and continue. The reason is that if this
2710 * is text, there may be a high percentage of encoded
2711 * characters, so base64 may get set below...
2713 if(body->type == TYPETEXT)
2714 eight_bit_chars++;
2715 else
2716 break;
2719 #if defined(DOS) || defined(OS2) /* for binary file detection */
2720 if(lastchar != BREAKOUT)
2721 lastchar = *p;
2722 #endif
2724 /* read another buffer in */
2725 if(n == 0){
2726 if(bol)
2727 line_so_far += p - bol;
2729 for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
2732 len += n;
2733 p = buf;
2735 else
2736 p++;
2739 * If there's no beginning-of-line pointer, then we must
2740 * have seen an end-of-line. Set bol to the start of the
2741 * new line...
2743 if(!bol)
2744 bol = p;
2746 #if defined(DOS) || defined(OS2) /* for binary file detection */
2747 /* either a lone \r or lone \n indicate binary file */
2748 if(lastchar == '\r' || lastchar == BREAKOUT){
2749 if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
2750 if(body->type == TYPEOTHER){
2751 body->type = TYPEAPPLICATION;
2752 body->subtype = cpystr("octet-stream");
2755 new_encoding = ENCBINARY;
2756 break;
2759 #endif
2763 /* stash away for later */
2764 so_attr(so, "maxline", long2string(max_line));
2766 if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
2768 * Since the type or encoding aren't set yet, fall thru a
2769 * series of tests to make sure an adequate type and
2770 * encoding are set...
2773 if(max_line >= 1000L){ /* 1000 comes from rfc821 */
2774 if(body->type == TYPEOTHER){
2776 * Since the types not set, then we didn't find a NULL.
2777 * If there's no NULL, then this is likely text. However,
2778 * since we can't be *completely* sure, we set it to
2779 * the generic type.
2781 body->type = TYPEAPPLICATION;
2782 body->subtype = cpystr("octet-stream");
2785 if(new_encoding != ENCBINARY)
2787 * As with NULL handling, if we're told it's text,
2788 * qp-encode it, else it gets base 64...
2790 new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
2793 if(eight_bit_chars == 0L){
2794 if(body->type == TYPEOTHER)
2795 body->type = TYPETEXT;
2797 if(new_encoding == ENCOTHER)
2798 new_encoding = ENC7BIT; /* short lines, no 8 bit */
2800 else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
2802 * The 30% threshold is based on qp encoded readability
2803 * on non-MIME UA's.
2805 if(body->type == TYPEOTHER)
2806 body->type = TYPETEXT;
2808 if(new_encoding != ENCBINARY)
2809 new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
2811 else{
2812 if(body->type == TYPEOTHER){
2813 body->type = TYPEAPPLICATION;
2814 body->subtype = cpystr("octet-stream");
2818 * Apply maximal encoding regardless of previous
2819 * setting. This segment's either not text, or is
2820 * unlikely to be readable with > 30% of the
2821 * text encoded anyway, so we might as well save space...
2823 new_encoding = ENCBINARY; /* > 30% 8 bit chars */
2827 #ifdef ENCODE_FROMS
2828 /* If there were From_'s at the beginning of a line or standalone dots */
2829 if((froms || dots) && new_encoding != ENCBINARY)
2830 new_encoding = ENC8BIT;
2831 #endif
2833 /* Set the subtype */
2834 if(body->subtype == NULL)
2835 body->subtype = cpystr(rfc822_default_subtype(body->type));
2837 if(body->encoding == ENCOTHER)
2838 body->encoding = new_encoding;
2840 fs_give((void **)&buf);
2842 if(we_cancel)
2843 cancel_busy_cue(-1);
2848 * Call this to set the charset of an attachment we have
2849 * created. If the attachment contains any non-ascii characters
2850 * then we'll set the charset to the passed in charset, otherwise
2851 * we'll make it us-ascii.
2853 void
2854 set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
2856 unsigned char c;
2857 int can_be_ascii = 1;
2858 STORE_S *so = (STORE_S *)body->contents.text.data;
2859 int we_cancel = 0;
2861 if(!body || body->type != TYPETEXT)
2862 return;
2864 we_cancel = busy_cue(NULL, NULL, 1);
2866 so_seek(so, 0L, 0);
2868 while(can_be_ascii && so_readc(&c, so))
2869 if(!c || c & 0x80)
2870 can_be_ascii--;
2872 if(can_be_ascii)
2873 set_parameter(&body->parameter, "charset", "US-ASCII");
2874 else if(charset && *charset && strucmp(charset, "US-ASCII"))
2875 set_parameter(&body->parameter, "charset", charset);
2876 else{
2878 * Else we don't know. There are non ascii characters but we either
2879 * don't have a charset to set it to or that charset is just us_ascii,
2880 * which is impossible. So we label it unknown. An alternative would
2881 * have been to strip the high bits instead and label it ascii.
2883 set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
2886 if(we_cancel)
2887 cancel_busy_cue(-1);
2892 * since encoding happens on the way out the door, this is basically
2893 * just needed to handle TYPEMULTIPART
2895 void
2896 pine_encode_body (struct mail_bodystruct *body)
2898 PART *part;
2900 dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
2901 if (body) switch (body->type) {
2902 char *freethis;
2904 case TYPEMULTIPART: /* multi-part */
2905 if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
2906 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
2908 snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
2909 (long) getpid ());
2910 tmp[sizeof(tmp)-1] = '\0';
2911 set_parameter(&body->parameter, "boundary", tmp);
2914 if(freethis)
2915 fs_give((void **) &freethis);
2917 part = body->nested.part; /* encode body parts */
2918 do pine_encode_body (&part->body);
2919 while ((part = part->next) != NULL); /* until done */
2920 break;
2922 case TYPETEXT :
2924 * If the part is text we edited, then it is UTF-8.
2925 * The user may be asking us to send it as something else
2926 * or we may want to downconvert to a more-specific characterset.
2927 * Mark it for conversion here so the right MIME header's written.
2928 * Do conversion pine_rfc822_output_body.
2929 * Attachments are left as is.
2931 if(body->contents.text.data
2932 && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
2933 char *charset, *posting_charset, *lp;
2935 if(!((charset = parameter_val(body->parameter, "charset"))
2936 && !strucmp(charset, UNKNOWN_CHARSET))
2937 && (posting_charset = posting_characterset(body, charset, MsgBody))){
2939 set_parameter(&body->parameter, "charset", posting_charset);
2942 * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
2943 * and doesn't use anything but ASCII characters.
2944 * Why is it not ENC7BIT already? Because when we set the encoding
2945 * in set_mime_type_by_grope we were groping through UTF-8 text
2946 * not 2022 text. Not only that, but we didn't know at that point
2947 * that it wouldn't stay UTF-8 when we sent it, which would require
2948 * encoding.
2950 if(!strucmp(posting_charset, "iso-2022-jp")
2951 && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
2952 && strlen(lp) < 4)
2953 body->encoding = ENC7BIT;
2956 if(charset)
2957 fs_give((void **)&charset);
2960 break;
2962 /* case MESSAGE: */ /* here for documentation */
2963 /* Encapsulated messages are always treated as text objects at this point.
2964 This means that you must replace body->contents.msg with
2965 body->contents.text, which probably involves copying
2966 body->contents.msg.text to body->contents.text */
2967 default: /* all else has some encoding */
2969 * but we'll delay encoding it until the message is on the way
2970 * into the mail slot...
2972 break;
2978 * pine_header_line - simple wrapper around c-client call to contain
2979 * repeated code, and to write fcc if required.
2982 pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
2983 int writehdr, int localcopy)
2985 int ret = 1;
2986 int flags;
2987 int big = 10000;
2988 char *value, *folded = NULL, *cs;
2989 char *converted;
2991 if(!text)
2992 return 1;
2994 converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
2996 if(converted){
2997 if(cs && !strucmp(cs, "us-ascii"))
2998 value = converted;
2999 else
3000 value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
3001 (unsigned char *) converted, cs,
3002 encode_whole_header(field, header));
3004 if(value && value == converted){ /* no encoding was done, have to fold */
3005 int fold_by, len;
3006 char *actual_field;
3008 len = ((header && header->env && header->env->remail)
3009 ? strlen("ReSent-") : 0) +
3010 (field ? strlen(field) : 0) + 2;
3012 actual_field = (char *)fs_get((len+1) * sizeof(char));
3013 snprintf(actual_field, len+1, "%s%s: ",
3014 (header && header->env && header->env->remail) ? "ReSent-" : "",
3015 field ? field : "");
3016 actual_field[len] = '\0';
3019 * We were folding everything except message-id, but that wasn't
3020 * sufficient. Since 822 only allows folding where linear-white-space
3021 * is allowed we'd need a smarter folder than "fold" to do it. So,
3022 * instead of inventing that smarter folder (which would have to
3023 * know 822 syntax)
3025 * We could just alloc space and copy the actual_field followed by
3026 * the value into it, but since that's what fold does anyway we'll
3027 * waste some cpu time and use fold with a big fold parameter.
3029 * We upped the references folding from 75 to 256 because we were
3030 * encountering longer-than-75 message ids, and to break one line
3031 * in references is to break them all.
3033 * Also, some users are adding long text without spaces to the subject
3034 * line (e.g. URLs) so we up that number too. For the moment this is
3035 * going to 512 (a url that is about 6 lines long.)
3037 flags = FLD_CRLF;
3038 if(field && !strucmp("Subject", field)){
3039 fold_by = 75;
3040 flags |= FLD_NEXTSPC;
3042 else if(field && !strucmp("References", field))
3043 fold_by = 256;
3044 else
3045 fold_by = big;
3047 folded = fold(value, fold_by, big, actual_field, " ", flags);
3049 if(actual_field)
3050 fs_give((void **)&actual_field);
3052 else if(value){ /* encoding was done */
3053 RFC822BUFFER rbuf;
3054 size_t ll;
3057 * rfc1522_encode already inserted continuation lines and did
3058 * the necessary folding so we don't have to do it. Let
3059 * rfc822_header_line add the trailing crlf and the resent- if
3060 * necessary. The 20 could actually be a 12.
3062 ll = strlen(field) + strlen(value) + 20;
3063 folded = (char *) fs_get(ll * sizeof(char));
3064 *folded = '\0';
3065 rbuf.f = dummy_soutr;
3066 rbuf.s = NULL;
3067 rbuf.beg = folded;
3068 rbuf.cur = folded;
3069 rbuf.end = folded+ll-1;
3070 rfc822_output_header_line(&rbuf, field,
3071 (header && header->env && header->env->remail) ? LONGT : 0L, value);
3072 *rbuf.cur = '\0';
3075 if(value && folded){
3076 if(writehdr && f)
3077 ret = (*f)(s, folded);
3079 if(ret && localcopy && lmc.so && !lmc.all_written)
3080 ret = so_puts(lmc.so, folded);
3083 if(folded)
3084 fs_give((void **)&folded);
3086 if(converted && converted != text)
3087 fs_give((void **) &converted);
3089 else
3090 ret = 0;
3092 return(ret);
3097 * Do appropriate encoding of text header lines.
3098 * For some field types (those that consist of 822 *text) we just encode
3099 * the whole thing. For structured fields we encode only within comments
3100 * if possible.
3102 * Args d -- Destination buffer if needed. (tmp_20k_buf)
3103 * s -- Source string.
3104 * charset -- Charset to encode with.
3105 * encode_all -- If set, encode the whole string. If not, try to encode
3106 * only within comments if possible.
3108 * Returns S is returned if no encoding is done. D is returned if encoding
3109 * was needed.
3111 char *
3112 encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
3114 char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
3115 int in_comment = 0;
3117 if(!s)
3118 return((char *)s);
3120 if(dlen < SIZEOF_20KBUF)
3121 alpine_panic("bad call to encode_header_value");
3123 if(!encode_all){
3125 * We don't have to worry about keeping track of quoted-strings because
3126 * none of these fields which aren't addresses contain quoted-strings.
3127 * We do keep track of escaped parens inside of comments and comment
3128 * nesting.
3130 p = d+7000;
3131 for(q = (char *)s; *q; q++){
3132 switch(*q){
3133 case LPAREN:
3134 if(in_comment++ == 0)
3135 start_of_comment = q;
3137 break;
3139 case RPAREN:
3140 if(--in_comment == 0){
3141 /* encode the comment, excluding the outer parens */
3142 if(p-d < dlen-1)
3143 *p++ = LPAREN;
3145 *q = '\0';
3146 r = rfc1522_encode(d+14000, dlen-14000,
3147 (unsigned char *)start_of_comment+1,
3148 charset);
3149 if(r != start_of_comment+1)
3150 value = d+7000; /* some encoding was done */
3152 start_of_comment = NULL;
3153 if(r)
3154 sstrncpy(&p, r, dlen-1-(p-d));
3156 *q = RPAREN;
3157 if(p-d < dlen-1)
3158 *p++ = *q;
3160 else if(in_comment < 0){
3161 in_comment = 0;
3162 if(p-d < dlen-1)
3163 *p++ = *q;
3166 break;
3168 case BSLASH:
3169 if(!in_comment && *(q+1)){
3170 if(p-d < dlen-2){
3171 *p++ = *q++;
3172 *p++ = *q;
3176 break;
3178 default:
3179 if(!in_comment && p-d < dlen-1)
3180 *p++ = *q;
3182 break;
3186 if(value){
3187 /* Unterminated comment (wasn't really a comment) */
3188 if(start_of_comment)
3189 sstrncpy(&p, start_of_comment, dlen-1-(p-d));
3191 *p = '\0';
3196 * We have to check if there is anything that needs to be encoded that
3197 * wasn't in a comment. If there is, we'd better just start over and
3198 * encode the whole thing. So, if no encoding has been done within
3199 * comments, or if encoding is needed both within and outside of
3200 * comments, then we encode the whole thing. Otherwise, go with
3201 * the version that has only comments encoded.
3203 if(!value || rfc1522_encode(d, dlen,
3204 (unsigned char *)value, charset) != value)
3205 return(rfc1522_encode(d, dlen, s, charset));
3206 else{
3207 strncpy(d, value, dlen-1);
3208 d[dlen-1] = '\0';
3209 return(d);
3215 * pine_address_line - write a header field containing addresses,
3216 * one by one (so there's no buffer limit), and
3217 * wrapping where necessary.
3218 * Note: we use c-client functions to properly build the text string,
3219 * but have to screw around with pointers to fool c-client functions
3220 * into not blatting all the text into a single buffer. Yeah, I know.
3223 pine_address_line(char *field, METAENV *header, struct mail_address *alist,
3224 soutr_t f, void *s, int writehdr, int localcopy)
3226 char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
3227 size_t alloced = 0, sz;
3228 char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
3229 char *converted, *cs;
3230 ADDRESS *atmp;
3231 int i, count;
3232 int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
3233 RFC822BUFFER rbuf;
3234 static char comma[] = ", ";
3235 static char end_group[] = ";";
3236 #define no_comma (&comma[1])
3238 if(!alist) /* nothing in field! */
3239 return(1);
3241 if(!alist->host && alist->mailbox){ /* c-client group convention */
3242 in_group++;
3243 was_start_of_group++;
3244 /* encode mailbox of group */
3245 mtmp = alist->mailbox;
3246 if(mtmp){
3247 snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
3248 buftmp[sizeof(buftmp)-1] = '\0';
3249 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3250 if(converted){
3251 alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3252 (unsigned char *) converted, cs));
3253 if(converted && converted != buftmp)
3254 fs_give((void **) &converted);
3256 else{
3257 failed++;
3258 goto bail_out;
3262 else
3263 mtmp = NULL;
3265 ptmp = alist->personal; /* remember personal name */
3266 /* make sure personal name is encoded */
3267 if(ptmp){
3268 snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
3269 buftmp[sizeof(buftmp)-1] = '\0';
3270 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3271 if(converted){
3272 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3273 (unsigned char *) converted, cs));
3274 if(converted && converted != buftmp)
3275 fs_give((void **) &converted);
3277 else{
3278 failed++;
3279 goto bail_out;
3283 atmp = alist->next;
3284 alist->next = NULL; /* digest only first address! */
3286 /* use automatic buffer unless it isn't big enough */
3287 if((alloced = est_size(alist)) > sizeof(tmp)){
3288 tmpptr = (char *)fs_get(alloced);
3289 sz = alloced;
3291 else{
3292 tmpptr = tmp;
3293 sz = sizeof(tmp);
3296 rbuf.f = dummy_soutr;
3297 rbuf.s = NULL;
3298 rbuf.beg = tmpptr;
3299 rbuf.cur = tmpptr;
3300 rbuf.end = tmpptr+sz-1;
3301 rfc822_output_address_line(&rbuf, field,
3302 (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
3303 *rbuf.cur = '\0';
3305 alist->next = atmp; /* restore pointer to next addr */
3307 if(alist->personal && alist->personal != ptmp)
3308 fs_give((void **) &alist->personal);
3310 alist->personal = ptmp; /* in case it changed, restore name */
3312 if(mtmp){
3313 if(alist->mailbox && alist->mailbox != mtmp)
3314 fs_give((void **) &alist->mailbox);
3316 alist->mailbox = mtmp;
3319 if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
3320 count -= 2;
3321 tmpptr[count] = '\0';
3325 * If there is no sending_stream and we are writing the Lcc header,
3326 * then we are piping it to sendmail -t which expects it to be a bcc,
3327 * not lcc.
3329 * When we write it to the fcc or postponed (the lmc.so),
3330 * we want it to be lcc, not bcc, so we put it back.
3332 if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
3333 fix_lcc = 1;
3335 if(writehdr && f && *tmpptr){
3336 if(fix_lcc)
3337 tmpptr[0] = 'b';
3339 failed = !(*f)(s, tmpptr);
3340 if(fix_lcc)
3341 tmpptr[0] = 'L';
3343 if(failed)
3344 goto bail_out;
3347 if(localcopy && lmc.so &&
3348 !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
3349 goto bail_out;
3351 for(alist = atmp; alist; alist = alist->next){
3352 delim = comma;
3353 /* account for c-client's representation of group names */
3354 if(in_group){
3355 if(!alist->host){ /* end of group */
3356 in_group = 0;
3357 was_start_of_group = 0;
3359 * Rfc822_write_address no longer writes out the end of group
3360 * unless the whole group address is passed to it, so we do
3361 * it ourselves.
3363 delim = end_group;
3365 else if(!localcopy || !lmc.so || lmc.all_written)
3366 continue;
3368 /* start of new group, print phrase below */
3369 else if(!alist->host && alist->mailbox){
3370 in_group++;
3371 was_start_of_group++;
3374 /* no comma before first address in group syntax */
3375 if(was_start_of_group && alist->host){
3376 delim = no_comma;
3377 was_start_of_group = 0;
3380 /* write delimiter */
3381 if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
3382 || (localcopy && lmc.so && !lmc.all_written
3383 && !so_puts(lmc.so, delim)))
3384 goto bail_out;
3386 ptmp = alist->personal; /* remember personal name */
3387 snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
3388 buftmp[sizeof(buftmp)-1] = '\0';
3389 converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
3390 if(converted){
3391 alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
3392 (unsigned char *) converted, cs));
3393 if(converted && converted != buftmp)
3394 fs_give((void **) &converted);
3396 else{
3397 failed++;
3398 goto bail_out;
3401 atmp = alist->next;
3402 alist->next = NULL; /* tie off linked list */
3403 if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
3404 alloced = i;
3405 sz = alloced;
3406 fs_resize((void **)&tmpptr, alloced);
3409 *tmpptr = '\0';
3410 /* make sure we don't write out group end with rfc822_write_address */
3411 if(alist->host || alist->mailbox){
3412 rbuf.f = dummy_soutr;
3413 rbuf.s = NULL;
3414 rbuf.beg = tmpptr;
3415 rbuf.cur = tmpptr;
3416 rbuf.end = tmpptr+sz-1;
3417 rfc822_output_address_list(&rbuf, alist, 0L, NULL);
3418 *rbuf.cur = '\0';
3421 alist->next = atmp; /* restore next pointer */
3423 if(alist->personal && alist->personal != ptmp)
3424 fs_give((void **) &alist->personal);
3426 alist->personal = ptmp; /* in case it changed, restore name */
3429 * BUG
3430 * With group syntax addresses we no longer have two identical
3431 * streams of output. Instead, for the fcc/postpone copy we include
3432 * all of the addresses inside the :; of the group, and for the
3433 * mail we're sending we don't include them. That means we aren't
3434 * correctly keeping track of the column to wrap in, below. That is,
3435 * we are keeping track of the fcc copy but we aren't keeping track
3436 * of the regular copy. It could result in too long or too short
3437 * lines. Should almost never come up since group addresses are almost
3438 * never followed by other addresses in the same header, and even
3439 * when they are, you have to go out of your way to get the headers
3440 * messed up.
3442 if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
3443 count = i + 4;
3444 if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
3445 || (localcopy && lmc.so && !lmc.all_written &&
3446 !so_puts(lmc.so, "\015\012 ")))
3447 goto bail_out;
3449 else
3450 count += i + 2;
3452 if(((!in_group || was_start_of_group)
3453 && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
3454 || (localcopy && lmc.so && !lmc.all_written
3455 && *tmpptr && !so_puts(lmc.so, tmpptr)))
3456 goto bail_out;
3459 bail_out:
3460 if(tmpptr && tmpptr != tmp)
3461 fs_give((void **)&tmpptr);
3463 if(failed)
3464 return(0);
3466 return((writehdr && f ? (*f)(s, "\015\012") : 1)
3467 && ((localcopy && lmc.so
3468 && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
3473 * mutated pine version of c-client's rfc822_header() function.
3474 * changed to call pine-wrapped header and address functions
3475 * so we don't have to limit the header size to a fixed buffer.
3476 * This function also calls pine's body_header write function
3477 * because encoding is delayed until output_body() is called.
3479 long
3480 pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3482 PINEFIELD *pf;
3483 int j;
3485 if(header->env->remail){ /* if remailing */
3486 long i = strlen (header->env->remail);
3487 if(i > 4 && header->env->remail[i-4] == '\015')
3488 header->env->remail[i-2] = '\0'; /* flush extra blank line */
3490 if((f && !(*f)(s, header->env->remail))
3491 || (lmc.so && !lmc.all_written
3492 && !so_puts(lmc.so, header->env->remail)))
3493 return(0L); /* start with remail header */
3496 j = 0;
3497 for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
3498 switch(pf->type){
3500 * Warning: This is confusing. The 2nd to last argument used to
3501 * be just pf->writehdr. We want Bcc lines to be written out
3502 * if we are handing off to a sendmail temp file but not if we
3503 * are talking smtp, so bcc's writehdr is set to 0 and
3504 * pine_address_line was sending if writehdr OR !sending_stream.
3505 * That works as long as we want to write everything when
3506 * !sending_stream (an mta handoff to sendmail). But then we
3507 * added the undisclosed recipients line which should only get
3508 * written if writehdr is set, and not when we pass to a
3509 * sendmail temp file. So pine_address_line has been changed
3510 * so it bases its decision solely on the writehdr passed to it,
3511 * and the logic that worries about Bcc and sending_stream
3512 * was moved up to the caller (here) to decide when to set it.
3514 * So we have:
3515 * undisclosed recipients:; This will just be written
3516 * if writehdr was set and not
3517 * otherwise, nothing magical.
3518 *** We may want to change this, because sendmail -t doesn't handle
3519 *** the empty group syntax well unless it has been configured to
3520 *** do so. It isn't configured by default, or in any of the
3521 *** sendmail v8 configs. So we may want to not write this line
3522 *** if we're doing an mta_handoff (!sending_stream).
3524 * !sending_stream (which means a handoff to a sendmail -t)
3525 * bcc or lcc both set the arg so they'll get written
3526 * (There is also Lcc hocus pocus in pine_address_line
3527 * which converts the Lcc: to Bcc: for sendmail
3528 * processing.)
3529 * sending_stream (which means an smtp handoff)
3530 * bcc and lcc will never have writehdr set, so
3531 * will never be written (They both do have rcptto set,
3532 * so they both do cause RCPT TO commands.)
3534 * The localcopy is independent of sending_stream and is just
3535 * written if it is set for all of these.
3537 case Address:
3538 if(!pine_address_line(pf->name,
3539 header,
3540 pf->addr ? *pf->addr : NULL,
3543 (!strucmp("bcc",pf->name ? pf->name : "")
3544 || !strucmp("Lcc",pf->name ? pf->name : ""))
3545 ? !sending_stream
3546 : pf->writehdr,
3547 pf->localcopy))
3548 return(0L);
3550 break;
3552 case Fcc:
3553 case FreeText:
3554 case Subject:
3555 if(!pine_header_line(pf->name, header,
3556 pf->text ? *pf->text : NULL,
3557 f, s, pf->writehdr, pf->localcopy))
3558 return(0L);
3560 break;
3562 default:
3563 q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
3564 pf->name);
3565 break;
3570 #if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
3572 * Add comforting "X-" header line indicating what sort of
3573 * authenticity the receiver can expect...
3575 if(F_OFF(F_DISABLE_SENDER, ps_global)){
3576 NETMBX netmbox;
3577 char sstring[MAILTMPLEN], *label; /* place to write */
3578 MAILSTREAM *m;
3579 int i, anonymous = 1;
3581 for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
3582 m = ps_global->s_pool.streams[i];
3583 if(m && sp_flagged(m, SP_LOCKED)
3584 && mail_valid_net_parse(m->mailbox, &netmbox)
3585 && !netmbox.anoflag)
3586 anonymous = 0;
3589 if(!anonymous){
3590 char last_char = netmbox.host[strlen(netmbox.host) - 1],
3591 *user = (*netmbox.user)
3592 ? netmbox.user
3593 : cached_user_name(netmbox.mailbox);
3594 snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
3595 isdigit((unsigned char)last_char) ? "[" : "",
3596 netmbox.host,
3597 isdigit((unsigned char) last_char) ? "]" : "");
3598 sstring[sizeof(sstring)-1] = '\0';
3599 label = "X-X-Sender"; /* Jeez. */
3600 if(F_ON(F_USE_SENDER_NOT_X,ps_global))
3601 label += 4;
3603 else{
3604 strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
3605 sstring[sizeof(sstring)-1] = '\0';
3606 label = "X-Warning";
3609 if(!pine_header_line(label, header, sstring, f, s, 1, 1))
3610 return(0L);
3612 #endif
3614 if(body && !header->env->remail){ /* not if remail or no body */
3615 if((f && !(*f)(s, MIME_VER))
3616 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
3617 || !pine_write_body_header(body, f, s))
3618 return(0L);
3620 else{ /* write terminating newline */
3621 if((f && !(*f)(s, "\015\012"))
3622 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
3623 return(0L);
3626 return(1L);
3631 * pine_rfc822_output - pine's version of c-client call. Necessary here
3632 * since we're not using its structures as intended!
3634 long
3635 pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
3637 int we_cancel = 0;
3638 long retval;
3640 dprint((4, "-- pine_rfc822_output\n"));
3642 we_cancel = busy_cue(NULL, NULL, 1);
3643 pine_encode_body(body); /* encode body as necessary */
3644 /* build and output RFC822 header, output body */
3645 retval = pine_rfc822_header(header, body, f, s)
3646 && (body ? pine_rfc822_output_body(body, f, s) : 1L);
3648 if(we_cancel)
3649 cancel_busy_cue(-1);
3651 return(retval);
3656 * post_rfc822_output - cloak for pine's 822 output routine. Since
3657 * we can't pass opaque envelope thru c-client posting
3658 * logic, we need to wrap the real output inside
3659 * something that c-client knows how to call.
3661 long
3662 post_rfc822_output(char *tmp,
3663 ENVELOPE *env,
3664 struct mail_bodystruct *body,
3665 soutr_t f,
3666 void *s,
3667 long int ok8bit)
3669 return(pine_rfc822_output(send_header, body, f, s));
3674 * posting_characterset- determine what transliteration is reasonable
3675 * for posting the given non-ascii messsage data.
3677 * preferred_charset is the charset the original data was labeled in.
3678 * If we can keep that we do.
3680 * Returns: always returns the preferred character set.
3682 char *
3683 posting_characterset(void *data, char *preferred_charset, MsgPart mp)
3685 unsigned long *charsetmap = NULL;
3686 unsigned long validbitmap;
3687 static char *ascii = "US-ASCII";
3688 static char *utf8 = "UTF-8";
3689 int notcjk = 0;
3691 if(!ps_global->post_utf8){
3692 validbitmap = 0;
3694 if(mp == HdrText){
3695 char *text = NULL;
3696 UCS *ucs = NULL, *ucsp;
3698 text = (char *) data;
3700 /* convert text in header to UCS characters */
3701 if(text)
3702 ucsp = ucs = utf8_to_ucs4_cpystr(text);
3704 if(!(ucs && *ucs))
3705 return(ascii);
3708 * After the while loop is done the validbitmap has
3709 * a 1 bit for all the character sets that can
3710 * represent all of the characters of this header.
3712 charsetmap = init_charsetchecker(preferred_charset);
3714 if(!charsetmap)
3715 return(utf8);
3717 validbitmap = ~0;
3718 while((validbitmap & ~0x1) && (*ucsp)){
3719 if(*ucsp > 0xffff){
3720 fs_give((void **) &ucs);
3721 return(utf8);
3724 validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
3727 fs_give((void **) &ucs);
3729 notcjk = validbitmap & 0x1;
3730 validbitmap &= ~0x1;
3732 if(!validbitmap)
3733 return(utf8);
3735 else{
3736 struct mail_bodystruct *body = NULL;
3737 STORE_S *the_text = NULL;
3738 int outchars;
3739 unsigned char c;
3740 UCS ucs;
3741 CBUF_S cbuf;
3743 cbuf.cbuf[0] = '\0';
3744 cbuf.cbufp = cbuf.cbuf;
3745 cbuf.cbufend = cbuf.cbuf;
3747 body = (struct mail_bodystruct *) data;
3749 if(body && body->type == TYPEMULTIPART)
3750 body = &body->nested.part->body;
3752 if(body && body->type == TYPETEXT)
3753 the_text = (STORE_S *) body->contents.text.data;
3755 if(!the_text)
3756 return(ascii);
3758 so_seek(the_text, 0L, 0); /* rewind */
3760 charsetmap = init_charsetchecker(preferred_charset);
3762 if(!charsetmap)
3763 return(utf8);
3765 validbitmap = ~0;
3768 * Read a stream of UTF-8 characters from the_text
3769 * and convert them to UCS-4 characters for the translatable
3770 * test.
3772 while((validbitmap & ~0x1) && so_readc(&c, the_text)){
3773 if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
3774 /* got a ucs character */
3775 if(ucs > 0xffff)
3776 return(utf8);
3778 validbitmap &= charsetmap[(unsigned long) ucs];
3782 notcjk = validbitmap & 0x1;
3783 validbitmap &= ~0x1;
3785 if(!validbitmap)
3786 return(utf8);
3789 /* user chooses something other than UTF-8 */
3790 if(strucmp(ps_global->posting_charmap, utf8)){
3792 * If we're to post in other than UTF-8, and it can be
3793 * transliterated without losing fidelity, do it.
3794 * Else, use UTF-8.
3797 /* if ascii works, always use that */
3798 if(representable_in_charset(validbitmap, ascii))
3799 return(ascii);
3801 /* does the user's posting character set work? */
3802 if(representable_in_charset(validbitmap, ps_global->posting_charmap))
3803 return(ps_global->posting_charmap);
3805 /* this is the charset the message we are replying to was in */
3806 if(preferred_charset
3807 && strucmp(preferred_charset, ascii)
3808 && representable_in_charset(validbitmap, preferred_charset))
3809 return(preferred_charset);
3811 /* else, use UTF-8 */
3814 /* user chooses nothing, going with the default */
3815 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3816 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3817 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3818 char *most_preferred;
3821 * In this case the user didn't specify a posting character set
3822 * and we will choose the most-specific one from our list.
3825 /* ascii is best */
3826 if(representable_in_charset(validbitmap, ascii))
3827 return(ascii);
3829 /* Can we keep the original from the message we're replying to? */
3830 if(preferred_charset
3831 && strucmp(preferred_charset, ascii)
3832 && representable_in_charset(validbitmap, preferred_charset))
3833 return(preferred_charset);
3835 /* choose the best of the rest */
3836 most_preferred = most_preferred_charset(validbitmap);
3837 if(!most_preferred)
3838 return(utf8);
3841 * If the text we're labeling contains something like
3842 * smart quotes but no CJK characters, then instead of
3843 * labeling it as ISO-2022-JP we want to use UTF-8.
3845 if(notcjk){
3846 const CHARSET *cs;
3848 cs = utf8_charset(most_preferred);
3849 if(!cs
3850 || cs->script == SC_CHINESE_SIMPLIFIED
3851 || cs->script == SC_CHINESE_TRADITIONAL
3852 || cs->script == SC_JAPANESE
3853 || cs->script == SC_KOREAN)
3854 return(utf8);
3857 return(most_preferred);
3859 /* user explicitly chooses UTF-8 */
3860 else{
3861 /* if ascii works, always use that */
3862 if(representable_in_charset(validbitmap, ascii))
3863 return(ascii);
3865 /* else, use UTF-8 */
3870 return(utf8);
3874 static char **charsetlist = NULL;
3875 static int items_in_charsetlist = 0;
3876 static unsigned long *charsetmap = NULL;
3878 static char *downgrades[] = {
3879 "US-ASCII",
3880 "ISO-8859-15",
3881 "ISO-8859-1",
3882 "ISO-8859-2",
3883 "VISCII",
3884 "KOI8-R",
3885 "KOI8-U",
3886 "ISO-8859-7",
3887 "ISO-8859-6",
3888 "ISO-8859-8",
3889 "TIS-620",
3890 "ISO-2022-JP",
3891 "GB2312",
3892 "BIG5",
3893 "EUC-KR"
3897 unsigned long *
3898 init_charsetchecker(char *preferred_charset)
3900 int i, count = 0, reset = 0;
3901 char *ascii = "US-ASCII";
3902 char *utf8 = "UTF-8";
3905 * When user doesn't set a posting character set posting_charmap ends up
3906 * set to UTF-8. That also happens if user sets UTF-8 explicitly.
3907 * That's where the strange set of if-else's come from.
3910 /* user chooses something other than UTF-8 */
3911 if(strucmp(ps_global->posting_charmap, utf8)){
3912 count++; /* US-ASCII */
3913 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
3914 reset++;
3916 /* if posting_charmap is valid, include it in list */
3917 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3918 && strucmp(ps_global->posting_charmap, ascii)
3919 && strucmp(ps_global->posting_charmap, utf8)
3920 && utf8_charset(ps_global->posting_charmap)){
3921 count++;
3922 if(!reset
3923 && (items_in_charsetlist < count
3924 || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
3925 reset++;
3928 if(preferred_charset && preferred_charset[0]
3929 && strucmp(preferred_charset, ascii)
3930 && strucmp(preferred_charset, utf8)
3931 && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
3932 count++;
3933 if(!reset
3934 && (items_in_charsetlist < count
3935 || strucmp(charsetlist[count-1], preferred_charset)))
3936 reset++;
3939 if(items_in_charsetlist != count)
3940 reset++;
3942 if(reset){
3943 if(charsetlist)
3944 free_list_array(&charsetlist);
3946 items_in_charsetlist = count;
3947 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
3949 i = 0;
3950 charsetlist[i++] = cpystr(ascii);
3952 if(ps_global->posting_charmap && ps_global->posting_charmap[0]
3953 && strucmp(ps_global->posting_charmap, ascii)
3954 && strucmp(ps_global->posting_charmap, utf8)
3955 && utf8_charset(ps_global->posting_charmap))
3956 charsetlist[i++] = cpystr(ps_global->posting_charmap);
3958 if(preferred_charset && preferred_charset[0]
3959 && strucmp(preferred_charset, ascii)
3960 && strucmp(preferred_charset, utf8)
3961 && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
3962 charsetlist[i++] = cpystr(preferred_charset);
3964 charsetlist[i] = NULL;
3967 /* user chooses nothing, going with the default */
3968 else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
3969 && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
3970 && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
3971 int add_preferred = 0;
3973 /* does preferred_charset have to be added to the list? */
3974 if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
3975 add_preferred = 1;
3976 for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
3977 if(!strucmp(downgrades[i], preferred_charset))
3978 add_preferred = 0;
3981 if(add_preferred){
3982 /* existing list is right size already */
3983 if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
3984 /* just check to see if last list item is the preferred_charset */
3985 if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
3986 /* no, fix it */
3987 reset++;
3988 fs_give((void **) &charsetlist[items_in_charsetlist-1]);
3989 charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
3992 else{
3993 reset++;
3994 if(charsetlist)
3995 free_list_array(&charsetlist);
3997 count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
3998 items_in_charsetlist = count;
3999 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4000 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4001 charsetlist[i] = cpystr(downgrades[i]);
4003 charsetlist[i++] = cpystr(preferred_charset);
4004 charsetlist[i] = NULL;
4007 else{
4008 /* if list is same size as downgrades, consider it good */
4009 if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
4010 reset++;
4012 if(reset){
4013 if(charsetlist)
4014 free_list_array(&charsetlist);
4016 count = sizeof(downgrades)/sizeof(downgrades[0]);
4017 items_in_charsetlist = count;
4018 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4019 for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
4020 charsetlist[i] = cpystr(downgrades[i]);
4022 charsetlist[i] = NULL;
4026 /* user explicitly chooses UTF-8 */
4027 else{
4028 /* include possibility of ascii even if they explicitly ask for UTF-8 */
4029 count++; /* US-ASCII */
4030 if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
4031 reset++;
4033 if(items_in_charsetlist != count)
4034 reset++;
4036 if(reset){
4037 if(charsetlist)
4038 free_list_array(&charsetlist);
4040 /* the list is just ascii and nothing else */
4041 items_in_charsetlist = count;
4042 charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
4044 i = 0;
4045 charsetlist[i++] = cpystr(ascii);
4046 charsetlist[i] = NULL;
4051 if(reset){
4052 if(charsetmap)
4053 fs_give((void **) &charsetmap);
4055 if(charsetlist)
4056 charsetmap = utf8_csvalidmap(charsetlist);
4059 return(charsetmap);
4063 /* total reset */
4064 void
4065 free_charsetchecker(void)
4067 if(charsetlist)
4068 free_list_array(&charsetlist);
4070 items_in_charsetlist = 0;
4072 if(charsetmap)
4073 fs_give((void **) &charsetmap);
4078 representable_in_charset(unsigned long validbitmap, char *charset)
4080 int i, done = 0, ret = 0;
4081 unsigned long j;
4083 if(!(charset && charset[0]))
4084 return ret;
4086 if(!strucmp(charset, "UTF-8"))
4087 return 1;
4089 for(i = 0; !done && i < items_in_charsetlist; i++){
4090 if(!strucmp(charset, charsetlist[i])){
4091 j = 1;
4092 j <<= (i+1);
4093 done++;
4094 if(validbitmap & j)
4095 ret = 1;
4099 return ret;
4103 char *
4104 most_preferred_charset(unsigned long validbitmap)
4106 unsigned long bm;
4107 unsigned long rm;
4108 int index;
4110 if(!(validbitmap && items_in_charsetlist > 0))
4111 return("UTF-8");
4113 /* careful, find_rightmost_bit modifies the bitmap */
4114 bm = validbitmap;
4115 rm = find_rightmost_bit(&bm);
4116 index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
4118 return(charsetlist[index]);
4123 * Set parameter to new value.
4125 void
4126 set_parameter(PARAMETER **param, char *paramname, char *new_value)
4128 PARAMETER *pm;
4130 if(!param || !(paramname && *paramname))
4131 return;
4133 if(*param == NULL){
4134 pm = (*param) = mail_newbody_parameter();
4135 pm->attribute = cpystr(paramname);
4137 else{
4138 int nomatch;
4140 for(pm = *param;
4141 (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
4142 pm = pm->next)
4143 ;/* searching for paramname parameter */
4145 if(nomatch){ /* add charset parameter */
4146 pm->next = mail_newbody_parameter();
4147 pm = pm->next;
4148 pm->attribute = cpystr(paramname);
4150 /* else pm is existing paramname parameter */
4153 if(pm){
4154 if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
4155 if(pm->value)
4156 fs_give((void **) &pm->value);
4158 if(new_value)
4159 pm->value = cpystr(new_value);
4165 /*----------------------------------------------------------------------
4166 Remove the leading digits from SMTP error messages
4167 -----*/
4168 char *
4169 tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
4171 while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
4172 (*error == '.' && isdigit((unsigned char)*(error+1))))
4173 error++;
4175 snprintf(outbuf, outbuflen, printstring, error);
4176 outbuf[outbuflen-1] = '\0';
4177 return(outbuf);
4182 * Local globals pine's body output routine needs
4184 static soutr_t l_f;
4185 static TCPSTREAM *l_stream;
4186 static unsigned c_in_buf = 0;
4189 * def to make our pipe write's more friendly
4191 #ifdef PIPE_MAX
4192 #if PIPE_MAX > 20000
4193 #undef PIPE_MAX
4194 #endif
4195 #endif
4197 #ifndef PIPE_MAX
4198 #define PIPE_MAX 1024
4199 #endif
4203 * l_flust_net - empties gf_io terminal function's buffer
4206 l_flush_net(int force)
4208 if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
4209 char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
4211 tmp_20k_buf[c_in_buf] = '\0';
4212 if(!force){
4214 * The start of each write is expected to be the start of a
4215 * "record" (i.e., a CRLF terminated line). Make sure that is true
4216 * else we might screw up SMTP dot quoting...
4218 for(p = tmp_20k_buf, lp = NULL;
4219 (p = strstr(p, "\015\012")) != NULL;
4220 lp = (p += 2))
4224 if(!lp && c_in_buf > 2) /* no CRLF! */
4225 for(p = &tmp_20k_buf[c_in_buf] - 2;
4226 p > &tmp_20k_buf[0] && *p == '.';
4227 p--) /* find last non-dot */
4230 if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
4231 /* snippet remains */
4232 c = *lp;
4233 *lp = '\0';
4237 if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
4238 || (lmc.so && !lmc.all_written
4239 && !(lmc.text_only && lmc.text_written)
4240 && !so_puts(lmc.so, tmp_20k_buf)))
4241 return(0);
4243 c_in_buf = 0;
4244 if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
4245 while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
4246 c_in_buf++, lp++;
4249 return(1);
4254 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
4258 l_putc(int c)
4260 if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
4261 tmp_20k_buf[c_in_buf++] = (char) c;
4263 return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
4269 * pine_rfc822_output_body - pine's version of c-client call. Again,
4270 * necessary since c-client doesn't know about how
4271 * we're treating attachments
4273 long
4274 pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
4276 STORE_S *bodyso;
4277 PART *part;
4278 PARAMETER *param;
4279 char *t, *cookie = NIL, *encode_error;
4280 char tmp[MAILTMPLEN];
4281 int add_trailing_crlf;
4282 LOC_2022_JP ljp;
4283 gf_io_t gc;
4285 dprint((4, "-- pine_rfc822_output_body: %d\n",
4286 body ? body->type : 0));
4288 bodyso = (STORE_S *) body->contents.text.data;
4290 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4291 part = body->nested.part; /* first body part */
4292 /* find cookie */
4293 for (param = body->parameter; param && !cookie; param = param->next)
4294 if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
4295 if (!cookie) cookie = "-"; /* yucky default */
4298 * Output a bit of text before the first multipart delimiter
4299 * to warn unsuspecting users of non-mime-aware ua's that
4300 * they should expect weirdness. We do not add this when signing a
4301 * message, though...
4303 #ifdef SMIME
4304 if(ps_global->smime && !ps_global->smime->do_sign)
4305 #endif
4306 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"))
4307 return(0);
4309 do { /* for each part */
4310 /* build cookie */
4311 snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
4312 tmp[sizeof(tmp)-1] = '\0';
4313 /* append cookie,mini-hdr,contents */
4314 if((f && !(*f)(s, tmp))
4315 || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
4316 || !pine_write_body_header(&part->body,f,s)
4317 || !pine_rfc822_output_body (&part->body,f,s))
4318 return(0);
4319 } while ((part = part->next) != NULL); /* until done */
4320 /* output trailing cookie */
4321 snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
4322 tmp[sizeof(tmp)-1] = '\0';
4323 #ifdef SMIME
4324 if(ps_global->smime && ps_global->smime->do_sign
4325 && strlen(tmp) < sizeof(tmp)-2)
4326 strncat(tmp, "\r\n", 2);
4327 #endif
4328 if(lmc.so && !lmc.all_written){
4329 so_puts(lmc.so, t);
4330 so_puts(lmc.so, "\015\012");
4333 return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
4336 l_f = f; /* set up for writing chars... */
4337 l_stream = s; /* out other end of pipe... */
4338 gf_filter_init();
4339 dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
4340 body->size.bytes));
4342 if(bodyso)
4343 gf_set_so_readc(&gc, bodyso);
4344 else
4345 return(1);
4348 * Don't add trailing line if it is ExternalText, which already guarantees
4349 * a trailing newline.
4351 add_trailing_crlf = !(bodyso->src == ExternalText);
4353 so_seek(bodyso, 0L, 0);
4355 if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
4356 char *charset;
4358 if(body->type == TYPETEXT
4359 && so_attr(bodyso, "edited", NULL)
4360 && (charset = parameter_val(body->parameter, "charset"))){
4361 if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
4362 if(!strucmp(charset, "iso-2022-jp")){
4363 ljp.report_err = 0;
4364 gf_link_filter(gf_line_test,
4365 gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
4367 else{
4368 void *table = utf8_rmap(charset);
4370 if(table){
4371 gf_link_filter(gf_convert_utf8_charset,
4372 gf_convert_utf8_charset_opt(table,0,1));
4374 else{
4375 /* else, just send it? */
4376 set_parameter(&body->parameter, "charset", "UTF-8");
4381 fs_give((void **)&charset);
4385 * Convert text pieces to canonical form
4386 * BEFORE applying any encoding (rfc1341: appendix G)...
4387 * NOTE: almost all filters expect CRLF newlines
4389 if(body->type == TYPETEXT
4390 && body->encoding != ENCBASE64
4391 && !so_attr(bodyso, "rawbody", NULL)){
4392 gf_link_filter(gf_local_nvtnl, NULL);
4395 switch (body->encoding) { /* all else needs filtering */
4396 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
4397 gf_link_filter(gf_8bit_qp, NULL);
4398 break;
4400 case ENCBINARY: /* encode binary into BASE64 */
4401 gf_link_filter(gf_binary_b64, NULL);
4402 break;
4404 default: /* otherwise text */
4405 break;
4409 if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
4410 q_status_message1(SM_ORDER | SM_DING, 3, 4,
4411 _("Encoding Error \"%s\""), encode_error);
4412 display_message('x');
4415 gf_clear_so_readc(bodyso);
4417 if(encode_error || !l_flush_net(TRUE))
4418 return(0);
4420 send_bytes_sent += gf_bytes_piped();
4421 so_release((STORE_S *)body->contents.text.data);
4423 if(lmc.so && !lmc.all_written && lmc.text_only){
4424 if(lmc.text_written){ /* we have some splainin' to do */
4425 char tmp[MAILTMPLEN];
4426 char *name = NULL;
4428 if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
4429 && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
4430 return(0);
4433 * BUG: If this name is not ascii it's going to cause trouble.
4435 name = parameter_val(body->parameter, "name");
4436 snprintf(tmp, sizeof(tmp),
4437 " A %s/%s%s%s%s segment of about %s bytes.\015\012",
4438 body_type_names(body->type),
4439 body->subtype ? body->subtype : "Unknown",
4440 name ? " (Name=\"" : "",
4441 name ? name : "",
4442 name ? "\")" : "",
4443 comatose(body->size.bytes));
4444 tmp[sizeof(tmp)-1] = '\0';
4445 if(name)
4446 fs_give((void **)&name);
4448 if(!so_puts(lmc.so, tmp))
4449 return(0);
4451 else /* suppress everything after first text part */
4452 lmc.text_written = (body->type == TYPETEXT
4453 && (!body->subtype
4454 || !strucmp(body->subtype, "plain")));
4457 if(add_trailing_crlf)
4458 return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
4459 && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
4460 else
4461 return(1);
4464 char *
4465 ToLower(char *s, char *t)
4467 int i;
4469 for(i = 0; s != NULL && s[i] != '\0'; i++)
4470 t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
4471 t[i] = '\0';
4473 return t;
4477 * pine_write_body_header - another c-client clone. This time
4478 * so the final encoding labels get set
4479 * correctly since it hasn't happened yet,
4480 * and to be paranoid about line lengths.
4482 * Returns: TRUE/nonzero on success, zero on error
4485 pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
4487 char tmp[MAILTMPLEN];
4488 RFC822BUFFER rbuf;
4489 int i;
4490 unsigned char c;
4491 STRINGLIST *stl;
4492 STORE_S *so;
4493 extern const char *tspecials;
4495 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
4496 if(!(so_puts(so, "Content-Type: ")
4497 && so_puts(so, ToLower(body_types[body->type], tmp))
4498 && so_puts(so, "/")
4499 && so_puts(so, ToLower(body->subtype
4500 ? body->subtype
4501 : rfc822_default_subtype (body->type),tmp))))
4502 return(pwbh_finish(0, so));
4504 if(body->parameter){
4505 if(!pine_write_params(body->parameter, so))
4506 return(pwbh_finish(0, so));
4508 else if(!so_puts(so, "; CHARSET=US-ASCII"))
4509 return(pwbh_finish(0, so));
4511 if(!so_puts(so, "\015\012"))
4512 return(pwbh_finish(0, so));
4514 if ((body->encoding /* note: encoding 7BIT never output! */
4515 && !(so_puts(so, "Content-Transfer-Encoding: ")
4516 && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
4517 ? ENCBASE64
4518 : (body->encoding == ENC8BIT)
4519 ? ENCQUOTEDPRINTABLE
4520 : (body->encoding <= ENCMAX)
4521 ? body->encoding
4522 : ENCOTHER])
4523 && so_puts(so, "\015\012")))
4525 * If requested, strip Content-ID headers that don't look like they
4526 * are needed. Microsoft's Outlook XP has a bug that causes it to
4527 * not show that there is an attachment when there is a Content-ID
4528 * header present on that attachment.
4530 * If user has quell-content-id turned on, don't output content-id
4531 * unless it is of type message/external-body.
4532 * Since this code doesn't look inside messages being forwarded
4533 * type message content-ids will remain as is and type multipart
4534 * alternative will remain as is. We don't create those on our
4535 * own. If we did, we'd have to worry about getting this right.
4537 || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
4538 || (body->type == TYPEMESSAGE
4539 && body->subtype
4540 && !strucmp(body->subtype, "external-body")))
4541 && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
4542 && so_puts(so, "\015\012")))
4543 || (body->description
4544 && strlen(body->description) < 5000 /* arbitrary! */
4545 && !pine_write_header_line("Content-Description: ", body->description, so))
4546 || (body->md5
4547 && !(so_puts(so, "Content-MD5: ")
4548 && so_puts(so, body->md5)
4549 && so_puts(so, "\015\012"))))
4550 return(pwbh_finish(0, so));
4552 if ((stl = body->language) != NULL) {
4553 if(!so_puts(so, "Content-Language: "))
4554 return(pwbh_finish(0, so));
4556 do {
4557 if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
4558 return(pwbh_finish(0, so));
4560 tmp[0] = '\0';
4561 rbuf.f = dummy_soutr;
4562 rbuf.s = NULL;
4563 rbuf.beg = tmp;
4564 rbuf.cur = tmp;
4565 rbuf.end = tmp+sizeof(tmp)-1;
4566 rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
4567 *rbuf.cur = '\0';
4569 if(!so_puts(so, tmp)
4570 || ((stl = stl->next) && !so_puts(so, ", ")))
4571 return(pwbh_finish(0, so));
4573 while (stl);
4575 if(!so_puts(so, "\015\012"))
4576 return(pwbh_finish(0, so));
4579 if (body->disposition.type) {
4580 if(!(so_puts(so, "Content-Disposition: ")
4581 && so_puts(so, body->disposition.type)))
4582 return(pwbh_finish(0, so));
4584 if(!pine_write_params(body->disposition.parameter, so))
4585 return(pwbh_finish(0, so));
4587 if(!so_puts(so, "\015\012"))
4588 return(pwbh_finish(0, so));
4591 /* copy out of so, a line at a time (or less than a K)
4592 * and send it down the pike
4594 so_seek(so, 0L, 0);
4595 i = 0;
4596 while(so_readc(&c, so))
4597 if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
4598 tmp[i] = '\0';
4599 if((f && !(*f)(s, tmp))
4600 || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
4601 return(pwbh_finish(0, so));
4603 i = 0;
4606 /* Finally, write blank line */
4607 if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
4608 return(pwbh_finish(0, so));
4610 return(pwbh_finish(i == 0, so)); /* better of ended on LF */
4613 return(0);
4618 * pine_write_header - convert, encode (if needed) and
4619 * write "header-name: field-body"
4622 pine_write_header_line(char *hdr, char *val, STORE_S *so)
4624 char *cv, *cs, *vp;
4625 int rv;
4627 cs = posting_characterset(val, NULL, HdrText);
4628 cv = utf8_to_charset(val, cs, 0);
4629 vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
4630 (unsigned char *) cv, cs);
4632 rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
4634 if(cv && cv != val)
4635 fs_give((void **) &cv);
4638 return(rv);
4643 * pine_write_param - convert, encode and write MIME header-field parameters
4646 pine_write_params(PARAMETER *param, STORE_S *so)
4648 for(; param; param = param->next){
4649 int rv;
4650 char *cv, *cs;
4651 extern const char *tspecials;
4653 cs = posting_characterset(param->value, NULL, HdrText);
4654 cv = utf8_to_charset(param->value, cs, 0);
4655 rv = (so_puts(so, "; ")
4656 && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
4658 if(cv && cv != param->value)
4659 fs_give((void **) &cv);
4661 if(!rv)
4662 return(0);
4665 return(1);
4671 lmc_body_header_line(char *line, int beginning)
4673 if(lmc.so && !lmc.all_written){
4674 if(beginning && lmc.text_only && lmc.text_written
4675 && (!struncmp(line, "content-type:", 13)
4676 || !struncmp(line, "content-transfer-encoding:", 26)
4677 || !struncmp(line, "content-disposition:", 20))){
4679 * "comment out" the real values since our comment isn't
4680 * likely the same type, disposition nor encoding...
4682 if(!so_puts(lmc.so, "X-"))
4683 return(FALSE);
4686 return(so_puts(lmc.so, line));
4689 return(TRUE);
4694 lmc_body_header_finish(void)
4696 if(lmc.so && !lmc.all_written){
4697 if(lmc.text_only && lmc.text_written
4698 && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
4699 return(FALSE);
4701 return(so_puts(lmc.so, "\015\012"));
4704 return(TRUE);
4710 pwbh_finish(int rv, STORE_S *so)
4712 if(so)
4713 so_give(&so);
4715 return(rv);
4720 * pine_free_body - c-client call wrapper so the body data pointer we
4721 * we're using in a way c-client doesn't know about
4722 * gets free'd appropriately.
4724 void
4725 pine_free_body(struct mail_bodystruct **body)
4728 * Preempt c-client's contents.text.data clean up since we've
4729 * usurped it's meaning for our own purposes...
4731 pine_free_body_data (*body);
4733 /* Then let c-client handle the rest... */
4734 mail_free_body(body);
4739 * pine_free_body_data - free pine's interpretations of the body part's
4740 * data pointer.
4742 void
4743 pine_free_body_data(struct mail_bodystruct *body)
4745 if(body){
4746 if(body->type == TYPEMULTIPART){
4747 PART *part = body->nested.part;
4748 do /* for each part */
4749 pine_free_body_data(&part->body);
4750 while ((part = part->next) != NULL); /* until done */
4752 else if(body->contents.text.data)
4753 so_give((STORE_S **) &body->contents.text.data);
4758 long
4759 send_body_size(struct mail_bodystruct *body)
4761 long l = 0L;
4762 PART *part;
4764 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
4765 part = body->nested.part; /* first body part */
4766 do /* for each part */
4767 l += send_body_size(&part->body);
4768 while ((part = part->next) != NULL); /* until done */
4769 return(l);
4772 return(l + body->size.bytes);
4777 sent_percent(void)
4779 int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
4780 / send_bytes_to_send);
4781 return(MIN(i, 100));
4786 * pine_smtp_verbose_out - write
4788 void
4789 pine_smtp_verbose_out(char *s)
4791 #ifdef _WINDOWS
4792 LPTSTR slpt;
4793 #endif
4794 if(verbose_send_output && s){
4795 char *p, last = '\0';
4797 for(p = s; *p; p++)
4798 if(*p == '\015')
4799 *p = ' ';
4800 else
4801 last = *p;
4803 #ifdef _WINDOWS
4805 * The stream is opened in Unicode mode, so we need to fix the
4806 * argument to fputs.
4808 slpt = utf8_to_lptstr((LPSTR) s);
4809 if(slpt){
4810 _fputts(slpt, verbose_send_output);
4811 fs_give((void **) &slpt);
4814 if(last != '\012')
4815 _fputtc(L'\n', verbose_send_output);
4816 #else
4817 fputs(s, verbose_send_output);
4818 if(last != '\012')
4819 fputc('\n', verbose_send_output);
4820 #endif
4827 * pine_header_forbidden - is this name a "forbidden" header?
4829 * name - the header name to check
4830 * We don't allow user to change these.
4833 pine_header_forbidden(char *name)
4835 char **p;
4836 static char *forbidden_headers[] = {
4837 "sender",
4838 "x-sender",
4839 "x-x-sender",
4840 "date",
4841 "received",
4842 "message-id",
4843 "in-reply-to",
4844 "path",
4845 "resent-message-id",
4846 "resent-date",
4847 "resent-from",
4848 "resent-sender",
4849 "resent-to",
4850 "resent-cc",
4851 "resent-reply-to",
4852 "mime-version",
4853 "content-type",
4854 "x-priority",
4855 "user-agent",
4856 "list-help", /* rfc 2369, section 3 */
4857 "list-unsubscribe",
4858 "list-subscribe",
4859 "list-post",
4860 "list-owner",
4861 "list-archive",
4862 NULL
4865 for(p = forbidden_headers; *p; p++)
4866 if(!strucmp(name, *p))
4867 break;
4869 return((*p) ? 1 : 0);
4874 * hdr_is_in_list - is there a custom value for this header?
4876 * hdr - the header name to check
4877 * custom - the list to check in
4878 * Returns 1 if there is a custom value, 0 otherwise.
4881 hdr_is_in_list(char *hdr, PINEFIELD *custom)
4883 PINEFIELD *pf;
4885 for(pf = custom; pf && pf->name; pf = pf->next)
4886 if(strucmp(pf->name, hdr) == 0)
4887 return 1;
4889 return 0;
4894 * count_custom_hdrs_pf - returns number of custom headers in arg
4895 * custom -- the list to be counted
4896 * only_nonstandard -- only count headers which aren't standard pine headers
4899 count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
4901 int ret = 0;
4903 for(; custom && custom->name; custom = custom->next)
4904 if(!only_nonstandard || !custom->standard)
4905 ret++;
4907 return(ret);
4912 * count_custom_hdrs_list - returns number of custom headers in arg
4915 count_custom_hdrs_list(char **list)
4917 char **p;
4918 char *q = NULL;
4919 char *name;
4920 char *t;
4921 char save;
4922 int ret = 0;
4924 if(list){
4925 for(p = list; (q = *p) != NULL; p++){
4926 if(q[0]){
4927 /* remove leading whitespace */
4928 name = skip_white_space(q);
4930 /* look for colon or space or end */
4931 for(t = name;
4932 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
4933 ;/* do nothing */
4935 save = *t;
4936 *t = '\0';
4937 if(!pine_header_forbidden(name))
4938 ret++;
4940 *t = save;
4945 return(ret);
4950 * set_default_hdrval - put the user's default value for this header
4951 * into pf->textbuf.
4952 * setthis - the pinefield to be set
4953 * custom - where to look for the default
4955 CustomType
4956 set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
4958 PINEFIELD *pf;
4959 CustomType ret = NoMatch;
4961 if(!setthis || !setthis->name){
4962 q_status_message(SM_ORDER,3,7,"Internal error setting default header");
4963 return(ret);
4966 setthis->textbuf = NULL;
4968 for(pf = custom; pf && pf->name; pf = pf->next){
4969 if(strucmp(pf->name, setthis->name) != 0)
4970 continue;
4972 ret = pf->cstmtype;
4974 /* turn on editing */
4975 if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
4976 setthis->canedit = 1;
4978 if(pf->val)
4979 setthis->textbuf = cpystr(pf->val);
4982 if(!setthis->textbuf)
4983 setthis->textbuf = cpystr("");
4985 return(ret);
4990 * pine_header_standard - is this name a "standard" header?
4992 * name - the header name to check
4994 FieldType
4995 pine_header_standard(char *name)
4997 int i;
4999 /* check to see if this is a standard header */
5000 for(i = 0; pf_template[i].name; i++)
5001 if(!strucmp(name, pf_template[i].name))
5002 return(pf_template[i].type);
5004 return(TypeUnknown);
5009 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
5010 * Allocates space for each name and addr ptr.
5011 * Allocates space for default in textbuf, even if empty.
5013 * head - the first PINEFIELD to fill in
5014 * list - the list to parse
5016 void
5017 customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
5019 char **p, *q, *t, *name, *value, save;
5020 PINEFIELD *pf;
5022 pf = head;
5024 if(list){
5025 for(p = list; (q = *p) != NULL; p++){
5027 if(q[0]){
5029 /* anything after leading whitespace? */
5030 if(!*(name = skip_white_space(q)))
5031 continue;
5033 /* look for colon or space or end */
5034 for(t = name;
5035 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5036 ;/* do nothing */
5038 /* if there is a space in the field-name, skip it */
5039 if(isspace((unsigned char)*t)){
5040 q_status_message1(SM_ORDER, 3, 3,
5041 _("Space not allowed in header name (%s)"),
5042 name);
5043 continue;
5046 save = *t;
5047 *t = '\0';
5049 /* Don't allow any of the forbidden headers. */
5050 if(pine_header_forbidden(name)){
5051 q_status_message1(SM_ORDER | SM_DING, 3, 3,
5052 _("Not allowed to change header \"%s\""),
5053 name);
5055 *t = save;
5056 continue;
5059 if(pf){
5060 if(pine_header_standard(name) != TypeUnknown)
5061 pf->standard = 1;
5063 pf->name = cpystr(name);
5064 pf->type = FreeText;
5065 pf->cstmtype = cstmtype;
5066 pf->next = pf+1;
5068 #ifdef OLDWAY
5070 * Some mailers apparently break if we change
5071 * user@domain into Fred <user@domain> for
5072 * return-receipt-to,
5073 * so we'll just call this a FreeText field, too.
5076 * For now, all custom headers are FreeText except for
5077 * this one that we happen to know about. We might
5078 * have to add some syntax to the config option so that
5079 * people can tell us their custom header takes addresses.
5081 if(!strucmp(pf->name, "Return-Receipt-to")){
5082 pf->type = Address;
5083 pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
5084 *pf->addr = (ADDRESS *)NULL;
5086 #endif /* OLDWAY */
5088 *t = save;
5090 /* remove space between name and colon */
5091 value = skip_white_space(t);
5093 /* give them an alloc'd default, even if empty */
5094 pf->textbuf = cpystr((*value == ':')
5095 ? skip_white_space(++value) : "");
5096 if(pf->textbuf && pf->textbuf[0])
5097 pf->val = cpystr(pf->textbuf);
5099 pf++;
5101 else
5102 *t = save;
5107 /* fix last next pointer */
5108 if(head && pf != head)
5109 (pf-1)->next = NULL;
5114 * add_defaults_from_list - the PINEFIELDS list given by "head" is already
5115 * setup except that it doesn't have default values.
5116 * Add those defaults if they exist in "list".
5118 * head - the first PINEFIELD to add a default to
5119 * list - the list to get the defaults from
5121 void
5122 add_defaults_from_list(PINEFIELD *head, char **list)
5124 char **p, *q, *t, *name, *value, save;
5125 PINEFIELD *pf;
5127 for(pf = head; pf && list; pf = pf->next){
5128 if(!pf->name)
5129 continue;
5131 for(p = list; (q = *p) != NULL; p++){
5133 if(q[0]){
5135 /* anything after leading whitespace? */
5136 if(!*(name = skip_white_space(q)))
5137 continue;
5139 /* look for colon or space or end */
5140 for(t = name;
5141 *t && !isspace((unsigned char)*t) && *t != ':'; t++)
5142 ;/* do nothing */
5144 /* if there is a space in the field-name, skip it */
5145 if(isspace((unsigned char)*t))
5146 continue;
5148 save = *t;
5149 *t = '\0';
5151 if(strucmp(name, pf->name) != 0){
5152 *t = save;
5153 continue;
5156 *t = save;
5159 * Found the right header. See if it has a default
5160 * value set.
5163 /* remove space between name and colon */
5164 value = skip_white_space(t);
5166 if(*value == ':'){
5167 char *defval;
5169 defval = skip_white_space(++value);
5170 if(defval && *defval){
5171 if(pf->val)
5172 fs_give((void **)&pf->val);
5174 pf->val = cpystr(defval);
5178 break; /* on to next pf */
5186 * parse_custom_hdrs - allocate PINEFIELDS for custom headers
5187 * fill in the defaults
5188 * Args - list -- The list to parse.
5189 * cstmtype -- Fill the in cstmtype field with this value
5191 PINEFIELD *
5192 parse_custom_hdrs(char **list, CustomType cstmtype)
5194 PINEFIELD *pfields;
5195 int i;
5198 * add one for possible use by fcc
5199 * What is this "possible use"? I don't see it. I don't
5200 * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
5202 i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
5203 pfields = (PINEFIELD *)fs_get((size_t) i);
5204 memset(pfields, 0, (size_t) i);
5206 /* set up the custom header pfields */
5207 customized_hdr_setup(pfields, list, cstmtype);
5209 return(pfields);
5214 * Combine the two lists of headers into one list which is allocated here
5215 * and freed by the caller. Eliminate duplicates with values from the role
5216 * taking precedence over values from the default.
5218 PINEFIELD *
5219 combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
5221 PINEFIELD *pfields, *pf, *pf2;
5222 int max_hdrs, i;
5224 max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
5225 count_custom_hdrs_pf(dflthdrs,0);
5227 pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5228 memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
5230 pf = pfields;
5231 for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
5232 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5233 pf->type = pf2->type;
5234 pf->cstmtype = pf2->cstmtype;
5235 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5236 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5237 pf->standard = pf2->standard;
5238 pf->next = pf+1;
5239 pf++;
5242 /* if these aren't already there, add them */
5243 for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
5244 /* check for already there */
5245 for(i = 0;
5246 pfields[i].name && strucmp(pfields[i].name, pf2->name);
5247 i++)
5250 if(!pfields[i].name){ /* this is a new one */
5251 pf->name = pf2->name ? cpystr(pf2->name) : NULL;
5252 pf->type = pf2->type;
5253 pf->cstmtype = pf2->cstmtype;
5254 pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
5255 pf->val = pf2->val ? cpystr(pf2->val) : NULL;
5256 pf->standard = pf2->standard;
5257 pf->next = pf+1;
5258 pf++;
5262 /* fix last next pointer */
5263 if(pf != pfields)
5264 (pf-1)->next = NULL;
5266 return(pfields);
5271 * free_customs - free misc. resources associated with custom header fields
5273 * pf - pointer to first custom field
5275 void
5276 free_customs(PINEFIELD *head)
5278 PINEFIELD *pf;
5280 for(pf = head; pf && pf->name; pf = pf->next){
5282 fs_give((void **)&pf->name);
5284 if(pf->val)
5285 fs_give((void **)&pf->val);
5287 /* only true for FreeText */
5288 if(pf->textbuf)
5289 fs_give((void **)&pf->textbuf);
5291 /* only true for Address */
5292 if(pf->addr && *pf->addr)
5293 mail_free_address(pf->addr);
5296 fs_give((void **)&head);
5301 * encode_whole_header
5303 * Returns 1 if whole value should be encoded
5304 * 0 to encode only within comments
5307 encode_whole_header(char *field, METAENV *header)
5309 int retval = 0;
5310 PINEFIELD *pf;
5312 if(field && (!strucmp(field, "Subject") ||
5313 !strucmp(field, "Comment") ||
5314 !struncmp(field, "X-", 2)))
5315 retval++;
5316 else if(field && *field && header && header->custom){
5317 for(pf = header->custom; pf && pf->name; pf = pf->next){
5319 if(!pf->standard && !strucmp(pf->name, field)){
5320 retval++;
5321 break;
5326 return(retval);
5330 /*----------------------------------------------------------------------
5331 Post news via NNTP or inews
5333 Args: env -- envelope of message to post
5334 body -- body of message to post
5336 Returns: -1 if failed or cancelled, 1 if succeeded
5338 WARNING: This call function has the side effect of writing the message
5339 to the lmc.so object.
5340 ----*/
5342 news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
5343 void (*pipecb_f)(PIPE_S *, int, void *))
5345 char *error_mess, error_buf[200], **news_servers;
5346 char **servers_to_use;
5347 int we_cancel = 0, server_no = 0, done_posting = 0;
5348 void *orig_822_output;
5349 BODY *bp = NULL;
5351 error_buf[0] = '\0';
5352 we_cancel = busy_cue("Posting news", NULL, 0);
5354 dprint((4, "Posting: [%s]\n",
5355 (header && header->env && header->env->newsgroups)
5356 ? header->env->newsgroups : "?"));
5358 if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
5359 || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
5360 && ps_global->VAR_NNTP_SERVER[0][0])){
5361 /*---------- NNTP server defined ----------*/
5362 error_mess = NULL;
5363 servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
5364 && alt_nntp_servers[0][0])
5365 ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
5368 * Install our rfc822 output routine
5370 orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
5371 (void) mail_parameters(NULL, SET_RFC822OUTPUT,
5372 (void *)post_rfc822_output);
5374 server_no = 0;
5375 news_servers = (char **)fs_get(2 * sizeof(char *));
5376 news_servers[1] = NULL;
5377 while(!done_posting && servers_to_use[server_no] &&
5378 servers_to_use[server_no][0]){
5379 news_servers[0] = servers_to_use[server_no];
5380 ps_global->noshow_error = 1;
5381 #ifdef DEBUG
5382 sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
5383 #else
5384 sending_stream = nntp_open(news_servers, 0L);
5385 #endif
5386 ps_global->noshow_error = 0;
5388 if(sending_stream != NULL) {
5389 unsigned short save_encoding, added_encoding;
5392 * Fake that we've got clearance from the transport agent
5393 * for 8bit transport for the benefit of our output routines...
5395 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
5396 && (bp = first_text_8bit(body))){
5397 int i;
5399 for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
5402 if(i > ENCMAX){ /* no empty encoding slots! */
5403 bp = NULL;
5405 else {
5406 added_encoding = i;
5407 body_encodings[added_encoding] = body_encodings[ENC8BIT];
5408 save_encoding = bp->encoding;
5409 bp->encoding = added_encoding;
5414 * Set global header pointer so we can get at it later...
5416 send_header = header;
5417 ps_global->noshow_error = 1;
5418 if(nntp_mail(sending_stream, header->env, body) == 0)
5419 snprintf(error_mess = error_buf, sizeof(error_buf),
5420 _("Error posting message: %s"),
5421 sending_stream->reply);
5422 else{
5423 done_posting = 1;
5424 error_buf[0] = '\0';
5425 error_mess = NULL;
5428 error_buf[sizeof(error_buf)-1] = '\0';
5429 smtp_close(sending_stream);
5430 ps_global->noshow_error = 0;
5431 sending_stream = NULL;
5432 if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
5433 body_encodings[added_encoding] = NULL;
5434 bp->encoding = save_encoding;
5437 } else {
5438 /*---- Open of NNTP connection failed ------ */
5439 snprintf(error_mess = error_buf, sizeof(error_buf),
5440 _("Error connecting to news server: %s"),
5441 ps_global->c_client_error);
5442 error_buf[sizeof(error_buf)-1] = '\0';
5443 dprint((1, error_buf));
5445 server_no++;
5447 fs_give((void **)&news_servers);
5448 (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
5449 } else {
5450 /*----- Post via local mechanism -------*/
5451 #ifdef _WINDOWS
5452 snprintf(error_mess = error_buf, sizeof(error_buf),
5453 _("Can't post, NNTP-server must be defined!"));
5454 #else /* UNIX */
5455 error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
5456 pipecb_f);
5457 #endif
5460 if(we_cancel)
5461 cancel_busy_cue(0);
5463 if(error_mess){
5464 if(lmc.so && !lmc.all_written)
5465 so_give(&lmc.so); /* clean up any fcc data */
5467 q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
5468 return(-1);
5471 lmc.all_written = 1;
5472 return(1);
5476 /* ----------------------------------------------------------------------
5477 Figure out command to start local SMTP agent
5479 Args: errbuf -- buffer for reporting errors (assumed non-NULL)
5481 Returns an alloc'd copy of the local SMTP agent invocation or NULL
5483 ----*/
5484 char *
5485 smtp_command(char *errbuf, size_t errbuflen)
5487 #ifdef _WINDOWS
5488 if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
5489 && ps_global->VAR_SMTP_SERVER[0][0]))
5490 strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
5492 errbuf[errbuflen-1] = '\0';
5493 #else /* UNIX */
5494 # if defined(SENDMAIL) && defined(SENDMAILFLAGS)
5495 #define SENDTMPLEN 256
5496 char tmp[SENDTMPLEN];
5497 /* SENDTMPLEN == sizeof(tmp) */
5498 snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
5499 (SENDTMPLEN-3)/2, SENDMAILFLAGS);
5500 return(cpystr(tmp));
5501 # else
5502 strncpy(errbuf, _("No default posting command."), errbuflen);
5503 errbuf[errbuflen-1] = '\0';
5504 # endif
5505 #endif
5507 return(NULL);
5512 #ifndef _WINDOWS
5513 /*----------------------------------------------------------------------
5514 Hand off given message to local posting agent
5516 Args: envelope -- The envelope for the BCC and debugging
5517 header -- The text of the message header
5518 errbuf -- buffer for reporting errors (assumed non-NULL)
5519 len -- Length of errbuf
5521 ----*/
5523 mta_handoff(METAENV *header, struct mail_bodystruct *body,
5524 char *errbuf, size_t len,
5525 void (*bigresult_f) (char *, int),
5526 void (*pipecb_f)(PIPE_S *, int, void *))
5528 #ifdef DF_SENDMAIL_PATH
5529 char cmd_buf[256];
5530 #endif
5531 char *cmd = NULL;
5534 * A bit of complicated policy implemented here.
5535 * There are two posting variables sendmail-path and smtp-server.
5536 * Precedence is in that order.
5537 * They can be set one of 4 ways: fixed, command-line, user, or globally.
5538 * Precedence is in that order.
5539 * Said differently, the order goes something like what's below.
5541 * NOTE: the fixed/command-line/user precendence handling is also
5542 * indicated by what's pointed to by ps_global->VAR_*, but since
5543 * that also includes the global defaults, it's not sufficient.
5546 if(ps_global->FIX_SENDMAIL_PATH
5547 && ps_global->FIX_SENDMAIL_PATH[0]){
5548 cmd = ps_global->FIX_SENDMAIL_PATH;
5550 else if(!(ps_global->FIX_SMTP_SERVER
5551 && ps_global->FIX_SMTP_SERVER[0])){
5552 if(ps_global->COM_SENDMAIL_PATH
5553 && ps_global->COM_SENDMAIL_PATH[0]){
5554 cmd = ps_global->COM_SENDMAIL_PATH;
5556 else if(!(ps_global->COM_SMTP_SERVER
5557 && ps_global->COM_SMTP_SERVER[0])){
5558 if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5559 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
5560 (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
5561 && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
5562 if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
5563 && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
5564 cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
5565 else
5566 cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
5568 else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
5569 && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
5570 (ps_global->vars[V_SMTP_SERVER].main_user_val.l
5571 && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
5572 if(ps_global->GLO_SENDMAIL_PATH
5573 && ps_global->GLO_SENDMAIL_PATH[0]){
5574 cmd = ps_global->GLO_SENDMAIL_PATH;
5576 #ifdef DF_SENDMAIL_PATH
5578 * This defines the default method of posting. So,
5579 * unless we're told otherwise use it...
5581 else if(!(ps_global->GLO_SMTP_SERVER
5582 && ps_global->GLO_SMTP_SERVER[0])){
5583 strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
5584 cmd_buf[sizeof(cmd_buf)-1] = '\0';
5586 #endif
5591 *errbuf = '\0';
5592 if(cmd){
5593 dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
5595 (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
5596 return(1);
5598 else
5599 return(0);
5604 /*----------------------------------------------------------------------
5605 Hand off given message to local posting agent
5607 Args: envelope -- The envelope for the BCC and debugging
5608 header -- The text of the message header
5609 errbuf -- buffer for reporting errors (assumed non-NULL)
5610 errbuflen -- Length of errbuf
5612 Fork off mailer process and pipe the message into it
5613 Called to post news via Inews when NNTP is unavailable
5615 ----*/
5616 char *
5617 post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
5618 size_t errbuflen,
5619 void (*bigresult_f) (char *, int),
5620 void (*pipecb_f)(PIPE_S *, int, void *))
5622 char *err = NULL;
5623 #ifdef SENDNEWS
5624 char *s;
5625 char tmp[200];
5627 if(s = strstr(header->env->date," (")) /* fix the date format for news */
5628 *s = '\0';
5630 if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
5631 strncpy(tmp, err, sizeof(tmp)-1);
5632 tmp[sizeof(tmp)-1] = '\0';
5633 snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
5634 SENDNEWS, tmp);
5637 if(s)
5638 *s = ' '; /* restore the date */
5640 #else /* !SENDNEWS */ /* this is the default case */
5641 strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
5642 err[errbuflen-1] = '\0';
5643 #endif /* !SENDNEWS */
5644 return(err);
5649 /*----------------------------------------------------------------------
5650 Hand off message to local MTA; it parses recipients from 822 header
5652 Args: header -- struct containing header data
5653 body -- struct containing message body data
5654 cmd -- command to use for handoff (%s says where file should go)
5655 errs -- pointer to buf to hold errors
5657 ----*/
5658 char *
5659 mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
5660 char *errs, size_t errslen, void (*bigresult_f)(char *, int),
5661 void (*pipecb_f)(PIPE_S *, int, void *))
5663 char *result = NULL;
5664 PIPE_S *pipe;
5666 dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
5668 if((pipe = open_system_pipe(cmd, &result, NULL,
5669 PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5670 0, pipecb_f, pipe_report_error)) != NULL){
5671 if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
5672 (TCPSTREAM *) pipe)){
5673 strncpy(errs, _("Error posting."), errslen-1);
5674 errs[errslen-1] = '\0';
5677 if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
5678 snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
5679 if(result && bigresult_f)
5680 (*bigresult_f)(result, CM_BR_ERROR);
5683 else
5684 snprintf(errs, errslen, _("Error running \"%s\""), cmd);
5686 if(result){
5687 our_unlink(result);
5688 fs_give((void **)&result);
5691 return(*errs ? errs : NULL);
5696 * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
5697 * pipes rather than a tcp stream
5699 long
5700 pine_pipe_soutr_nl (void *stream, char *s)
5702 long rv = T;
5703 char *p;
5704 size_t n;
5706 while(*s && rv){
5707 if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0){
5708 while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
5709 if(rv < 0){
5710 if(errno != EINTR){
5711 rv = 0;
5712 break;
5715 else{
5716 s += rv;
5717 n -= rv;
5721 if(p && rv){
5722 s = p + 2; /* write UNIX EOL */
5723 while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
5724 if(rv < 0 && errno != EINTR){
5725 rv = 0;
5726 break;
5729 else
5730 break;
5733 return(rv);
5735 #endif
5738 /**************** "PIPE" READING POSTING I/O ROUTINES ****************/
5742 * helpful def's
5744 #define S(X) ((PIPE_S *)(X))
5745 #define GETBUFLEN (4 * MAILTMPLEN)
5748 void *
5749 piped_smtp_open (char *host, char *service, long unsigned int port)
5751 char *postcmd;
5752 void *rv = NULL;
5754 if(strucmp(host, "localhost")){
5755 char tmp[MAILTMPLEN];
5756 /* MAILTMPLEN = sizeof(tmp) */
5757 snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
5758 MAILTMPLEN-50, host);
5759 tmp[sizeof(tmp)-1] = '\0';
5760 mm_log(tmp, ERROR);
5762 else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
5763 rv = open_system_pipe(postcmd, NULL, NULL,
5764 PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
5765 0, NULL, pipe_report_error);
5766 fs_give((void **) &postcmd);
5768 else
5769 mm_log(ps_global->c_client_error, ERROR);
5771 return(rv);
5775 void *
5776 piped_aopen (NETMBX *mb, char *service, char *user)
5778 return(NULL);
5783 * piped_soutr - Replacement for tcp_soutr that writes one of our
5784 * pipes rather than a tcp stream
5786 long
5787 piped_soutr (void *stream, char *s)
5789 return(piped_sout(stream, s, strlen(s)));
5794 * piped_sout - Replacement for tcp_soutr that writes one of our
5795 * pipes rather than a tcp stream
5797 long
5798 piped_sout (void *stream, char *s, long unsigned int size)
5800 int i, o;
5802 if(S(stream)->out.d < 0)
5803 return(0L);
5805 if((i = (int) size) != 0){
5806 while((o = write(S(stream)->out.d, s, i)) != i)
5807 if(o < 0){
5808 if(errno != EINTR){
5809 piped_abort(stream);
5810 return(0L);
5813 else{
5814 s += o; /* try again, fix up counts */
5815 i -= o;
5819 return(1L);
5824 * piped_getline - Replacement for tcp_getline that reads one
5825 * of our pipes rather than a tcp pipe
5827 * C-client expects that the \r\n will be stripped off.
5829 char *
5830 piped_getline (void *stream)
5832 static int cnt;
5833 static char *ptr;
5834 int n, m;
5835 char *ret, *s, *sp, c = '\0', d;
5837 if(S(stream)->in.d < 0)
5838 return(NULL);
5840 if(!S(stream)->tmp){ /* initialize! */
5841 /* alloc space to collect input (freed in close_system_pipe) */
5842 S(stream)->tmp = (char *) fs_get(GETBUFLEN);
5843 memset(S(stream)->tmp, 0, GETBUFLEN);
5844 cnt = -1;
5847 while(cnt < 0){
5848 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5849 if(errno != EINTR){
5850 piped_abort(stream);
5851 return(NULL);
5854 if(cnt == 0){
5855 piped_abort(stream);
5856 return(NULL);
5859 ptr = S(stream)->tmp;
5862 s = ptr;
5863 n = 0;
5864 while(cnt--){
5865 d = *ptr++;
5866 if((c == '\015') && (d == '\012')){
5867 ret = (char *)fs_get (n--);
5868 memcpy(ret, s, n);
5869 ret[n] = '\0';
5870 return(ret);
5873 n++;
5874 c = d;
5876 /* copy partial string from buffer */
5877 memcpy((ret = sp = (char *) fs_get (n)), s, n);
5878 /* get more data */
5879 while(cnt < 0){
5880 memset(S(stream)->tmp, 0, GETBUFLEN);
5881 while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
5882 if(errno != EINTR){
5883 fs_give((void **) &ret);
5884 piped_abort(stream);
5885 return(NULL);
5888 if(cnt == 0){
5889 if(n > 0)
5890 ret[n-1] = '\0'; /* to try to get error message logged */
5891 else{
5892 piped_abort(stream);
5893 return(NULL);
5897 ptr = S(stream)->tmp;
5900 if(c == '\015' && *ptr == '\012'){
5901 ptr++;
5902 cnt--;
5903 ret[n - 1] = '\0'; /* tie off string with null */
5905 else if ((s = piped_getline(stream)) != NULL) {
5906 ret = (char *) fs_get(n + 1 + (m = strlen (s)));
5907 memcpy(ret, sp, n); /* copy first part */
5908 memcpy(ret + n, s, m); /* and second part */
5909 fs_give((void **) &sp); /* flush first part */
5910 fs_give((void **) &s); /* flush second part */
5911 ret[n + m] = '\0'; /* tie off string with null */
5914 return(ret);
5919 * piped_close - Replacement for tcp_close that closes pipes to our
5920 * child rather than a tcp connection
5922 void
5923 piped_close(void *stream)
5926 * Uninstall our hooks into smtp_send since it's being used by
5927 * the nntp driver as well...
5929 (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
5934 * piped_abort - close down the pipe we were using to post
5936 void
5937 piped_abort(void *stream)
5939 if(S(stream)->in.d >= 0){
5940 close(S(stream)->in.d);
5941 S(stream)->in.d = -1;
5944 if(S(stream)->out.d){
5945 close(S(stream)->out.d);
5946 S(stream)->out.d = -1;
5951 char *
5952 piped_host(void *stream)
5954 return(ps_global->hostname ? ps_global->hostname : "localhost");
5958 unsigned long
5959 piped_port(void *stream)
5961 return(0L);