summaryrefslogtreecommitdiff
path: root/pith/send.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/send.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith/send.c')
-rw-r--r--pith/send.c5901
1 files changed, 5901 insertions, 0 deletions
diff --git a/pith/send.c b/pith/send.c
new file mode 100644
index 00000000..a0c60439
--- /dev/null
+++ b/pith/send.c
@@ -0,0 +1,5901 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/send.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/mimedesc.h"
+#include "../pith/context.h"
+#include "../pith/status.h"
+#include "../pith/folder.h"
+#include "../pith/bldaddr.h"
+#include "../pith/pipe.h"
+#include "../pith/mailview.h"
+#include "../pith/mailindx.h"
+#include "../pith/list.h"
+#include "../pith/filter.h"
+#include "../pith/reply.h"
+#include "../pith/addrstring.h"
+#include "../pith/rfc2231.h"
+#include "../pith/stream.h"
+#include "../pith/util.h"
+#include "../pith/adrbklib.h"
+#include "../pith/options.h"
+#include "../pith/busy.h"
+#include "../pith/text.h"
+#include "../pith/imap.h"
+#include "../pith/ablookup.h"
+#include "../pith/sort.h"
+#include "../pith/smime.h"
+
+#include "../c-client/smtp.h"
+#include "../c-client/nntp.h"
+
+
+/* this is used in pine_send and pine_simple_send */
+/* name::type::canedit::writehdr::localcopy::rcptto */
+PINEFIELD pf_template[] = {
+ {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
+ {"From", Address, 0, 1, 1, 0},
+ {"Reply-To", Address, 0, 1, 1, 0},
+ {TONAME, Address, 1, 1, 1, 1},
+ {CCNAME, Address, 1, 1, 1, 1},
+ {"bcc", Address, 1, 0, 1, 1},
+ {"Newsgroups", FreeText, 1, 1, 1, 0},
+ {"Fcc", Fcc, 1, 0, 0, 0},
+ {"Lcc", Address, 1, 0, 1, 1},
+ {"Attchmnt", Attachment, 1, 1, 1, 0},
+ {SUBJNAME, Subject, 1, 1, 1, 0},
+ {"References", FreeText, 0, 1, 1, 0},
+ {"Date", FreeText, 0, 1, 1, 0},
+ {"In-Reply-To", FreeText, 0, 1, 1, 0},
+ {"Message-ID", FreeText, 0, 1, 1, 0},
+ {PRIORITYNAME, FreeText, 0, 1, 1, 0},
+ {"User-Agent", FreeText, 0, 1, 1, 0},
+ {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
+ {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
+ {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
+ {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
+ {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
+ {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
+ {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
+ {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
+ {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ {"X-X-Sender", Address, 0, 1, 1, 0},
+#endif
+ {NULL, FreeText}
+};
+
+
+PRIORITY_S priorities[] = {
+ {1, "Highest"},
+ {2, "High"},
+ {3, "Normal"},
+ {4, "Low"},
+ {5, "Lowest"},
+ {0, NULL}
+};
+
+
+#define ctrl(c) ((c) & 0x1f)
+
+/* which message part to test for xliteration */
+typedef enum {MsgBody, HdrText} MsgPart;
+
+
+/*
+ * Internal prototypes
+ */
+long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
+int l_flush_net(int);
+int l_putc(int);
+int pine_write_header_line(char *, char *, STORE_S *);
+int pine_write_params(PARAMETER *, STORE_S *);
+char *tidy_smtp_mess(char *, char *, char *, size_t);
+int lmc_body_header_line(char *, int);
+int lmc_body_header_finish(void);
+int pwbh_finish(int, STORE_S *);
+int sent_percent(void);
+unsigned short *setup_avoid_table(void);
+#ifndef _WINDOWS
+int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+long pine_pipe_soutr_nl(void *, char *);
+#endif
+char *smtp_command(char *, size_t);
+void *piped_smtp_open(char *, char *, unsigned long);
+void *piped_aopen(NETMBX *, char *, char *);
+long piped_soutr(void *, char *);
+long piped_sout(void *, char *, unsigned long);
+char *piped_getline(void *);
+void piped_close(void *);
+void piped_abort(void *);
+char *piped_host(void *);
+unsigned long piped_port(void *);
+char *posting_characterset(void *, char *, MsgPart);
+int body_is_translatable(void *, char *);
+int text_is_translatable(void *, char *);
+int dummy_putc(int);
+unsigned long *init_charsetchecker(char *);
+int representable_in_charset(unsigned long, char *);
+char *most_preferred_charset(unsigned long);
+
+/*
+ * Storage object where the FCC (or postponed msg) is to be written.
+ * This is amazingly bogus. Much work was done to put messages
+ * together and encode them as they went to the tmp file for sendmail
+ * or into the SMTP slot (especially for for DOS, to prevent a temporary
+ * file (and needlessly copying the message).
+ *
+ * HOWEVER, since there's no piping into c-client routines
+ * (particularly mail_append() which copies the fcc), the fcc will have
+ * to be copied to disk. This global tells pine's copy of the rfc822
+ * output functions where to also write the message bytes for the fcc.
+ * With piping in the c-client we could just have two pipes to shove
+ * down rather than messing with damn copies. FIX THIS!
+ *
+ * The function open_fcc, locates the actual folder and creates it if
+ * requested before any mailing or posting is done.
+ */
+struct local_message_copy lmc;
+
+
+/*
+ * Locally global pointer to stream used for sending/posting.
+ * It's also used to indicate when/if we write the Bcc: field in
+ * the header.
+ */
+static SENDSTREAM *sending_stream = NULL;
+
+
+static struct hooks {
+ void *rfc822_out; /* Message outputter */
+} sending_hooks;
+
+
+static FILE *verbose_send_output = NULL;
+static long send_bytes_sent, send_bytes_to_send;
+static METAENV *send_header = NULL;
+
+/*
+ * Hooks for prompts and confirmations
+ */
+int (*pith_opt_daemon_confirm)(void);
+
+
+static NETDRIVER piped_io = {
+ piped_smtp_open, /* open a connection */
+ piped_aopen, /* open an authenticated connection */
+ piped_getline, /* get a line */
+ NULL, /* get a buffer */
+ piped_soutr, /* output pushed data */
+ piped_sout, /* output string */
+ piped_close, /* close connection */
+ piped_host, /* return host name */
+ piped_host, /* remotehost */
+ piped_port, /* return port number */
+ piped_host /* return local host (NOTE: same as host!) */
+};
+
+
+/*
+ * Since c-client preallocates, it's necessary here to define a limit
+ * such that we don't blow up in c-client (see rfc822_address_line()).
+ */
+#define MAX_SINGLE_ADDR MAILTMPLEN
+
+#define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
+
+
+/*
+ * Phone home hash controls
+ */
+#define PH_HASHBITS 24
+#define PH_MAXHASH (1<<(PH_HASHBITS))
+
+
+/*
+ * postponed_stream - return stream associated with postponed messages
+ * in argument.
+ */
+int
+postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
+{
+ MAILSTREAM *stream = NULL;
+ CONTEXT_S *p_cntxt = NULL;
+ char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
+ int exists;
+
+ if(!(streamp && mbox))
+ return(0);
+
+ *streamp = NULL;
+
+ /*
+ * find default context to look for folder...
+ *
+ * The "mbox" is assumed to be local if we're given what looks
+ * like an absolute path. This is different from Goto/Save
+ * where we do alot of work to interpret paths relative to the
+ * server. This reason is to support all the pre-4.00 pinerc'
+ * that specified a path and because there's yet to be a way
+ * in c-client to specify otherwise in the face of a remote
+ * context.
+ */
+ if(!is_absolute_path(mbox)
+ && !(p_cntxt = default_save_context(ps_global->context_list)))
+ p_cntxt = ps_global->context_list;
+
+ /* check to see if the folder exists, the user wants to continue
+ * and that we can actually read something in...
+ */
+ exists = folder_name_exists(p_cntxt, mbox, &fullname);
+ if(fullname)
+ mbox = fullname;
+
+ if(exists & FEX_ISFILE){
+ context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
+ if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
+ /*
+ * The mbox is relative to the home directory.
+ * Make it absolute so we can compare it to
+ * stream->mailbox.
+ */
+ build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
+ SIZEOF_20KBUF);
+ strncpy(tmp, tmp_20k_buf, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ if((stream = ps_global->mail_stream)
+ && !(stream->mailbox
+ && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
+ || (*tmp == '{'
+ && same_stream(tmp, stream)
+ && (p = strchr(tmp, '}'))
+ && (q = strchr(stream->mailbox,'}'))
+ && !strcmp(p + 1, q + 1)))))
+ stream = NULL;
+
+ if(!stream){
+ stream = context_open(p_cntxt, NULL, mbox,
+ SP_USEPOOL|SP_TEMPUSE, NULL);
+ if(stream && !stream->halfopen){
+ if(stream->nmsgs > 0)
+ refresh_sort(stream, sp_msgmap(stream), SRT_NON);
+
+ if(checknmsgs && stream->nmsgs < 1){
+ pine_mail_close(stream);
+ exists = 0;
+ stream = NULL;
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ _("Can't open %s mailbox: %s"), type, mbox);
+ if(stream)
+ pine_mail_close(stream);
+
+ exists = 0;
+ stream = NULL;
+ }
+ }
+ }
+ else{
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("%s message folder doesn't exist!"), type);
+ }
+ }
+
+ if(fullname)
+ fs_give((void **) &fullname);
+
+ *streamp = stream;
+
+ return(exists);
+}
+
+
+int
+redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
+ struct mail_bodystruct **body, char **fcc, char **lcc,
+ REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
+ ACTION_S **role, int flags, STORE_S *so)
+{
+ MAILSTREAM *stream;
+ ENVELOPE *e = NULL;
+ BODY *b;
+ PART *part;
+ PINEFIELD *pf;
+ gf_io_t pc;
+ char *extras, **fields, **values, *p;
+ char *hdrs[2], *h, *charset;
+ char **smtp_servers = NULL, **nntp_servers = NULL;
+ int i, pine_generated = 0, our_replyto = 0;
+ int added_to_role = 0;
+ unsigned gbpt_flags = GBPT_NONE;
+ MESSAGECACHE *mc;
+
+ if(!(streamp && *streamp))
+ return(redraft_cleanup(streamp, TRUE, flags));
+
+ stream = *streamp;
+
+ if(flags & REDRAFT_HTML)
+ gbpt_flags |= GBPT_HTML_OK;
+
+ /* grok any user-defined or non-c-client headers */
+ if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
+
+ /*
+ * The custom headers to look for in the suspended message should
+ * have been stored in the X-Our-Headers header. So first we get
+ * that list. If we can't find it (version that stored the
+ * message < 4.30) then we use the global custom list.
+ */
+ hdrs[0] = OUR_HDRS_LIST;
+ hdrs[1] = NULL;
+ if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
+ int commas = 0;
+ char **list;
+ char *hdrval = NULL;
+
+ if((hdrval = strindex(h, ':')) != NULL){
+ for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
+ hdrval++)
+ ;
+ }
+
+ /* count elements in list */
+ for(p = hdrval; p && *p; p++)
+ if(*p == ',')
+ commas++;
+
+ if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
+
+ *custom = parse_custom_hdrs(list, Replace);
+ add_defaults_from_list(*custom,
+ ps_global->VAR_CUSTOM_HDRS);
+ free_list_array(&list);
+ }
+
+ if(*custom && !(*custom)->name){
+ free_customs(*custom);
+ *custom = NULL;
+ }
+
+ fs_give((void **)&h);
+ }
+
+ if(!*custom)
+ *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
+
+#define INDEX_FCC 0
+#define INDEX_POSTERR 1
+#define INDEX_REPLYUID 2
+#define INDEX_REPLYMBOX 3
+#define INDEX_SMTP 4
+#define INDEX_NNTP 5
+#define INDEX_CURSORPOS 6
+#define INDEX_OUR_REPLYTO 7
+#define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
+#define FIELD_COUNT 9
+
+ i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
+
+ /*
+ * Having these two fields separated isn't the slickest, but
+ * converting the pointer array for fetchheader_lines() to
+ * a list of structures or some such for simple_header_parse()
+ * is too goonie. We could do something like re-use c-client's
+ * PARAMETER struct which is a simple char * pairing, but that
+ * doesn't make sense to pass to fetchheader_lines()...
+ */
+ fields = (char **) fs_get((size_t) i * sizeof(char *));
+ values = (char **) fs_get((size_t) i * sizeof(char *));
+ memset(fields, 0, (size_t) i * sizeof(char *));
+ memset(values, 0, (size_t) i * sizeof(char *));
+
+ fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
+ fields[++i] = "X-Post-Error"; /* posting errors too */
+ fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
+ fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
+ fields[++i] = "X-SMTP-Server";/* SMTP server to use */
+ fields[++i] = "X-NNTP-Server";/* NNTP server to use */
+ fields[++i] = "X-Cursor-Pos"; /* Cursor position */
+ fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
+ fields[++i] = "Lcc"; /* Lcc: too... */
+ if(++i != FIELD_COUNT)
+ panic("Fix FIELD_COUNT");
+
+ for(pf = *custom; pf && pf->name; pf = pf->next)
+ if(!pf->standard)
+ fields[i++] = pf->name; /* assign custom fields */
+
+ if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
+ simple_header_parse(extras, fields, values);
+ fs_give((void **) &extras);
+
+ /*
+ * translate RFC 1522 strings,
+ * starting with "Lcc" field
+ */
+ for(i = INDEX_LCC; fields[i]; i++)
+ if(values[i]){
+ size_t len;
+ char *bufp, *biggerbuf = NULL;
+
+ if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
+ len++;
+ biggerbuf = (char *)fs_get(len * sizeof(char));
+ bufp = biggerbuf;
+ }
+ else{
+ bufp = tmp_20k_buf;
+ len = SIZEOF_20KBUF;
+ }
+
+ p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
+
+ if(p == tmp_20k_buf){
+ fs_give((void **)&values[i]);
+ values[i] = cpystr(p);
+ }
+
+ if(biggerbuf)
+ fs_give((void **)&biggerbuf);
+ }
+
+ for(pf = *custom, i = FIELD_COUNT;
+ pf && pf->name;
+ pf = pf->next){
+ if(pf->standard){
+ /*
+ * Because the value is already in the envelope.
+ */
+ pf->cstmtype = NoMatch;
+ continue;
+ }
+
+ if(values[i]){ /* use this instead of default */
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ pf->textbuf = values[i]; /* freed in pine_send! */
+ }
+ else if(pf->textbuf) /* was erased before postpone */
+ fs_give((void **)&pf->textbuf);
+
+ i++;
+ }
+
+ if(values[INDEX_FCC]) /* If "Fcc:" was there... */
+ pine_generated = 1; /* we put it there? */
+
+ /*
+ * Since c-client fills in the reply_to field in the envelope
+ * even if there isn't a Reply-To header in the message we
+ * have to work around that. When we postpone we add
+ * a second header that has value "Empty" if there really
+ * was a Reply-To and it was empty. It has the
+ * value "Full" if we put the Reply-To contents there
+ * intentionally (and it isn't empty).
+ */
+ if(values[INDEX_OUR_REPLYTO]){
+ if(values[INDEX_OUR_REPLYTO][0] == 'E')
+ our_replyto = 'E'; /* we put an empty one there */
+ else if(values[INDEX_OUR_REPLYTO][0] == 'F')
+ our_replyto = 'F'; /* we put it there */
+
+ fs_give((void **) &values[INDEX_OUR_REPLYTO]);
+ }
+
+ if(fcc) /* fcc: special case... */
+ *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
+ else if(values[INDEX_FCC])
+ fs_give((void **) &values[INDEX_FCC]);
+
+ if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
+ q_status_message(SM_ORDER|SM_DING, 4, 4,
+ values[INDEX_POSTERR]);
+ fs_give((void **) &values[INDEX_POSTERR]);
+ }
+
+ if(values[INDEX_REPLYUID]){
+ if(reply)
+ *reply = build_reply_uid(values[INDEX_REPLYUID]);
+
+ fs_give((void **) &values[INDEX_REPLYUID]);
+
+ if(values[INDEX_REPLYMBOX] && reply && *reply)
+ (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
+
+ if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
+ (*reply)->origmbox = cpystr((*reply)->mailbox);
+ }
+
+ if(values[INDEX_REPLYMBOX])
+ fs_give((void **) &values[INDEX_REPLYMBOX]);
+
+ if(values[INDEX_SMTP]){
+ char *q;
+ size_t cnt = 0;
+
+ /*
+ * Turn the space delimited list of smtp servers into
+ * a char ** list.
+ */
+ p = values[INDEX_SMTP];
+ do{
+ if(!*p || isspace((unsigned char) *p))
+ cnt++;
+ } while(*p++);
+
+ smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
+ memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
+
+ cnt = 0;
+ q = p = values[INDEX_SMTP];
+ do{
+ if(!*p || isspace((unsigned char) *p)){
+ if(*p){
+ *p = '\0';
+ smtp_servers[cnt++] = cpystr(q);
+ *p = ' ';
+ q = p+1;
+ }
+ else
+ smtp_servers[cnt++] = cpystr(q);
+ }
+ } while(*p++);
+
+ fs_give((void **) &values[INDEX_SMTP]);
+ }
+
+ if(values[INDEX_NNTP]){
+ char *q;
+ size_t cnt = 0;
+
+ /*
+ * Turn the space delimited list of smtp nntp into
+ * a char ** list.
+ */
+ p = values[INDEX_NNTP];
+ do{
+ if(!*p || isspace((unsigned char) *p))
+ cnt++;
+ } while(*p++);
+
+ nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
+ memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
+
+ cnt = 0;
+ q = p = values[INDEX_NNTP];
+ do{
+ if(!*p || isspace((unsigned char) *p)){
+ if(*p){
+ *p = '\0';
+ nntp_servers[cnt++] = cpystr(q);
+ *p = ' ';
+ q = p+1;
+ }
+ else
+ nntp_servers[cnt++] = cpystr(q);
+ }
+ } while(*p++);
+
+ fs_give((void **) &values[INDEX_NNTP]);
+ }
+
+ if(values[INDEX_CURSORPOS]){
+ /*
+ * The redraft cursor position is written as two fields
+ * separated by a space. First comes the name of the
+ * header field we're in, or just a ":" if we're in the
+ * body. Then comes the offset into that header or into
+ * the body.
+ */
+ if(redraft_pos){
+ char *q1, *q2;
+
+ *redraft_pos
+ = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
+ (*redraft_pos)->offset = 0L;
+
+ q1 = skip_white_space(values[INDEX_CURSORPOS]);
+ if(*q1 && (q2 = strindex(q1, SPACE))){
+ *q2 = '\0';
+ (*redraft_pos)->hdrname = cpystr(q1);
+ q1 = skip_white_space(q2+1);
+ if(*q1)
+ (*redraft_pos)->offset = atol(q1);
+ }
+ else
+ (*redraft_pos)->hdrname = cpystr(":");
+ }
+
+ fs_give((void **) &values[INDEX_CURSORPOS]);
+ }
+
+ if(lcc)
+ *lcc = values[INDEX_LCC];
+ else
+ fs_give((void **) &values[INDEX_LCC]);
+ }
+
+ fs_give((void **)&fields);
+ fs_give((void **)&values);
+
+ *outgoing = copy_envelope(e);
+
+ /*
+ * If the postponed message has a From which is different from
+ * the default, it is either because allow-changing-from is on
+ * or because there was a role with a from that allowed it to happen.
+ * If allow-changing-from is not on, put this back in a role
+ * so that it will be allowed again in pine_send.
+ */
+ if(role && *role == NULL &&
+ !ps_global->never_allow_changing_from &&
+ *outgoing){
+ /*
+ * Now check to see if the from is different from default from.
+ */
+ ADDRESS *deffrom;
+
+ deffrom = generate_from();
+ if(!((*outgoing)->from &&
+ address_is_same(deffrom, (*outgoing)->from) &&
+ ((!(deffrom->personal && deffrom->personal[0]) &&
+ !((*outgoing)->from->personal &&
+ (*outgoing)->from->personal[0])) ||
+ (deffrom->personal && (*outgoing)->from->personal &&
+ !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
+
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ if(!(*outgoing)->from)
+ (*outgoing)->from = mail_newaddr();
+
+ (*role)->from = (*outgoing)->from;
+ (*outgoing)->from = NULL;
+ added_to_role++;
+ }
+
+ mail_free_address(&deffrom);
+ }
+
+ /*
+ * Look at each empty address and see if the user has specified
+ * a default for that field or not. If they have, that means
+ * they have erased it before postponing, so they won't want
+ * the default to come back. If they haven't specified a default,
+ * then the default should be generated in pine_send. We prevent
+ * the default from being assigned by assigning an empty address
+ * to the variable here.
+ *
+ * BUG: We should do this for custom Address headers, too, but
+ * there isn't such a thing yet.
+ */
+ if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
+ (*outgoing)->to = mail_newaddr();
+ if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
+ (*outgoing)->cc = mail_newaddr();
+ if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
+ (*outgoing)->bcc = mail_newaddr();
+
+ if(our_replyto == 'E'){
+ /* user erased reply-to before postponing */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+
+ /*
+ * If empty is not the normal default, make the outgoing
+ * reply_to be an emtpy address. If it is default, leave it
+ * as NULL and the default will be used.
+ */
+ if(hdr_is_in_list("reply-to", *custom)){
+ PINEFIELD pf;
+
+ pf.name = "reply-to";
+ set_default_hdrval(&pf, *custom);
+ if(pf.textbuf){
+ if(pf.textbuf[0]) /* empty is not default */
+ (*outgoing)->reply_to = mail_newaddr();
+
+ fs_give((void **)&pf.textbuf);
+ }
+ }
+ }
+ else if(our_replyto == 'F'){
+ int add_to_role = 0;
+
+ /*
+ * The reply-to is real. If it is different from the default
+ * reply-to, put it in the role so that it will show up when
+ * the user edits.
+ */
+ if(hdr_is_in_list("reply-to", *custom)){
+ PINEFIELD pf;
+ char *str;
+
+ pf.name = "reply-to";
+ set_default_hdrval(&pf, *custom);
+ if(pf.textbuf && pf.textbuf[0]){
+ if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
+ if(!strcmp(str, pf.textbuf)){
+ /* standard value, leave it alone */
+ ;
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ fs_give((void **)&str);
+ }
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ if(pf.textbuf)
+ fs_give((void **)&pf.textbuf);
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ if(add_to_role && role && (*role == NULL || added_to_role)){
+ if(*role == NULL){
+ added_to_role++;
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ }
+
+ (*role)->replyto = (*outgoing)->reply_to;
+ (*outgoing)->reply_to = NULL;
+ }
+ }
+ else{
+ /* this is a bogus c-client generated replyto */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+ }
+
+ if((smtp_servers || nntp_servers)
+ && role && (*role == NULL || added_to_role)){
+ if(*role == NULL){
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ }
+
+ if(smtp_servers)
+ (*role)->smtp = smtp_servers;
+ if(nntp_servers)
+ (*role)->nntp = nntp_servers;
+ }
+
+ if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
+ (*outgoing)->subject = cpystr("");
+
+ if(!pine_generated){
+ /*
+ * Now, this is interesting. We should have found
+ * the "fcc:" field if pine wrote the message being
+ * redrafted. Hence, we probably can't trust the
+ * "originator" type fields, so we'll blast them and let
+ * them get set later in pine_send. This should allow
+ * folks with custom or edited From's and such to still
+ * use redraft reasonably, without inadvertently sending
+ * messages that appear to be "From" others...
+ */
+ if((*outgoing)->from)
+ mail_free_address(&(*outgoing)->from);
+
+ /*
+ * Ditto for Reply-To and Sender...
+ */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+
+ if((*outgoing)->sender)
+ mail_free_address(&(*outgoing)->sender);
+ }
+
+ if(!pine_generated || !(flags & REDRAFT_DEL)){
+
+ /*
+ * Generate a fresh message id for pretty much the same
+ * reason From and such got wacked...
+ * Also, if we're coming from a form letter, we need to
+ * generate a different id each time.
+ */
+ if((*outgoing)->message_id)
+ fs_give((void **)&(*outgoing)->message_id);
+
+ (*outgoing)->message_id = generate_message_id();
+ }
+
+ if(b && b->type != TYPETEXT){
+ if(b->type == TYPEMULTIPART){
+ if(strucmp(b->subtype, "mixed")){
+ q_status_message1(SM_INFO, 3, 4,
+ "Converting Multipart/%s to Multipart/Mixed",
+ b->subtype);
+ fs_give((void **)&b->subtype);
+ b->subtype = cpystr("mixed");
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Unable to resume type %s/%s message",
+ body_types[b->type], b->subtype);
+ return(redraft_cleanup(streamp, TRUE, flags));
+ }
+ }
+
+ gf_set_so_writec(&pc, so);
+
+ if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
+ *body = copy_body(NULL, b);
+ part = (*body)->nested.part;
+ part->body.contents.text.data = (void *)so;
+ set_mime_type_by_grope(&part->body);
+ if(part->body.type != TYPETEXT){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Unable to resume; first part is non-text: %s/%s",
+ body_types[part->body.type],
+ part->body.subtype);
+ return(redraft_cleanup(streamp, TRUE, flags));
+ }
+
+ if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
+ /* let outgoing routines decide on charset */
+ if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
+ set_parameter(&part->body.parameter, "charset", NULL);
+
+ fs_give((void **) &charset);
+ }
+
+ ps_global->postpone_no_flow = 1;
+
+ get_body_part_text(stream, &b->nested.part->body,
+ cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
+ ps_global->postpone_no_flow = 0;
+
+ if(!fetch_contents(stream, cont_msg, NULL, *body))
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error including all message parts"));
+ }
+ else{
+ *body = mail_newbody();
+ (*body)->type = TYPETEXT;
+ if(b->subtype)
+ (*body)->subtype = cpystr(b->subtype);
+
+ if((charset = parameter_val(b->parameter,"charset")) != NULL){
+ /* let outgoing routines decide on charset */
+ if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
+ fs_give((void **) &charset);
+ else{
+ (*body)->parameter = mail_newbody_parameter();
+ (*body)->parameter->attribute = cpystr("charset");
+ if(utf8_charset(charset)){
+ fs_give((void **) &charset);
+ (*body)->parameter->value = cpystr("UTF-8");
+ }
+ else
+ (*body)->parameter->value = charset;
+ }
+ }
+
+ (*body)->contents.text.data = (void *)so;
+ ps_global->postpone_no_flow = 1;
+ get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
+ NULL, NULL, gbpt_flags);
+ ps_global->postpone_no_flow = 0;
+ }
+
+ gf_clear_so_writec(so);
+
+ /* We have what we want, blast this message... */
+ if((flags & REDRAFT_DEL)
+ && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
+ && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
+ mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
+ }
+ else
+ return(redraft_cleanup(streamp, TRUE, flags));
+
+ return(redraft_cleanup(streamp, FALSE, flags));
+}
+
+
+/*----------------------------------------------------------------------
+ Clear deleted messages from given stream and expunge if necessary
+
+Args: stream --
+ problem --
+
+ ----*/
+int
+redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
+{
+ MAILSTREAM *stream;
+
+ if(!(streamp && *streamp))
+ return(0);
+
+ if(!problem && streamp && (stream = *streamp)){
+ if(stream->nmsgs){
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(stream); /* clean out deleted */
+ ps_global->expunge_in_progress = 0;
+ }
+
+ if(!stream->nmsgs){ /* close and delete folder */
+ int do_the_broach = 0;
+ char *mbox = NULL;
+
+ if(stream){
+ if(stream->original_mailbox && stream->original_mailbox[0])
+ mbox = cpystr(stream->original_mailbox);
+ else if(stream->mailbox && stream->mailbox[0])
+ mbox = cpystr(stream->mailbox);
+ }
+
+ /* if it is current, we have to change folders */
+ if(stream == ps_global->mail_stream)
+ do_the_broach++;
+
+ /*
+ * This is the stream to the empty postponed-msgs folder.
+ * We are going to delete the folder in a second. It is
+ * probably preferable to unselect the mailbox and leave
+ * this stream open for re-use instead of actually closing it,
+ * so we do that if possible.
+ */
+ if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
+ /*
+ * This does the UNSELECT on the stream. A NULL
+ * return should mean that something went wrong and
+ * a mail_close already happened, so that should have
+ * cleaned things up in the callback.
+ */
+ if((stream=mail_open(stream, stream->mailbox,
+ OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
+ /* now close it so it is put into the stream cache */
+ sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
+ pine_mail_close(stream);
+ }
+ }
+ else
+ pine_mail_actually_close(stream);
+
+ *streamp = NULL;
+
+ if(do_the_broach){
+ ps_global->mail_stream = NULL; /* already closed above */
+ }
+
+ if(mbox && !pine_mail_delete(NULL, mbox))
+ q_status_message1(SM_ORDER|SM_DING, 3, 3,
+ /* TRANSLATORS: Arg is a mailbox name */
+ _("Can't delete %s"), mbox);
+
+ if(mbox)
+ fs_give((void **) &mbox);
+ }
+ }
+
+ return(!problem);
+}
+
+
+/*----------------------------------------------------------------------
+ Parse the given header text for any given fields
+
+Args: text -- Text to parse for fcc and attachments refs
+ fields -- array of field names to look for
+ values -- array of pointer to save values to, returned NULL if
+ fields isn't in text.
+
+This function simply looks for the given fields in the given header
+text string.
+NOTE: newlines are expected CRLF, and we'll ignore continuations
+ ----*/
+void
+simple_header_parse(char *text, char **fields, char **values)
+{
+ int i, n;
+ char *p, *t;
+
+ for(i = 0; fields[i]; i++)
+ values[i] = NULL; /* clear values array */
+
+ /*---- Loop until the end of the header ----*/
+ for(p = text; *p; ){
+ for(i = 0; fields[i]; i++) /* find matching field? */
+ if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
+ for(p += n + 1; *p; p++){ /* find start of value */
+ if(*p == '\015' && *(p+1) == '\012'
+ && !isspace((unsigned char) *(p+2)))
+ break;
+
+ if(!isspace((unsigned char) *p))
+ break; /* order here is key... */
+ }
+
+ if(!values[i]){ /* if we haven't already */
+ values[i] = fs_get(strlen(text) + 1);
+ values[i][0] = '\0'; /* alloc space for it */
+ }
+
+ if(*p && *p != '\015'){ /* non-blank value. */
+ t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
+ while(*p){ /* check for cont'n lines */
+ if(*p == '\015' && *(p+1) == '\012'){
+ if(isspace((unsigned char) *(p+2))){
+ p += 2;
+ continue;
+ }
+ else
+ break;
+ }
+
+ *t++ = *p++;
+ }
+
+ *t = '\0';
+ }
+
+ break;
+ }
+
+ /* Skip to end of line, what ever it was */
+ for(; *p ; p++)
+ if(*p == '\015' && *(p+1) == '\012'){
+ p += 2;
+ break;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ build a fresh REPLY_S from the given string (see pine_send for format)
+
+Args: s -- "X-Reply-UID" header value
+
+Returns: filled in REPLY_S or NULL on parse error
+ ----*/
+REPLY_S *
+build_reply_uid(char *s)
+{
+ char *p, *prefix = NULL, *val, *seq, *mbox;
+ int i, nseq, forwarded = 0;
+ REPLY_S *reply = NULL;
+
+ /* FORMAT: (n prefix)(n validity uidlist)mailbox */
+ /* if 'n prefix' is empty, uid list represents forwarded msgs */
+ if(*s == '('){
+ if(*(p = s + 1) == ')'){
+ forwarded = 1;
+ }
+ else{
+ for(; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+
+ if((i = atoi(s+1)) && i < strlen(p)){
+ prefix = p;
+ *(p += i) = '\0';
+ }
+ }
+ else
+ return(NULL);
+ }
+
+ if(*++p == '(' && *++p){
+ for(seq = p; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+ for(val = p; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+
+ if((nseq = atoi(seq)) && isdigit(*(seq = p))
+ && (p = strchr(p, ')')) && *(mbox = ++p)){
+ imapuid_t *uidl;
+
+ uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
+ for(i = 0; i < nseq; i++)
+ if((p = strchr(seq,',')) != NULL){
+ *p = '\0';
+ if((uidl[i]= strtoul(seq,NULL,10)) != 0)
+ seq = ++p;
+ else
+ break;
+ }
+ else if((p = strchr(seq, ')')) != NULL){
+ if((uidl[i] = strtoul(seq,NULL,10)) != 0)
+ i++;
+
+ break;
+ }
+
+ if(i == nseq){
+ reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
+ memset(reply, 0, sizeof(REPLY_S));
+ reply->uid = 1;
+ reply->data.uid.validity = strtoul(val, NULL, 10);
+ if(forwarded)
+ reply->forwarded = 1;
+ else
+ reply->prefix = cpystr(prefix);
+
+ reply->mailbox = cpystr(mbox);
+ uidl[nseq] = 0;
+ reply->data.uid.msgs = uidl;
+ }
+ else
+ fs_give((void **) &uidl);
+ }
+ }
+ }
+ }
+ }
+
+ return(reply);
+}
+
+
+/*
+ * pine_new_env - allocate a new METAENV and fill it in.
+ */
+METAENV *
+pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
+{
+ int cnt, i, stdcnt;
+ char *p;
+ PINEFIELD *pfields, *pf, **sending_order;
+ METAENV *header;
+
+ header = (METAENV *) fs_get(sizeof(METAENV));
+
+ /* how many fields are there? */
+ for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
+ ;
+
+ stdcnt = cnt;
+
+ for(pf = custom; pf; pf = pf->next)
+ cnt++;
+
+ /* temporary PINEFIELD array */
+ i = (cnt + 1) * sizeof(PINEFIELD);
+ pfields = (PINEFIELD *)fs_get((size_t) i);
+ memset(pfields, 0, (size_t) i);
+
+ i = (cnt + 1) * sizeof(PINEFIELD *);
+ sending_order = (PINEFIELD **)fs_get((size_t) i);
+ memset(sending_order, 0, (size_t) i);
+
+ header->env = outgoing;
+ header->local = pfields;
+ header->custom = custom;
+ header->sending_order = sending_order;
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+# define NN 4
+#else
+# define NN 3
+#endif
+
+ /* initialize pfield */
+ pf = pfields;
+ for(i=0; i < stdcnt; i++, pf++){
+
+ pf->name = cpystr(pf_template[i].name);
+ if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
+ /* slide string over so it is Sender instead of X-X-Sender */
+ for(p=pf->name; *(p+1); p++)
+ *p = *(p+4);
+
+ pf->type = pf_template[i].type;
+ pf->canedit = pf_template[i].canedit;
+ pf->rcptto = pf_template[i].rcptto;
+ pf->writehdr = pf_template[i].writehdr;
+ pf->localcopy = pf_template[i].localcopy;
+ pf->extdata = NULL; /* unused */
+ pf->next = pf + 1;
+
+ switch(pf->type){
+ case FreeText:
+ switch(i){
+ case N_AUTHRCVD:
+ sending_order[0] = pf;
+ break;
+
+ case N_NEWS:
+ pf->text = &outgoing->newsgroups;
+ sending_order[1] = pf;
+ break;
+
+ case N_DATE:
+ pf->text = (char **) &outgoing->date;
+ sending_order[2] = pf;
+ break;
+
+ case N_INREPLY:
+ pf->text = &outgoing->in_reply_to;
+ sending_order[NN+9] = pf;
+ break;
+
+ case N_MSGID:
+ pf->text = &outgoing->message_id;
+ sending_order[NN+10] = pf;
+ break;
+
+ case N_REF: /* won't be used here */
+ sending_order[NN+11] = pf;
+ break;
+
+ case N_PRIORITY:
+ sending_order[NN+12] = pf;
+ break;
+
+ case N_USERAGENT:
+ pf->text = &pf->textbuf;
+ pf->textbuf = generate_user_agent();
+ sending_order[NN+13] = pf;
+ break;
+
+ case N_POSTERR: /* won't be used here */
+ sending_order[NN+14] = pf;
+ break;
+
+ case N_RPLUID: /* won't be used here */
+ sending_order[NN+15] = pf;
+ break;
+
+ case N_RPLMBOX: /* won't be used here */
+ sending_order[NN+16] = pf;
+ break;
+
+ case N_SMTP: /* won't be used here */
+ sending_order[NN+17] = pf;
+ break;
+
+ case N_NNTP: /* won't be used here */
+ sending_order[NN+18] = pf;
+ break;
+
+ case N_CURPOS: /* won't be used here */
+ sending_order[NN+19] = pf;
+ break;
+
+ case N_OURREPLYTO: /* won't be used here */
+ sending_order[NN+20] = pf;
+ break;
+
+ case N_OURHDRS: /* won't be used here */
+ sending_order[NN+21] = pf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Internal error: 1)FreeText header %s", comatose(i));
+ break;
+ }
+
+ break;
+
+ case Attachment:
+ break;
+
+ case Address:
+ switch(i){
+ case N_FROM:
+ sending_order[3] = pf;
+ pf->addr = &outgoing->from;
+ break;
+
+ case N_TO:
+ sending_order[NN+2] = pf;
+ pf->addr = &outgoing->to;
+ if(tobufpp)
+ (*tobufpp) = &pf->scratch;
+
+ break;
+
+ case N_CC:
+ sending_order[NN+3] = pf;
+ pf->addr = &outgoing->cc;
+ break;
+
+ case N_BCC:
+ sending_order[NN+4] = pf;
+ pf->addr = &outgoing->bcc;
+ break;
+
+ case N_REPLYTO:
+ sending_order[NN+1] = pf;
+ pf->addr = &outgoing->reply_to;
+ break;
+
+ case N_LCC: /* won't be used here */
+ sending_order[NN+7] = pf;
+ break;
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ case N_SENDER:
+ sending_order[4] = pf;
+ pf->addr = &outgoing->sender;
+ break;
+#endif
+
+ case N_NOBODY: /* won't be used here */
+ sending_order[NN+5] = pf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Internal error: Address header %s", comatose(i));
+ break;
+ }
+ break;
+
+ case Fcc:
+ sending_order[NN+8] = pf;
+ pf->text = fccp;
+ break;
+
+ case Subject:
+ sending_order[NN+6] = pf;
+ pf->text = &outgoing->subject;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Unknown header type %d in pine_new_send", (void *)pf->type);
+ break;
+ }
+ }
+
+ if(((--pf)->next = custom) != NULL){
+ i--;
+
+ /*
+ * NOTE: "i" is assumed to now index first custom field in sending
+ * order.
+ */
+ for(pf = pf->next; pf && pf->name; pf = pf->next){
+ if(pf->standard)
+ continue;
+
+ pf->canedit = 1;
+ pf->rcptto = 0;
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+
+ switch(pf->type){
+ case Address:
+ if(pf->addr){ /* better be set */
+ char *addr = NULL;
+ BuildTo bldto;
+
+ bldto.type = Str;
+ bldto.arg.str = pf->textbuf;
+
+ sending_order[i++] = pf;
+ /* change default text into an ADDRESS */
+ /* strip quotes around whole default */
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+ }
+
+ break;
+
+ case FreeText:
+ sending_order[i++] = pf;
+ pf->text = &pf->textbuf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
+ (void *)pf->type);
+ break;
+ }
+ }
+ }
+
+
+ return(header);
+}
+
+
+void
+pine_free_env(METAENV **menv)
+{
+ int cnt;
+
+
+ if((*menv)->local){
+ for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
+ ;
+
+ for(; cnt >= 0; cnt--){
+ if((*menv)->local[cnt].textbuf)
+ fs_give((void **) &(*menv)->local[cnt].textbuf);
+
+ fs_give((void **) &(*menv)->local[cnt].name);
+ }
+
+ fs_give((void **) &(*menv)->local);
+ }
+
+ if((*menv)->sending_order)
+ fs_give((void **) &(*menv)->sending_order);
+
+ fs_give((void **) menv);
+}
+
+
+/*----------------------------------------------------------------------
+ Check for addresses the user is not permitted to send to, or probably
+ doesn't want to send to
+
+Returns: 0 if OK
+ 1 if there are only empty groups
+ -1 if the message shouldn't be sent
+
+Queues a message indicating what happened
+ ---*/
+int
+check_addresses(METAENV *header)
+{
+ PINEFIELD *pf;
+ ADDRESS *a;
+ int send_daemon = 0, rv = CA_EMPTY;
+
+ /*---- Is he/she trying to send mail to the mailer-daemon ----*/
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ for(a = *pf->addr; a != NULL; a = a->next){
+ if(a->host && (a->host[0] == '.'
+ || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
+ && a->host[0] == '@'))){
+ q_status_message2(SM_ORDER, 4, 7,
+ /* TRANSLATORS: First arg is the address we can't
+ send to, second arg is "not in addressbook". */
+ _("Can't send to address %s: %s"),
+ a->mailbox,
+ (a->host[0] == '.')
+ ? a->host
+ : _("not in addressbook"));
+ return(CA_BAD);
+ }
+ else if(ps_global->restricted
+ && !address_is_us(*pf->addr, ps_global)){
+ q_status_message(SM_ORDER, 3, 3,
+ "Restricted demo version of Alpine. You may only send mail to yourself");
+ return(CA_BAD);
+ }
+ else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
+ send_daemon = 1;
+ rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
+ }
+ else if(a->mailbox && a->host){
+ rv = CA_OK;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * If this isn't general enough we can modify it. The value passed in
+ * is expected to be one of the desc settings from the priorities array,
+ * like "High". The header value is X-Priority: 2 (High)
+ * or something similar. If value doesn't match any of the values then
+ * the actual value is used instead.
+ */
+PINEFIELD *
+set_priority_header(METAENV *header, char *value)
+{
+ PINEFIELD *pf;
+
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
+ break;
+
+ if(pf){
+ if(pf->textbuf)
+ fs_give((void **) &pf->textbuf);
+
+ if(value){
+ PRIORITY_S *p;
+
+ for(p = priorities; p && p->desc; p++)
+ if(!strcmp(p->desc, value))
+ break;
+
+ if(p && p->desc){
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
+ pf->textbuf = cpystr(buf);
+ }
+ else
+ pf->textbuf = cpystr(value);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Set answered flags for messages specified by reply structure
+
+Args: reply --
+
+Returns: with appropriate flags set and index cache entries suitably tweeked
+----*/
+void
+update_answered_flags(REPLY_S *reply)
+{
+ char *seq = NULL, *p;
+ long i, ourstream = 0, we_cancel = 0;
+ MAILSTREAM *stream = NULL;
+
+ /* nothing to flip in a pseudo reply */
+ if(reply && (reply->msgno || reply->uid)){
+ int j;
+ MAILSTREAM *m;
+
+ /*
+ * If an established stream will do, use it, else
+ * build one unless we have an array of msgno's...
+ *
+ * I was just mimicking what was already here. I don't really
+ * understand why we use strcmp instead of same_stream_and_mailbox().
+ * Or sp_stream_get(reply->mailbox, SP_MATCH).
+ * Hubert 2003-07-09
+ */
+ for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
+ m = ps_global->s_pool.streams[j];
+ if(m && reply->mailbox && m->mailbox
+ && !strcmp(reply->mailbox, m->mailbox))
+ stream = m;
+ }
+
+ if(!stream && reply->msgno)
+ return;
+
+ /*
+ * This is here only for people who ran pine4.42 and are
+ * processing postponed mail from 4.42 now. Pine4.42 saved the
+ * original mailbox name in the canonical name's position in
+ * the postponed-msgs folder so it won't match the canonical
+ * name from the stream.
+ */
+ if(!stream && (!reply->origmbox ||
+ (reply->mailbox &&
+ !strcmp(reply->origmbox, reply->mailbox))))
+ stream = sp_stream_get(reply->mailbox, SP_MATCH);
+
+ /* TRANSLATORS: program is busy updating the Answered flags so warns user */
+ we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
+ if(!stream){
+ if((stream = pine_mail_open(NULL,
+ reply->origmbox ? reply->origmbox
+ : reply->mailbox,
+ OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
+ NULL)) != NULL){
+ ourstream++;
+ }
+ else{
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return;
+ }
+ }
+
+ if(stream->uid_validity == reply->data.uid.validity){
+ for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
+ if(i){
+ sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
+ SIZEOF_20KBUF-(p-tmp_20k_buf));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ if(reply->forwarded){
+ /*
+ * $Forwarded is a regular keyword so we only try to
+ * set it if the stream allows keywords.
+ * We could mess up if the stream has keywords but just
+ * isn't allowing anymore and $Forwarded already exists,
+ * but what are the odds?
+ */
+ if(stream && stream->kwd_create)
+ mail_flag(stream, seq = cpystr(tmp_20k_buf),
+ FORWARDED_FLAG,
+ ST_SET | ((reply->uid) ? ST_UID : 0L));
+ }
+ else
+ mail_flag(stream, seq = cpystr(tmp_20k_buf),
+ "\\ANSWERED",
+ ST_SET | ((reply->uid) ? ST_UID : 0L));
+
+ if(seq)
+ fs_give((void **)&seq);
+ }
+
+ if(ourstream)
+ pine_mail_close(stream); /* clean up dangling stream */
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+ }
+}
+
+
+/*
+ * phone_home_from - make phone home request's from address IMpersonal.
+ * Doesn't include user's personal name.
+ */
+ADDRESS *
+phone_home_from(void)
+{
+ ADDRESS *addr = mail_newaddr();
+ char tmp[32];
+
+ /* garble up mailbox name */
+ snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
+ tmp[sizeof(tmp)-1] = '\0';
+ addr->mailbox = cpystr(tmp);
+ addr->host = cpystr(ps_global->maildomain);
+ return(addr);
+}
+
+
+/*
+ * one-way-hash a username into an 8-digit decimal number
+ *
+ * Corey Satten, corey@cac.washington.edu, 7/15/98
+ */
+unsigned int
+phone_home_hash(char *s)
+{
+ unsigned int h;
+
+ for (h=0; *s; ++s) {
+ if (h & 1)
+ h = (h>>1) | (PH_MAXHASH/2);
+ else
+ h = (h>>1);
+
+ h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
+ }
+
+ return (h);
+}
+
+
+/*----------------------------------------------------------------------
+ Call the mailer, SMTP, sendmail or whatever
+
+Args: header -- full header (envelope and local parts) of message to send
+ body -- The full body of the message including text
+ alt_smtp_servers --
+ verbosefile -- non-null means caller wants verbose interaction and the resulting
+ output file name to be returned
+
+Returns: -1 if failed, 1 if succeeded
+----*/
+int
+call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
+ int flags, void (*bigresult_f)(char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char error_buf[200], *error_mess = NULL, *postcmd;
+ ADDRESS *a;
+ ENVELOPE *fake_env = NULL;
+ int addr_error_count, we_cancel = 0;
+ long smtp_opts = 0L;
+ char *verbose_file = NULL;
+ BODY *bp = NULL;
+ PINEFIELD *pf;
+ BODY *origBody = body;
+
+ dprint((4, "Sending mail...\n"));
+
+ /* Check for any recipients */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ break;
+
+ if(!pf){
+ q_status_message(SM_ORDER,3,3,
+ _("Can't send message. No recipients specified!"));
+ return(0);
+ }
+
+#ifdef SMIME
+ if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
+ int result;
+
+ STORE_S *so = lmc.so;
+ lmc.so = NULL;
+
+ result = 1;
+
+ if(ps_global->smime->do_encrypt)
+ result = encrypt_outgoing_message(header, &body);
+
+ /* need to free new body from encrypt if sign fails? */
+ if(result && ps_global->smime->do_sign)
+ result = sign_outgoing_message(header, &body, ps_global->smime->do_encrypt);
+
+ lmc.so = so;
+
+ if(!result)
+ return 0;
+ }
+#endif
+
+ /* set up counts and such to keep track sent percentage */
+ send_bytes_sent = 0;
+ gf_filter_init(); /* zero piped byte count, 'n */
+ send_bytes_to_send = send_body_size(body); /* count body bytes */
+ ps_global->c_client_error[0] = error_buf[0] = '\0';
+ we_cancel = busy_cue(_("Sending mail"),
+ send_bytes_to_send ? sent_percent : NULL, 0);
+
+#ifndef _WINDOWS
+
+ /* try posting via local "<mta> <-t>" if specified */
+ if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
+ if(error_buf[0])
+ error_mess = error_buf;
+
+ goto done;
+ }
+
+#endif
+
+ /*
+ * If the user's asked for it, and we find that the first text
+ * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
+ */
+ if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
+ smtp_opts |= SOP_8BITMIME;
+
+#ifdef DEBUG
+#ifndef DEBUGJOURNAL
+ if(debug > 5 || (flags & CM_VERBOSE))
+#endif
+ smtp_opts |= SOP_DEBUG;
+#endif
+
+ if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
+ smtp_opts |= SOP_DSN;
+ if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
+ if(flags & CM_DSN_DELAY)
+ smtp_opts |= SOP_DSN_NOTIFY_DELAY;
+ if(flags & CM_DSN_SUCCESS)
+ smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
+
+ /*
+ * If it isn't Never, then we're always going to let them
+ * know about failures. This means we don't allow for the
+ * possibility of setting delay or success without failure.
+ */
+ smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
+
+ if(flags & CM_DSN_FULL)
+ smtp_opts |= SOP_DSN_RETURN_FULL;
+ }
+ }
+
+
+ /*
+ * Set global header pointer so post_rfc822_output can get at it when
+ * it's called back from c-client's sending routine...
+ */
+ send_header = header;
+
+ /*
+ * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
+ * The purpose is to give smtp_mail the list for SMTP RCPT when
+ * there are recipients in pine's METAENV that are outside c-client's
+ * envelope.
+ *
+ * NOTE: If there aren't any, don't bother. Dealt with it below.
+ */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
+ && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
+ || *pf->addr == header->env->bcc))
+ break;
+
+ if(pf && pf->name){
+ ADDRESS **tail;
+
+ fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
+ memset(fake_env, 0, sizeof(ENVELOPE));
+ fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
+ tail = &(fake_env->to);
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
+ *tail = rfc822_cpy_adr(*pf->addr);
+ while(*tail)
+ tail = &((*tail)->next);
+ }
+ }
+
+ /*
+ * Install our rfc822 output routine
+ */
+ sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
+ (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
+
+ /*
+ * Allow for verbose posting
+ */
+ (void) mail_parameters(NULL, SET_SMTPVERBOSE,
+ (void *) pine_smtp_verbose_out);
+
+ /*
+ * We do this because we want mm_log to put the error message into
+ * c_client_error instead of showing it itself.
+ */
+ ps_global->noshow_error = 1;
+
+ /*
+ * OK, who posts what? We tried an mta_handoff above, but there
+ * was either none specified or we decided not to use it. So,
+ * if there's an smtp-server defined anywhere,
+ */
+ if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
+ /*---------- SMTP ----------*/
+ dprint((4, "call_mailer: via TCP (%s)\n",
+ alt_smtp_servers[0]));
+ TIME_STAMP("smtp-open start (tcp)", 1);
+ sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
+ }
+ else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
+ && ps_global->VAR_SMTP_SERVER[0][0]){
+ /*---------- SMTP ----------*/
+ dprint((4, "call_mailer: via TCP\n"));
+ TIME_STAMP("smtp-open start (tcp)", 1);
+ sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
+ }
+ else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
+ char *cmdlist[2];
+
+ /*----- Send via LOCAL SMTP agent ------*/
+ dprint((4, "call_mailer: via \"%s\"\n", postcmd));
+
+ TIME_STAMP("smtp-open start (pipe)", 1);
+ fs_give((void **) &postcmd);
+ cmdlist[0] = "localhost";
+ cmdlist[1] = NULL;
+ sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
+ SMTPTCPPORT, smtp_opts);
+/* BUG: should provide separate stderr output! */
+ }
+
+ ps_global->noshow_error = 0;
+
+ TIME_STAMP("smtp open", 1);
+ if(sending_stream){
+ unsigned short save_encoding, added_encoding;
+
+ dprint((1, "Opened SMTP server \"%s\"\n",
+ net_host(sending_stream->netstream)
+ ? net_host(sending_stream->netstream) : "?"));
+
+ if(flags & CM_VERBOSE){
+ TIME_STAMP("verbose start", 1);
+ if((verbose_file = temp_nam(NULL, "sd")) != NULL){
+ if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
+ if(!smtp_verbose(sending_stream)){
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ "Mail not sent. VERBOSE mode error%s%.50s.",
+ (sending_stream && sending_stream->reply)
+ ? ": ": "",
+ (sending_stream && sending_stream->reply)
+ ? sending_stream->reply : "");
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+ }
+ else{
+ our_unlink(verbose_file);
+ strncpy(error_mess = error_buf,
+ "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+ }
+ else{
+ strncpy(error_mess = error_buf,
+ "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+
+ TIME_STAMP("verbose end", 1);
+ }
+
+ /*
+ * Before we actually send data, see if we have to protect
+ * the first text body part from getting encoded. We protect
+ * it from getting encoded in "pine_rfc822_output_body" by
+ * temporarily inventing a synonym for ENC8BIT...
+ * This works like so:
+ * Suppose bp->encoding is set to ENC8BIT.
+ * We change that here to some unused value (added_encoding) and
+ * set body_encodings[added_encoding] to "8BIT".
+ * Then post_rfc822_output is called which calls
+ * pine_rfc822_output_body. Inside that routine
+ * pine_write_body_header writes out the encoding for the
+ * part. Normally it would see encoding == ENC8BIT and it would
+ * change that to QUOTED-PRINTABLE, but since encoding has been
+ * set to added_encoding it uses body_encodings[added_encoding]
+ * which is "8BIT" instead. Then the actual body is written by
+ * pine_write_body_header which does not do the gf_8bit_qp
+ * filtering because encoding != ENC8BIT (instead it's equal
+ * to added_encoding).
+ */
+ if(bp && sending_stream->protocol.esmtp.eightbit.ok
+ && sending_stream->protocol.esmtp.eightbit.want){
+ int i;
+
+ for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
+ ;
+
+ if(i > ENCMAX){ /* no empty encoding slots! */
+ bp = NULL;
+ }
+ else {
+ added_encoding = i;
+ body_encodings[added_encoding] = body_encodings[ENC8BIT];
+ save_encoding = bp->encoding;
+ bp->encoding = added_encoding;
+ }
+ }
+
+ if(sending_stream->protocol.esmtp.ok
+ && sending_stream->protocol.esmtp.dsn.want
+ && !sending_stream->protocol.esmtp.dsn.ok)
+ q_status_message(SM_ORDER,3,3,
+ _("Delivery Status Notification not available from this server."));
+
+ TIME_STAMP("smtp start", 1);
+ if(!error_mess && !smtp_mail(sending_stream, "MAIL",
+ fake_env ? fake_env : header->env, body)){
+
+ snprintf(error_buf, sizeof(error_buf),
+ _("Mail not sent. Sending error%s%s"),
+ (sending_stream && sending_stream->reply) ? ": ": ".",
+ (sending_stream && sending_stream->reply)
+ ? sending_stream->reply : "");
+ error_buf[sizeof(error_buf)-1] = '\0';
+ dprint((1, error_buf));
+ addr_error_count = 0;
+ if(fake_env){
+ for(a = fake_env->to; a != NULL; a = a->next)
+ if(a->error != NULL){
+ if(addr_error_count++ < MAX_ADDR_ERROR){
+
+ /*
+ * Too complicated to figure out which header line
+ * has the error in the fake_env case, so just
+ * leave cursor at default.
+ */
+
+
+ if(error_mess) /* previous error? */
+ q_status_message(SM_ORDER, 4, 7, error_mess);
+
+ error_mess = tidy_smtp_mess(a->error,
+ _("Mail not sent: %.80s"),
+ error_buf, sizeof(error_buf));
+ }
+
+ dprint((1, "Send Error: \"%s\"\n",
+ a->error));
+ }
+ }
+ else{
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ for(a = *pf->addr; a != NULL; a = a->next)
+ if(a->error != NULL){
+ if(addr_error_count++ < MAX_ADDR_ERROR){
+
+ if(error_mess) /* previous error? */
+ q_status_message(SM_ORDER, 4, 7, error_mess);
+
+ error_mess = tidy_smtp_mess(a->error,
+ _("Mail not sent: %.80s"),
+ error_buf, sizeof(error_buf));
+ }
+
+ dprint((1, "Send Error: \"%s\"\n",
+ a->error));
+ }
+ }
+
+ if(!error_mess)
+ error_mess = error_buf;
+ }
+
+ /* repair modified "body_encodings" array? */
+ if(bp && sending_stream->protocol.esmtp.eightbit.ok
+ && sending_stream->protocol.esmtp.eightbit.want){
+ body_encodings[added_encoding] = NULL;
+ bp->encoding = save_encoding;
+ }
+
+ TIME_STAMP("smtp closing", 1);
+ smtp_close(sending_stream);
+ sending_stream = NULL;
+ TIME_STAMP("smtp done", 1);
+ }
+ else if(!error_mess){
+ snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
+ ps_global->c_client_error[0] ? ": " : "",
+ ps_global->c_client_error);
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+
+ if(verbose_file){
+ if(verbose_send_output){
+ TIME_STAMP("verbose start", 1);
+ fclose(verbose_send_output);
+ verbose_send_output = NULL;
+ q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
+
+ if(bigresult_f)
+ (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
+
+ TIME_STAMP("verbose end", 1);
+ }
+
+ fs_give((void **)&verbose_file);
+ }
+
+ /*
+ * Restore original 822 emitter...
+ */
+ (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
+
+ if(fake_env)
+ mail_free_envelope(&fake_env);
+
+ done:
+
+#ifdef SMIME
+ /* Free replacement encrypted body */
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
+
+ if(body->type == TYPEMULTIPART){
+ /* Just get rid of first part, it's actually origBody */
+ void *x = body->nested.part;
+
+ body->nested.part = body->nested.part->next;
+
+ fs_give(&x);
+ }
+
+ pine_free_body(&body);
+ }
+#endif
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ TIME_STAMP("call_mailer done", 1);
+ /*-------- Did message make it ? ----------*/
+ if(error_mess){
+ /*---- Error sending mail -----*/
+ if(lmc.so && !lmc.all_written)
+ so_give(&lmc.so);
+
+ if(error_mess){
+ q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
+ dprint((1, "call_mailer ERROR: %s\n", error_mess));
+ }
+
+ return(-1);
+ }
+ else{
+ lmc.all_written = 1;
+ return(1);
+ }
+}
+
+
+/*
+ * write_postponed - exported method to write the given message
+ * to the postponed folder
+ */
+int
+write_postponed(METAENV *header, struct mail_bodystruct *body)
+{
+ char **pp, *folder;
+ int rv = 0, sz;
+ CONTEXT_S *fcc_cntxt = NULL;
+ PINEFIELD *pf;
+ static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
+
+ if(!ps_global->VAR_POSTPONED_FOLDER
+ || !ps_global->VAR_POSTPONED_FOLDER[0]){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("No postponed file defined"));
+ return(-1);
+ }
+
+ folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
+
+ lmc.all_written = lmc.text_written = lmc.text_only = 0;
+
+ lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
+
+ if(lmc.so){
+ /* BUG: writem sufficient ? */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ for(pp = writem; *pp; pp++)
+ if(!strucmp(pf->name, *pp)){
+ pf->localcopy = 1;
+ pf->writehdr = 1;
+ break;
+ }
+
+ /*
+ * Work around c-client reply-to bug. C-client will
+ * return a reply_to in an envelope even if there is
+ * no reply-to header field. We want to note here whether
+ * the reply-to is real or not.
+ */
+ if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
+ for(pf = header->local; pf; pf = pf->next)
+ if(!strcmp(pf->name, "Reply-To")){
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+ if(header->env->reply_to)
+ pf->textbuf = cpystr("Full");
+ else
+ pf->textbuf = cpystr("Empty");
+ }
+
+ /*
+ * Write the list of custom headers to the
+ * X-Our-Headers header so that we can recover the
+ * list in redraft.
+ */
+ sz = 0;
+ for(pf = header->custom; pf && pf->name; pf = pf->next)
+ sz += strlen(pf->name) + 1;
+
+ if(sz){
+ int i;
+ char *pstart, *pend;
+
+ for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
+ ;
+
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+ pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
+ pf->text = &pf->textbuf;
+ pf->textbuf[sz] = '\0'; /* tie off overflow */
+ /* note: "pf" overloaded */
+ for(pf = header->custom; pf && pf->name; pf = pf->next){
+ int r = sz - (pend - pstart); /* remaining buffer */
+
+ if(r > 0 && r != sz){
+ r--;
+ *pend++ = ',';
+ }
+
+ sstrncpy(&pend, pf->name, r);
+ }
+ }
+
+ if(pine_rfc822_output(header, body, NULL, NULL) < 0
+ || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
+ rv = -1;
+
+ so_give(&lmc.so);
+ }
+ else {
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Can't allocate internal storage: %s ",
+ error_description(errno));
+ rv = -1;
+ }
+
+ fs_give((void **) &folder);
+ return(rv);
+}
+
+
+int
+commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
+{
+ if(fcc && *fcc){
+ lmc.all_written = lmc.text_written = 0;
+ lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
+ return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
+ }
+ else
+ lmc.so = NULL;
+
+ return(TRUE);
+}
+
+
+int
+wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
+{
+ int rv = TRUE;
+
+ if(lmc.so){
+ if(!header || pine_rfc822_output(header, body, NULL, NULL)){
+ char label[50];
+
+ strncpy(label, "Fcc", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
+ snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
+ label[sizeof(label)-1] = '\0';
+ }
+
+ rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
+ }
+ else{
+ rv = FALSE;
+ }
+
+ so_give(&lmc.so);
+ }
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Checks to make sure the fcc is available and can be opened
+
+Args: fcc -- the name of the fcc to create. It can't be NULL.
+ fcc_cntxt -- Returns the context the fcc is in.
+ force -- supress user option prompt
+
+Returns allocated storage object on success, NULL on failure
+ ----*/
+STORE_S *
+open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
+{
+ int exists, ok = 0;
+
+ ps_global->mm_log_error = 0;
+
+ /*
+ * check for fcc's existance...
+ */
+ TIME_STAMP("open_fcc start", 1);
+ if(!is_absolute_path(fcc) && context_isambig(fcc)
+ && (strucmp(ps_global->inbox_name, fcc) != 0)){
+ int flip_dot = 0;
+
+ /*
+ * Don't want to preclude a user from Fcc'ing a .name'd folder
+ */
+ if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
+ flip_dot = 1;
+ F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
+ }
+
+ /*
+ * We only want to set the "context" if fcc is an ambiguous
+ * name. Otherwise, our "relativeness" rules for contexts
+ * (implemented in context.c) might cause the name to be
+ * interpreted in the wrong context...
+ */
+ if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
+ *fcc_cntxt = ps_global->context_list;
+
+ build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
+ if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
+ if(force
+ || (pith_opt_save_create_prompt
+ && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
+
+ ps_global->noshow_error = 1;
+
+ if(context_create(*fcc_cntxt, NULL, fcc))
+ ok++;
+
+ ps_global->noshow_error = 0;
+ }
+ else
+ ok--; /* declined! */
+ }
+ else
+ ok++; /* found! */
+
+ if(flip_dot)
+ F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
+
+ free_folder_list(*fcc_cntxt);
+ }
+ else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
+ if(exists & (FEX_ISFILE | FEX_ISDIR)){
+ ok++;
+ }
+ else{
+ if(force
+ || (pith_opt_save_create_prompt
+ && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
+
+ ps_global->mm_log_error = 0;
+ ps_global->noshow_error = 1;
+
+ ok = pine_mail_create(NULL, fcc) != 0L;
+
+ ps_global->noshow_error = 0;
+ }
+ else
+ ok--; /* declined! */
+ }
+ }
+
+ TIME_STAMP("open_fcc done.", 1);
+ if(ok > 0){
+ return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
+ }
+ else{
+ int l1, l2, l3, wid, w;
+ char *errstr, tmp[MAILTMPLEN];
+ char *s1, *s2;
+
+ if(ok == 0){
+ if(ps_global->mm_log_error){
+ s1 = err_prefix ? err_prefix : "Fcc Error: ";
+ s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
+
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ l3 = strlen(ps_global->c_client_error);
+ wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
+ ? ps_global->ttyo->screen_cols : 80;
+ w = wid - l1 - l2 - 5;
+
+ snprintf(errstr = tmp, sizeof(tmp),
+ "%.99s\"%.*s%.99s\".%.99s",
+ s1,
+ (l3 > w) ? MAX(w-3,0) : MAX(w,0),
+ ps_global->c_client_error,
+ (l3 > w) ? "..." : "",
+ s2);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ }
+ else
+ errstr = _("Fcc creation error. Message NOT sent or copied.");
+ }
+ else
+ errstr = _("Fcc creation rejected. Message NOT sent or copied.");
+
+ q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
+ }
+
+ return(NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ mail_append() the fcc accumulated in temp_storage to proper destination
+
+Args: fcc -- name of folder
+ fcc_cntxt -- context for folder
+ temp_storage -- String of file where Fcc has been accumulated
+
+This copies the string of file to the actual folder, which might be IMAP
+or a disk folder. The temp_storage is freed after it is written.
+An error message is produced if this fails.
+ ----*/
+int
+write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
+ MAILSTREAM *stream, char *label, char *flags)
+{
+ STRING msg;
+ CONTEXT_S *cntxt;
+ int we_cancel = 0;
+
+ if(!tmp_storage)
+ return(0);
+
+ TIME_STAMP("write_fcc start.", 1);
+ dprint((4, "Writing %s\n", (label && *label) ? label : ""));
+ if(label && *label){
+ char msg_buf[80];
+
+ strncpy(msg_buf, "Writing ", sizeof(msg_buf));
+ msg_buf[sizeof(msg_buf)-1] = '\0';
+ strncat(msg_buf, label, sizeof(msg_buf)-10);
+ we_cancel = busy_cue(msg_buf, NULL, 0);
+ }
+ else
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ so_seek(tmp_storage, 0L, 0);
+
+/*
+ * Before changing this note that these lines depend on the
+ * definition of FCC_SOURCE.
+ */
+ INIT(&msg, mail_string, (void *)so_text(tmp_storage),
+ strlen((char *)so_text(tmp_storage)));
+
+ cntxt = fcc_cntxt;
+
+ if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ "Write to \"%s\" FAILED!!!", fcc);
+ dprint((1, "ERROR appending %s in \"%s\"",
+ fcc ? fcc : "?",
+ (cntxt && cntxt->context) ? cntxt->context : "NULL"));
+ return(0);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(label ? 0 : -1);
+
+ dprint((4, "done.\n"));
+ TIME_STAMP("write_fcc done.", 1);
+ return(1);
+}
+
+
+/*
+ * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
+ * contained.
+ */
+BODY *
+first_text_8bit(struct mail_bodystruct *body)
+{
+ if(body->type == TYPEMULTIPART) /* advance to first contained part */
+ body = &body->nested.part->body;
+
+ return((body->type == TYPETEXT && body->encoding != ENC7BIT)
+ ? body : NULL);
+}
+
+
+/*
+ * Build and return the "From:" address for outbound messages from
+ * global data...
+ */
+ADDRESS *
+generate_from(void)
+{
+ ADDRESS *addr = mail_newaddr();
+ if(ps_global->VAR_PERSONAL_NAME){
+ addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
+ removing_leading_and_trailing_white_space(addr->personal);
+ if(addr->personal[0] == '\0')
+ fs_give((void **)&addr->personal);
+ }
+
+ addr->mailbox = cpystr(ps_global->VAR_USER_ID);
+ addr->host = cpystr(ps_global->maildomain);
+ removing_leading_and_trailing_white_space(addr->mailbox);
+ removing_leading_and_trailing_white_space(addr->host);
+ return(addr);
+}
+
+
+/*
+ * set_mime_type_by_grope - sniff the given storage object to determine its
+ * type, subtype, encoding, and charset
+ *
+ * "Type" and "encoding" must be set before calling this routine.
+ * If "type" is set to something other than TYPEOTHER on entry,
+ * then that is the "type" we wish to use. Same for "encoding"
+ * using ENCOTHER instead of TYPEOTHER. Otherwise, we
+ * figure them out here. If "type" is already set, we also
+ * leave subtype alone. If not, we figure out subtype here.
+ * There is a chance that we will upgrade encoding to a "higher"
+ * level. For example, if it comes in as 7BIT we may change
+ * that to 8BIT if we find a From_ we want to escape.
+ * We may also set the charset attribute if the type is TEXT.
+ *
+ * NOTE: this is rather inefficient if the store object is a CharStar
+ * but the win is all types are handled the same
+ */
+void
+set_mime_type_by_grope(struct mail_bodystruct *body)
+{
+#define RBUFSZ (8193)
+ unsigned char *buf, *p, *bol;
+ register size_t n;
+ long max_line = 0L,
+ eight_bit_chars = 0L,
+ line_so_far = 0L,
+ len = 0L;
+ STORE_S *so = (STORE_S *)body->contents.text.data;
+ unsigned short new_encoding = ENCOTHER;
+ int we_cancel = 0;
+#ifdef ENCODE_FROMS
+ short froms = 0, dots = 0,
+ bmap = 0x1, dmap = 0x1;
+#endif
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ buf = (unsigned char *)fs_get(RBUFSZ);
+ so_seek(so, 0L, 0);
+
+ for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
+ ;
+
+ buf[n] = '\0';
+
+ if(n){ /* check first few bytes to look for magic numbers */
+ if(body->type == TYPEOTHER){
+ if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("GIF");
+ }
+ else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
+ && buf[2] == 0xFF && buf[3] == 0xE0
+ && !strncmp((char *)&buf[6], "JFIF", 4)){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("JPEG");
+ }
+ else if((buf[0] == 'M' && buf[1] == 'M')
+ || (buf[0] == 'I' && buf[1] == 'I')){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("TIFF");
+ }
+ else if((buf[0] == '%' && buf[1] == '!')
+ || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("PostScript");
+ }
+ else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("PDF");
+ }
+ else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
+ body->type = TYPEAUDIO;
+ body->subtype = cpystr("Basic");
+ }
+ else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
+ && buf[2] == 0x16 && buf[3] == 0x00){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("APPLEFILE");
+ }
+ else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
+ && buf[2] == 0x03 && buf[3] == 0x04){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("ZIP");
+ }
+
+ /*
+ * if type was set above, but no encoding specified, go
+ * ahead and make it BASE64...
+ */
+ if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
+ body->encoding = ENCBINARY;
+ }
+ }
+ else{
+ /* PROBLEM !!! */
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ if(body->encoding == ENCOTHER)
+ body->encoding = ENCBINARY;
+ }
+ }
+
+ if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ int lastchar = '\0';
+#define BREAKOUT 300 /* a value that a character can't be */
+#endif
+
+ p = bol = buf;
+ len = n;
+ while (n--){
+/* Some people don't like quoted-printable caused by leading Froms */
+#ifdef ENCODE_FROMS
+ Find_Froms(froms, dots, bmap, dmap, *p);
+#endif
+ if(*p == '\n'){
+ max_line = MAX(max_line, line_so_far + p - bol);
+ bol = NULL; /* clear beginning of line */
+ line_so_far = 0L; /* clear line count */
+#if defined(DOS) || defined(OS2)
+ /* LF with no CR!! */
+ if(lastchar != '\r') /* must be non-text data! */
+ lastchar = BREAKOUT;
+#endif
+ }
+ else if(*p & 0x80){
+ eight_bit_chars++;
+ }
+ else if(!*p){
+ /* NULL found. Unless we're told otherwise, must be binary */
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ /*
+ * The "TYPETEXT" here handles the case that the NULL
+ * comes from imported text generated by some external
+ * editor that permits or inserts NULLS. Otherwise,
+ * assume it's a binary segment...
+ */
+ new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
+
+ /*
+ * Since we've already set encoding, count this as a
+ * hi bit char and continue. The reason is that if this
+ * is text, there may be a high percentage of encoded
+ * characters, so base64 may get set below...
+ */
+ if(body->type == TYPETEXT)
+ eight_bit_chars++;
+ else
+ break;
+ }
+
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ if(lastchar != BREAKOUT)
+ lastchar = *p;
+#endif
+
+ /* read another buffer in */
+ if(n == 0){
+ if(bol)
+ line_so_far += p - bol;
+
+ for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
+ ;
+
+ len += n;
+ p = buf;
+ }
+ else
+ p++;
+
+ /*
+ * If there's no beginning-of-line pointer, then we must
+ * have seen an end-of-line. Set bol to the start of the
+ * new line...
+ */
+ if(!bol)
+ bol = p;
+
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ /* either a lone \r or lone \n indicate binary file */
+ if(lastchar == '\r' || lastchar == BREAKOUT){
+ if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ new_encoding = ENCBINARY;
+ break;
+ }
+ }
+#endif
+ }
+ }
+
+ /* stash away for later */
+ so_attr(so, "maxline", long2string(max_line));
+
+ if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
+ /*
+ * Since the type or encoding aren't set yet, fall thru a
+ * series of tests to make sure an adequate type and
+ * encoding are set...
+ */
+
+ if(max_line >= 1000L){ /* 1000 comes from rfc821 */
+ if(body->type == TYPEOTHER){
+ /*
+ * Since the types not set, then we didn't find a NULL.
+ * If there's no NULL, then this is likely text. However,
+ * since we can't be *completely* sure, we set it to
+ * the generic type.
+ */
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ if(new_encoding != ENCBINARY)
+ /*
+ * As with NULL handling, if we're told it's text,
+ * qp-encode it, else it gets base 64...
+ */
+ new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
+ }
+
+ if(eight_bit_chars == 0L){
+ if(body->type == TYPEOTHER)
+ body->type = TYPETEXT;
+
+ if(new_encoding == ENCOTHER)
+ new_encoding = ENC7BIT; /* short lines, no 8 bit */
+ }
+ else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
+ /*
+ * The 30% threshold is based on qp encoded readability
+ * on non-MIME UA's.
+ */
+ if(body->type == TYPEOTHER)
+ body->type = TYPETEXT;
+
+ if(new_encoding != ENCBINARY)
+ new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
+ }
+ else{
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ /*
+ * Apply maximal encoding regardless of previous
+ * setting. This segment's either not text, or is
+ * unlikely to be readable with > 30% of the
+ * text encoded anyway, so we might as well save space...
+ */
+ new_encoding = ENCBINARY; /* > 30% 8 bit chars */
+ }
+ }
+
+#ifdef ENCODE_FROMS
+ /* If there were From_'s at the beginning of a line or standalone dots */
+ if((froms || dots) && new_encoding != ENCBINARY)
+ new_encoding = ENC8BIT;
+#endif
+
+ /* Set the subtype */
+ if(body->subtype == NULL)
+ body->subtype = cpystr(rfc822_default_subtype(body->type));
+
+ if(body->encoding == ENCOTHER)
+ body->encoding = new_encoding;
+
+ fs_give((void **)&buf);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * Call this to set the charset of an attachment we have
+ * created. If the attachment contains any non-ascii characters
+ * then we'll set the charset to the passed in charset, otherwise
+ * we'll make it us-ascii.
+ */
+void
+set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
+{
+ unsigned char c;
+ int can_be_ascii = 1;
+ STORE_S *so = (STORE_S *)body->contents.text.data;
+ int we_cancel = 0;
+
+ if(!body || body->type != TYPETEXT)
+ return;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ so_seek(so, 0L, 0);
+
+ while(can_be_ascii && so_readc(&c, so))
+ if(!c || c & 0x80)
+ can_be_ascii--;
+
+ if(can_be_ascii)
+ set_parameter(&body->parameter, "charset", "US-ASCII");
+ else if(charset && *charset && strucmp(charset, "US-ASCII"))
+ set_parameter(&body->parameter, "charset", charset);
+ else{
+ /*
+ * Else we don't know. There are non ascii characters but we either
+ * don't have a charset to set it to or that charset is just us_ascii,
+ * which is impossible. So we label it unknown. An alternative would
+ * have been to strip the high bits instead and label it ascii.
+ */
+ set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * since encoding happens on the way out the door, this is basically
+ * just needed to handle TYPEMULTIPART
+ */
+void
+pine_encode_body (struct mail_bodystruct *body)
+{
+ PART *part;
+
+ dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
+ if (body) switch (body->type) {
+ char *freethis;
+
+ case TYPEMULTIPART: /* multi-part */
+ if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
+ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
+
+ snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
+ (long) getpid ());
+ tmp[sizeof(tmp)-1] = '\0';
+ set_parameter(&body->parameter, "BOUNDARY", tmp);
+ }
+
+ if(freethis)
+ fs_give((void **) &freethis);
+
+ part = body->nested.part; /* encode body parts */
+ do pine_encode_body (&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ break;
+
+ case TYPETEXT :
+ /*
+ * If the part is text we edited, then it is UTF-8.
+ * The user may be asking us to send it as something else
+ * or we may want to downconvert to a more-specific characterset.
+ * Mark it for conversion here so the right MIME header's written.
+ * Do conversion pine_rfc822_output_body.
+ * Attachments are left as is.
+ */
+ if(body->contents.text.data
+ && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
+ char *charset, *posting_charset, *lp;
+
+ if(!((charset = parameter_val(body->parameter, "charset"))
+ && !strucmp(charset, UNKNOWN_CHARSET))
+ && (posting_charset = posting_characterset(body, charset, MsgBody))){
+
+ set_parameter(&body->parameter, "charset", posting_charset);
+
+ /*
+ * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
+ * and doesn't use anything but ASCII characters.
+ * Why is it not ENC7BIT already? Because when we set the encoding
+ * in set_mime_type_by_grope we were groping through UTF-8 text
+ * not 2022 text. Not only that, but we didn't know at that point
+ * that it wouldn't stay UTF-8 when we sent it, which would require
+ * encoding.
+ */
+ if(!strucmp(posting_charset, "iso-2022-jp")
+ && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
+ && strlen(lp) < 4)
+ body->encoding = ENC7BIT;
+ }
+
+ if(charset)
+ fs_give((void **)&charset);
+ }
+
+ break;
+
+/* case MESSAGE: */ /* here for documentation */
+ /* Encapsulated messages are always treated as text objects at this point.
+ This means that you must replace body->contents.msg with
+ body->contents.text, which probably involves copying
+ body->contents.msg.text to body->contents.text */
+ default: /* all else has some encoding */
+ /*
+ * but we'll delay encoding it until the message is on the way
+ * into the mail slot...
+ */
+ break;
+ }
+}
+
+
+/*
+ * pine_header_line - simple wrapper around c-client call to contain
+ * repeated code, and to write fcc if required.
+ */
+int
+pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
+ int writehdr, int localcopy)
+{
+ int ret = 1;
+ int big = 10000;
+ char *value, *folded = NULL, *cs;
+ char *converted;
+
+ if(!text)
+ return 1;
+
+ converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
+
+ if(converted){
+ if(cs && !strucmp(cs, "us-ascii"))
+ value = converted;
+ else
+ value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs,
+ encode_whole_header(field, header));
+
+ if(value && value == converted){ /* no encoding was done, have to fold */
+ int fold_by, len;
+ char *actual_field;
+
+ len = ((header && header->env && header->env->remail)
+ ? strlen("ReSent-") : 0) +
+ (field ? strlen(field) : 0) + 2;
+
+ actual_field = (char *)fs_get((len+1) * sizeof(char));
+ snprintf(actual_field, len+1, "%s%s: ",
+ (header && header->env && header->env->remail) ? "ReSent-" : "",
+ field ? field : "");
+ actual_field[len] = '\0';
+
+ /*
+ * We were folding everything except message-id, but that wasn't
+ * sufficient. Since 822 only allows folding where linear-white-space
+ * is allowed we'd need a smarter folder than "fold" to do it. So,
+ * instead of inventing that smarter folder (which would have to
+ * know 822 syntax)
+ *
+ * We could just alloc space and copy the actual_field followed by
+ * the value into it, but since that's what fold does anyway we'll
+ * waste some cpu time and use fold with a big fold parameter.
+ *
+ * We upped the references folding from 75 to 256 because we were
+ * encountering longer-than-75 message ids, and to break one line
+ * in references is to break them all.
+ */
+ if(field && !strucmp("Subject", field))
+ fold_by = 75;
+ else if(field && !strucmp("References", field))
+ fold_by = 256;
+ else
+ fold_by = big;
+
+ folded = fold(value, fold_by, big, actual_field, " ", FLD_CRLF);
+
+ if(actual_field)
+ fs_give((void **)&actual_field);
+ }
+ else if(value){ /* encoding was done */
+ RFC822BUFFER rbuf;
+ size_t ll;
+
+ /*
+ * rfc1522_encode already inserted continuation lines and did
+ * the necessary folding so we don't have to do it. Let
+ * rfc822_header_line add the trailing crlf and the resent- if
+ * necessary. The 20 could actually be a 12.
+ */
+ ll = strlen(field) + strlen(value) + 20;
+ folded = (char *) fs_get(ll * sizeof(char));
+ *folded = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = folded;
+ rbuf.cur = folded;
+ rbuf.end = folded+ll-1;
+ rfc822_output_header_line(&rbuf, field,
+ (header && header->env && header->env->remail) ? LONGT : 0L, value);
+ *rbuf.cur = '\0';
+ }
+
+ if(value && folded){
+ if(writehdr && f)
+ ret = (*f)(s, folded);
+
+ if(ret && localcopy && lmc.so && !lmc.all_written)
+ ret = so_puts(lmc.so, folded);
+ }
+
+ if(folded)
+ fs_give((void **)&folded);
+
+ if(converted && converted != text)
+ fs_give((void **) &converted);
+ }
+ else
+ ret = 0;
+
+ return(ret);
+}
+
+
+/*
+ * Do appropriate encoding of text header lines.
+ * For some field types (those that consist of 822 *text) we just encode
+ * the whole thing. For structured fields we encode only within comments
+ * if possible.
+ *
+ * Args d -- Destination buffer if needed. (tmp_20k_buf)
+ * s -- Source string.
+ * charset -- Charset to encode with.
+ * encode_all -- If set, encode the whole string. If not, try to encode
+ * only within comments if possible.
+ *
+ * Returns S is returned if no encoding is done. D is returned if encoding
+ * was needed.
+ */
+char *
+encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
+{
+ char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
+ int in_comment = 0;
+
+ if(!s)
+ return((char *)s);
+
+ if(dlen < SIZEOF_20KBUF)
+ panic("bad call to encode_header_value");
+
+ if(!encode_all){
+ /*
+ * We don't have to worry about keeping track of quoted-strings because
+ * none of these fields which aren't addresses contain quoted-strings.
+ * We do keep track of escaped parens inside of comments and comment
+ * nesting.
+ */
+ p = d+7000;
+ for(q = (char *)s; *q; q++){
+ switch(*q){
+ case LPAREN:
+ if(in_comment++ == 0)
+ start_of_comment = q;
+
+ break;
+
+ case RPAREN:
+ if(--in_comment == 0){
+ /* encode the comment, excluding the outer parens */
+ if(p-d < dlen-1)
+ *p++ = LPAREN;
+
+ *q = '\0';
+ r = rfc1522_encode(d+14000, dlen-14000,
+ (unsigned char *)start_of_comment+1,
+ charset);
+ if(r != start_of_comment+1)
+ value = d+7000; /* some encoding was done */
+
+ start_of_comment = NULL;
+ if(r)
+ sstrncpy(&p, r, dlen-1-(p-d));
+
+ *q = RPAREN;
+ if(p-d < dlen-1)
+ *p++ = *q;
+ }
+ else if(in_comment < 0){
+ in_comment = 0;
+ if(p-d < dlen-1)
+ *p++ = *q;
+ }
+
+ break;
+
+ case BSLASH:
+ if(!in_comment && *(q+1)){
+ if(p-d < dlen-2){
+ *p++ = *q++;
+ *p++ = *q;
+ }
+ }
+
+ break;
+
+ default:
+ if(!in_comment && p-d < dlen-1)
+ *p++ = *q;
+
+ break;
+ }
+ }
+
+ if(value){
+ /* Unterminated comment (wasn't really a comment) */
+ if(start_of_comment)
+ sstrncpy(&p, start_of_comment, dlen-1-(p-d));
+
+ *p = '\0';
+ }
+ }
+
+ /*
+ * We have to check if there is anything that needs to be encoded that
+ * wasn't in a comment. If there is, we'd better just start over and
+ * encode the whole thing. So, if no encoding has been done within
+ * comments, or if encoding is needed both within and outside of
+ * comments, then we encode the whole thing. Otherwise, go with
+ * the version that has only comments encoded.
+ */
+ if(!value || rfc1522_encode(d, dlen,
+ (unsigned char *)value, charset) != value)
+ return(rfc1522_encode(d, dlen, s, charset));
+ else{
+ strncpy(d, value, dlen-1);
+ d[dlen-1] = '\0';
+ return(d);
+ }
+}
+
+
+/*
+ * pine_address_line - write a header field containing addresses,
+ * one by one (so there's no buffer limit), and
+ * wrapping where necessary.
+ * Note: we use c-client functions to properly build the text string,
+ * but have to screw around with pointers to fool c-client functions
+ * into not blatting all the text into a single buffer. Yeah, I know.
+ */
+int
+pine_address_line(char *field, METAENV *header, struct mail_address *alist,
+ soutr_t f, void *s, int writehdr, int localcopy)
+{
+ char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
+ size_t alloced = 0, sz;
+ char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
+ char *converted, *cs;
+ ADDRESS *atmp;
+ int i, count;
+ int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
+ RFC822BUFFER rbuf;
+ static char comma[] = ", ";
+ static char end_group[] = ";";
+#define no_comma (&comma[1])
+
+ if(!alist) /* nothing in field! */
+ return(1);
+
+ if(!alist->host && alist->mailbox){ /* c-client group convention */
+ in_group++;
+ was_start_of_group++;
+ /* encode mailbox of group */
+ mtmp = alist->mailbox;
+ if(mtmp){
+ snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+ }
+ }
+ else
+ mtmp = NULL;
+
+ ptmp = alist->personal; /* remember personal name */
+ /* make sure personal name is encoded */
+ if(ptmp){
+ snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+ }
+
+ atmp = alist->next;
+ alist->next = NULL; /* digest only first address! */
+
+ /* use automatic buffer unless it isn't big enough */
+ if((alloced = est_size(alist)) > sizeof(tmp)){
+ tmpptr = (char *)fs_get(alloced);
+ sz = alloced;
+ }
+ else{
+ tmpptr = tmp;
+ sz = sizeof(tmp);
+ }
+
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmpptr;
+ rbuf.cur = tmpptr;
+ rbuf.end = tmpptr+sz-1;
+ rfc822_output_address_line(&rbuf, field,
+ (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
+ *rbuf.cur = '\0';
+
+ alist->next = atmp; /* restore pointer to next addr */
+
+ if(alist->personal && alist->personal != ptmp)
+ fs_give((void **) &alist->personal);
+
+ alist->personal = ptmp; /* in case it changed, restore name */
+
+ if(mtmp){
+ if(alist->mailbox && alist->mailbox != mtmp)
+ fs_give((void **) &alist->mailbox);
+
+ alist->mailbox = mtmp;
+ }
+
+ if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
+ count -= 2;
+ tmpptr[count] = '\0';
+ }
+
+ /*
+ * If there is no sending_stream and we are writing the Lcc header,
+ * then we are piping it to sendmail -t which expects it to be a bcc,
+ * not lcc.
+ *
+ * When we write it to the fcc or postponed (the lmc.so),
+ * we want it to be lcc, not bcc, so we put it back.
+ */
+ if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
+ fix_lcc = 1;
+
+ if(writehdr && f && *tmpptr){
+ if(fix_lcc)
+ tmpptr[0] = 'b';
+
+ failed = !(*f)(s, tmpptr);
+ if(fix_lcc)
+ tmpptr[0] = 'L';
+
+ if(failed)
+ goto bail_out;
+ }
+
+ if(localcopy && lmc.so &&
+ !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
+ goto bail_out;
+
+ for(alist = atmp; alist; alist = alist->next){
+ delim = comma;
+ /* account for c-client's representation of group names */
+ if(in_group){
+ if(!alist->host){ /* end of group */
+ in_group = 0;
+ was_start_of_group = 0;
+ /*
+ * Rfc822_write_address no longer writes out the end of group
+ * unless the whole group address is passed to it, so we do
+ * it ourselves.
+ */
+ delim = end_group;
+ }
+ else if(!localcopy || !lmc.so || lmc.all_written)
+ continue;
+ }
+ /* start of new group, print phrase below */
+ else if(!alist->host && alist->mailbox){
+ in_group++;
+ was_start_of_group++;
+ }
+
+ /* no comma before first address in group syntax */
+ if(was_start_of_group && alist->host){
+ delim = no_comma;
+ was_start_of_group = 0;
+ }
+
+ /* write delimiter */
+ if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
+ || (localcopy && lmc.so && !lmc.all_written
+ && !so_puts(lmc.so, delim)))
+ goto bail_out;
+
+ ptmp = alist->personal; /* remember personal name */
+ snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+
+ atmp = alist->next;
+ alist->next = NULL; /* tie off linked list */
+ if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
+ alloced = i;
+ sz = alloced;
+ fs_resize((void **)&tmpptr, alloced);
+ }
+
+ *tmpptr = '\0';
+ /* make sure we don't write out group end with rfc822_write_address */
+ if(alist->host || alist->mailbox){
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmpptr;
+ rbuf.cur = tmpptr;
+ rbuf.end = tmpptr+sz-1;
+ rfc822_output_address_list(&rbuf, alist, 0L, NULL);
+ *rbuf.cur = '\0';
+ }
+
+ alist->next = atmp; /* restore next pointer */
+
+ if(alist->personal && alist->personal != ptmp)
+ fs_give((void **) &alist->personal);
+
+ alist->personal = ptmp; /* in case it changed, restore name */
+
+ /*
+ * BUG
+ * With group syntax addresses we no longer have two identical
+ * streams of output. Instead, for the fcc/postpone copy we include
+ * all of the addresses inside the :; of the group, and for the
+ * mail we're sending we don't include them. That means we aren't
+ * correctly keeping track of the column to wrap in, below. That is,
+ * we are keeping track of the fcc copy but we aren't keeping track
+ * of the regular copy. It could result in too long or too short
+ * lines. Should almost never come up since group addresses are almost
+ * never followed by other addresses in the same header, and even
+ * when they are, you have to go out of your way to get the headers
+ * messed up.
+ */
+ if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
+ count = i + 4;
+ if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
+ || (localcopy && lmc.so && !lmc.all_written &&
+ !so_puts(lmc.so, "\015\012 ")))
+ goto bail_out;
+ }
+ else
+ count += i + 2;
+
+ if(((!in_group || was_start_of_group)
+ && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
+ || (localcopy && lmc.so && !lmc.all_written
+ && *tmpptr && !so_puts(lmc.so, tmpptr)))
+ goto bail_out;
+ }
+
+bail_out:
+ if(tmpptr && tmpptr != tmp)
+ fs_give((void **)&tmpptr);
+
+ if(failed)
+ return(0);
+
+ return((writehdr && f ? (*f)(s, "\015\012") : 1)
+ && ((localcopy && lmc.so
+ && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
+}
+
+
+/*
+ * mutated pine version of c-client's rfc822_header() function.
+ * changed to call pine-wrapped header and address functions
+ * so we don't have to limit the header size to a fixed buffer.
+ * This function also calls pine's body_header write function
+ * because encoding is delayed until output_body() is called.
+ */
+long
+pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ PINEFIELD *pf;
+ int j;
+
+ if(header->env->remail){ /* if remailing */
+ long i = strlen (header->env->remail);
+ if(i > 4 && header->env->remail[i-4] == '\015')
+ header->env->remail[i-2] = '\0'; /* flush extra blank line */
+
+ if((f && !(*f)(s, header->env->remail))
+ || (lmc.so && !lmc.all_written
+ && !so_puts(lmc.so, header->env->remail)))
+ return(0L); /* start with remail header */
+ }
+
+ j = 0;
+ for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
+ switch(pf->type){
+ /*
+ * Warning: This is confusing. The 2nd to last argument used to
+ * be just pf->writehdr. We want Bcc lines to be written out
+ * if we are handing off to a sendmail temp file but not if we
+ * are talking smtp, so bcc's writehdr is set to 0 and
+ * pine_address_line was sending if writehdr OR !sending_stream.
+ * That works as long as we want to write everything when
+ * !sending_stream (an mta handoff to sendmail). But then we
+ * added the undisclosed recipients line which should only get
+ * written if writehdr is set, and not when we pass to a
+ * sendmail temp file. So pine_address_line has been changed
+ * so it bases its decision solely on the writehdr passed to it,
+ * and the logic that worries about Bcc and sending_stream
+ * was moved up to the caller (here) to decide when to set it.
+ *
+ * So we have:
+ * undisclosed recipients:; This will just be written
+ * if writehdr was set and not
+ * otherwise, nothing magical.
+ *** We may want to change this, because sendmail -t doesn't handle
+ *** the empty group syntax well unless it has been configured to
+ *** do so. It isn't configured by default, or in any of the
+ *** sendmail v8 configs. So we may want to not write this line
+ *** if we're doing an mta_handoff (!sending_stream).
+ *
+ * !sending_stream (which means a handoff to a sendmail -t)
+ * bcc or lcc both set the arg so they'll get written
+ * (There is also Lcc hocus pocus in pine_address_line
+ * which converts the Lcc: to Bcc: for sendmail
+ * processing.)
+ * sending_stream (which means an smtp handoff)
+ * bcc and lcc will never have writehdr set, so
+ * will never be written (They both do have rcptto set,
+ * so they both do cause RCPT TO commands.)
+ *
+ * The localcopy is independent of sending_stream and is just
+ * written if it is set for all of these.
+ */
+ case Address:
+ if(!pine_address_line(pf->name,
+ header,
+ pf->addr ? *pf->addr : NULL,
+ f,
+ s,
+ (!strucmp("bcc",pf->name ? pf->name : "")
+ || !strucmp("Lcc",pf->name ? pf->name : ""))
+ ? !sending_stream
+ : pf->writehdr,
+ pf->localcopy))
+ return(0L);
+
+ break;
+
+ case Fcc:
+ case FreeText:
+ case Subject:
+ if(!pine_header_line(pf->name, header,
+ pf->text ? *pf->text : NULL,
+ f, s, pf->writehdr, pf->localcopy))
+ return(0L);
+
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
+ pf->name);
+ break;
+ }
+ }
+
+
+#if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
+ /*
+ * Add comforting "X-" header line indicating what sort of
+ * authenticity the receiver can expect...
+ */
+ if(F_OFF(F_DISABLE_SENDER, ps_global)){
+ NETMBX netmbox;
+ char sstring[MAILTMPLEN], *label; /* place to write */
+ MAILSTREAM *m;
+ int i, anonymous = 1;
+
+ for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED)
+ && mail_valid_net_parse(m->mailbox, &netmbox)
+ && !netmbox.anoflag)
+ anonymous = 0;
+ }
+
+ if(!anonymous){
+ char last_char = netmbox.host[strlen(netmbox.host) - 1],
+ *user = (*netmbox.user)
+ ? netmbox.user
+ : cached_user_name(netmbox.mailbox);
+ snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
+ isdigit((unsigned char)last_char) ? "[" : "",
+ netmbox.host,
+ isdigit((unsigned char) last_char) ? "]" : "");
+ sstring[sizeof(sstring)-1] = '\0';
+ label = "X-X-Sender"; /* Jeez. */
+ if(F_ON(F_USE_SENDER_NOT_X,ps_global))
+ label += 4;
+ }
+ else{
+ strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
+ sstring[sizeof(sstring)-1] = '\0';
+ label = "X-Warning";
+ }
+
+ if(!pine_header_line(label, header, sstring, f, s, 1, 1))
+ return(0L);
+ }
+#endif
+
+ if(body && !header->env->remail){ /* not if remail or no body */
+ if((f && !(*f)(s, MIME_VER))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
+ || !pine_write_body_header(body, f, s))
+ return(0L);
+ }
+ else{ /* write terminating newline */
+ if((f && !(*f)(s, "\015\012"))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
+ return(0L);
+ }
+
+ return(1L);
+}
+
+
+/*
+ * pine_rfc822_output - pine's version of c-client call. Necessary here
+ * since we're not using its structures as intended!
+ */
+long
+pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ int we_cancel = 0;
+ long retval;
+
+ dprint((4, "-- pine_rfc822_output\n"));
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+ pine_encode_body(body); /* encode body as necessary */
+ /* build and output RFC822 header, output body */
+ retval = pine_rfc822_header(header, body, f, s)
+ && (body ? pine_rfc822_output_body(body, f, s) : 1L);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return(retval);
+}
+
+
+/*
+ * post_rfc822_output - cloak for pine's 822 output routine. Since
+ * we can't pass opaque envelope thru c-client posting
+ * logic, we need to wrap the real output inside
+ * something that c-client knows how to call.
+ */
+long
+post_rfc822_output(char *tmp,
+ ENVELOPE *env,
+ struct mail_bodystruct *body,
+ soutr_t f,
+ void *s,
+ long int ok8bit)
+{
+ return(pine_rfc822_output(send_header, body, f, s));
+}
+
+
+/*
+ * posting_characterset- determine what transliteration is reasonable
+ * for posting the given non-ascii messsage data.
+ *
+ * preferred_charset is the charset the original data was labeled in.
+ * If we can keep that we do.
+ *
+ * Returns: always returns the preferred character set.
+ */
+char *
+posting_characterset(void *data, char *preferred_charset, MsgPart mp)
+{
+ unsigned long *charsetmap = NULL;
+ unsigned long validbitmap;
+ static char *ascii = "US-ASCII";
+ static char *utf8 = "UTF-8";
+ int notcjk = 0;
+
+ if(!ps_global->post_utf8){
+ validbitmap = 0;
+
+ if(mp == HdrText){
+ char *text = NULL;
+ UCS *ucs = NULL, *ucsp;
+
+ text = (char *) data;
+
+ /* convert text in header to UCS characters */
+ if(text)
+ ucsp = ucs = utf8_to_ucs4_cpystr(text);
+
+ if(!(ucs && *ucs))
+ return(ascii);
+
+ /*
+ * After the while loop is done the validbitmap has
+ * a 1 bit for all the character sets that can
+ * represent all of the characters of this header.
+ */
+ charsetmap = init_charsetchecker(preferred_charset);
+
+ if(!charsetmap)
+ return(utf8);
+
+ validbitmap = ~0;
+ while((validbitmap & ~0x1) && (*ucsp)){
+ if(*ucsp > 0xffff){
+ fs_give((void **) &ucs);
+ return(utf8);
+ }
+
+ validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
+ }
+
+ fs_give((void **) &ucs);
+
+ notcjk = validbitmap & 0x1;
+ validbitmap &= ~0x1;
+
+ if(!validbitmap)
+ return(utf8);
+ }
+ else{
+ struct mail_bodystruct *body = NULL;
+ STORE_S *the_text = NULL;
+ int outchars;
+ unsigned char c;
+ UCS ucs;
+ CBUF_S cbuf;
+
+ cbuf.cbuf[0] = '\0';
+ cbuf.cbufp = cbuf.cbuf;
+ cbuf.cbufend = cbuf.cbuf;
+
+ body = (struct mail_bodystruct *) data;
+
+ if(body && body->type == TYPEMULTIPART)
+ body = &body->nested.part->body;
+
+ if(body && body->type == TYPETEXT)
+ the_text = (STORE_S *) body->contents.text.data;
+
+ if(!the_text)
+ return(ascii);
+
+ so_seek(the_text, 0L, 0); /* rewind */
+
+ charsetmap = init_charsetchecker(preferred_charset);
+
+ if(!charsetmap)
+ return(utf8);
+
+ validbitmap = ~0;
+
+ /*
+ * Read a stream of UTF-8 characters from the_text
+ * and convert them to UCS-4 characters for the translatable
+ * test.
+ */
+ while((validbitmap & ~0x1) && so_readc(&c, the_text)){
+ if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
+ /* got a ucs character */
+ if(ucs > 0xffff)
+ return(utf8);
+
+ validbitmap &= charsetmap[(unsigned long) ucs];
+ }
+ }
+
+ notcjk = validbitmap & 0x1;
+ validbitmap &= ~0x1;
+
+ if(!validbitmap)
+ return(utf8);
+ }
+
+ /* user chooses something other than UTF-8 */
+ if(strucmp(ps_global->posting_charmap, utf8)){
+ /*
+ * If we're to post in other than UTF-8, and it can be
+ * transliterated without losing fidelity, do it.
+ * Else, use UTF-8.
+ */
+
+ /* if ascii works, always use that */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* does the user's posting character set work? */
+ if(representable_in_charset(validbitmap, ps_global->posting_charmap))
+ return(ps_global->posting_charmap);
+
+ /* this is the charset the message we are replying to was in */
+ if(preferred_charset
+ && strucmp(preferred_charset, ascii)
+ && representable_in_charset(validbitmap, preferred_charset))
+ return(preferred_charset);
+
+ /* else, use UTF-8 */
+
+ }
+ /* user chooses nothing, going with the default */
+ else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
+ char *most_preferred;
+
+ /*
+ * In this case the user didn't specify a posting character set
+ * and we will choose the most-specific one from our list.
+ */
+
+ /* ascii is best */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* Can we keep the original from the message we're replying to? */
+ if(preferred_charset
+ && strucmp(preferred_charset, ascii)
+ && representable_in_charset(validbitmap, preferred_charset))
+ return(preferred_charset);
+
+ /* choose the best of the rest */
+ most_preferred = most_preferred_charset(validbitmap);
+ if(!most_preferred)
+ return(utf8);
+
+ /*
+ * If the text we're labeling contains something like
+ * smart quotes but no CJK characters, then instead of
+ * labeling it as ISO-2022-JP we want to use UTF-8.
+ */
+ if(notcjk){
+ const CHARSET *cs;
+
+ cs = utf8_charset(most_preferred);
+ if(!cs
+ || cs->script == SC_CHINESE_SIMPLIFIED
+ || cs->script == SC_CHINESE_TRADITIONAL
+ || cs->script == SC_JAPANESE
+ || cs->script == SC_KOREAN)
+ return(utf8);
+ }
+
+ return(most_preferred);
+ }
+ /* user explicitly chooses UTF-8 */
+ else{
+ /* if ascii works, always use that */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* else, use UTF-8 */
+
+ }
+ }
+
+ return(utf8);
+}
+
+
+static char **charsetlist = NULL;
+static int items_in_charsetlist = 0;
+static unsigned long *charsetmap = NULL;
+
+static char *downgrades[] = {
+ "US-ASCII",
+ "ISO-8859-15",
+ "ISO-8859-1",
+ "ISO-8859-2",
+ "VISCII",
+ "KOI8-R",
+ "KOI8-U",
+ "ISO-8859-7",
+ "ISO-8859-6",
+ "ISO-8859-8",
+ "TIS-620",
+ "ISO-2022-JP",
+ "GB2312",
+ "BIG5",
+ "EUC-KR"
+};
+
+
+unsigned long *
+init_charsetchecker(char *preferred_charset)
+{
+ int i, count = 0, reset = 0;
+ char *ascii = "US-ASCII";
+ char *utf8 = "UTF-8";
+
+ /*
+ * When user doesn't set a posting character set posting_charmap ends up
+ * set to UTF-8. That also happens if user sets UTF-8 explicitly.
+ * That's where the strange set of if-else's come from.
+ */
+
+ /* user chooses something other than UTF-8 */
+ if(strucmp(ps_global->posting_charmap, utf8)){
+ count++; /* US-ASCII */
+ if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
+ reset++;
+
+ /* if posting_charmap is valid, include it in list */
+ if(ps_global->posting_charmap && ps_global->posting_charmap[0]
+ && strucmp(ps_global->posting_charmap, ascii)
+ && strucmp(ps_global->posting_charmap, utf8)
+ && utf8_charset(ps_global->posting_charmap)){
+ count++;
+ if(!reset
+ && (items_in_charsetlist < count
+ || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
+ reset++;
+ }
+
+ if(preferred_charset && preferred_charset[0]
+ && strucmp(preferred_charset, ascii)
+ && strucmp(preferred_charset, utf8)
+ && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
+ count++;
+ if(!reset
+ && (items_in_charsetlist < count
+ || strucmp(charsetlist[count-1], preferred_charset)))
+ reset++;
+ }
+
+ if(items_in_charsetlist != count)
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+
+ i = 0;
+ charsetlist[i++] = cpystr(ascii);
+
+ if(ps_global->posting_charmap && ps_global->posting_charmap[0]
+ && strucmp(ps_global->posting_charmap, ascii)
+ && strucmp(ps_global->posting_charmap, utf8)
+ && utf8_charset(ps_global->posting_charmap))
+ charsetlist[i++] = cpystr(ps_global->posting_charmap);
+
+ if(preferred_charset && preferred_charset[0]
+ && strucmp(preferred_charset, ascii)
+ && strucmp(preferred_charset, utf8)
+ && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
+ charsetlist[i++] = cpystr(preferred_charset);
+
+ charsetlist[i] = NULL;
+ }
+ }
+ /* user chooses nothing, going with the default */
+ else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
+ int add_preferred = 0;
+
+ /* does preferred_charset have to be added to the list? */
+ if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
+ add_preferred = 1;
+ for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ if(!strucmp(downgrades[i], preferred_charset))
+ add_preferred = 0;
+ }
+
+ if(add_preferred){
+ /* existing list is right size already */
+ if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
+ /* just check to see if last list item is the preferred_charset */
+ if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
+ /* no, fix it */
+ reset++;
+ fs_give((void **) &charsetlist[items_in_charsetlist-1]);
+ charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
+ }
+ }
+ else{
+ reset++;
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+ for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ charsetlist[i] = cpystr(downgrades[i]);
+
+ charsetlist[i++] = cpystr(preferred_charset);
+ charsetlist[i] = NULL;
+ }
+ }
+ else{
+ /* if list is same size as downgrades, consider it good */
+ if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ count = sizeof(downgrades)/sizeof(downgrades[0]);
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+ for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ charsetlist[i] = cpystr(downgrades[i]);
+
+ charsetlist[i] = NULL;
+ }
+ }
+ }
+ /* user explicitly chooses UTF-8 */
+ else{
+ /* include possibility of ascii even if they explicitly ask for UTF-8 */
+ count++; /* US-ASCII */
+ if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
+ reset++;
+
+ if(items_in_charsetlist != count)
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ /* the list is just ascii and nothing else */
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+
+ i = 0;
+ charsetlist[i++] = cpystr(ascii);
+ charsetlist[i] = NULL;
+ }
+ }
+
+
+ if(reset){
+ if(charsetmap)
+ fs_give((void **) &charsetmap);
+
+ if(charsetlist)
+ charsetmap = utf8_csvalidmap(charsetlist);
+ }
+
+ return(charsetmap);
+}
+
+
+/* total reset */
+void
+free_charsetchecker(void)
+{
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ items_in_charsetlist = 0;
+
+ if(charsetmap)
+ fs_give((void **) &charsetmap);
+}
+
+
+int
+representable_in_charset(unsigned long validbitmap, char *charset)
+{
+ int i, done = 0, ret = 0;
+ unsigned long j;
+
+ if(!(charset && charset[0]))
+ return ret;
+
+ if(!strucmp(charset, "UTF-8"))
+ return 1;
+
+ for(i = 0; !done && i < items_in_charsetlist; i++){
+ if(!strucmp(charset, charsetlist[i])){
+ j = 1;
+ j <<= (i+1);
+ done++;
+ if(validbitmap & j)
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+
+char *
+most_preferred_charset(unsigned long validbitmap)
+{
+ unsigned long bm;
+ unsigned long rm;
+ int index;
+
+ if(!(validbitmap && items_in_charsetlist > 0))
+ return("UTF-8");
+
+ /* careful, find_rightmost_bit modifies the bitmap */
+ bm = validbitmap;
+ rm = find_rightmost_bit(&bm);
+ index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
+
+ return(charsetlist[index]);
+}
+
+
+/*
+ * Set parameter to new value.
+ */
+void
+set_parameter(PARAMETER **param, char *paramname, char *new_value)
+{
+ PARAMETER *pm;
+
+ if(!param || !(paramname && *paramname))
+ return;
+
+ if(*param == NULL){
+ pm = (*param) = mail_newbody_parameter();
+ pm->attribute = cpystr(paramname);
+ }
+ else{
+ int nomatch;
+
+ for(pm = *param;
+ (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
+ pm = pm->next)
+ ;/* searching for paramname parameter */
+
+ if(nomatch){ /* add charset parameter */
+ pm->next = mail_newbody_parameter();
+ pm = pm->next;
+ pm->attribute = cpystr(paramname);
+ }
+ /* else pm is existing paramname parameter */
+ }
+
+ if(pm){
+ if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
+ if(pm->value)
+ fs_give((void **) &pm->value);
+
+ if(new_value)
+ pm->value = cpystr(new_value);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Remove the leading digits from SMTP error messages
+ -----*/
+char *
+tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
+{
+ while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
+ (*error == '.' && isdigit((unsigned char)*(error+1))))
+ error++;
+
+ snprintf(outbuf, outbuflen, printstring, error);
+ outbuf[outbuflen-1] = '\0';
+ return(outbuf);
+}
+
+
+/*
+ * Local globals pine's body output routine needs
+ */
+static soutr_t l_f;
+static TCPSTREAM *l_stream;
+static unsigned c_in_buf = 0;
+
+/*
+ * def to make our pipe write's more friendly
+ */
+#ifdef PIPE_MAX
+#if PIPE_MAX > 20000
+#undef PIPE_MAX
+#endif
+#endif
+
+#ifndef PIPE_MAX
+#define PIPE_MAX 1024
+#endif
+
+
+/*
+ * l_flust_net - empties gf_io terminal function's buffer
+ */
+int
+l_flush_net(int force)
+{
+ if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
+ char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
+
+ tmp_20k_buf[c_in_buf] = '\0';
+ if(!force){
+ /*
+ * The start of each write is expected to be the start of a
+ * "record" (i.e., a CRLF terminated line). Make sure that is true
+ * else we might screw up SMTP dot quoting...
+ */
+ for(p = tmp_20k_buf, lp = NULL;
+ (p = strstr(p, "\015\012")) != NULL;
+ lp = (p += 2))
+ ;
+
+
+ if(!lp && c_in_buf > 2) /* no CRLF! */
+ for(p = &tmp_20k_buf[c_in_buf] - 2;
+ p > &tmp_20k_buf[0] && *p == '.';
+ p--) /* find last non-dot */
+ ;
+
+ if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
+ /* snippet remains */
+ c = *lp;
+ *lp = '\0';
+ }
+ }
+
+ if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
+ || (lmc.so && !lmc.all_written
+ && !(lmc.text_only && lmc.text_written)
+ && !so_puts(lmc.so, tmp_20k_buf)))
+ return(0);
+
+ c_in_buf = 0;
+ if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
+ while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
+ c_in_buf++, lp++;
+ }
+
+ return(1);
+}
+
+
+/*
+ * l_putc - gf_io terminal function that calls smtp's soutr_t function.
+ *
+ */
+int
+l_putc(int c)
+{
+ if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
+ tmp_20k_buf[c_in_buf++] = (char) c;
+
+ return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
+}
+
+
+
+/*
+ * pine_rfc822_output_body - pine's version of c-client call. Again,
+ * necessary since c-client doesn't know about how
+ * we're treating attachments
+ */
+long
+pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ PART *part;
+ PARAMETER *param;
+ char *t, *cookie = NIL, *encode_error;
+ char tmp[MAILTMPLEN];
+ int add_trailing_crlf;
+ LOC_2022_JP ljp;
+ gf_io_t gc;
+
+ dprint((4, "-- pine_rfc822_output_body: %d\n",
+ body ? body->type : 0));
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ part = body->nested.part; /* first body part */
+ /* find cookie */
+ for (param = body->parameter; param && !cookie; param = param->next)
+ if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
+ if (!cookie) cookie = "-"; /* yucky default */
+
+ /*
+ * Output a bit of text before the first multipart delimiter
+ * to warn unsuspecting users of non-mime-aware ua's that
+ * they should expect weirdness...
+ */
+ 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"))
+ return(0);
+
+ do { /* for each part */
+ /* build cookie */
+ snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
+ tmp[sizeof(tmp)-1] = '\0';
+ /* append cookie,mini-hdr,contents */
+ if((f && !(*f)(s, tmp))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
+ || !pine_write_body_header(&part->body,f,s)
+ || !pine_rfc822_output_body (&part->body,f,s))
+ return(0);
+ } while ((part = part->next) != NULL); /* until done */
+ /* output trailing cookie */
+ snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
+ tmp[sizeof(tmp)-1] = '\0';
+ if(lmc.so && !lmc.all_written){
+ so_puts(lmc.so, t);
+ so_puts(lmc.so, "\015\012");
+ }
+
+ return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
+ }
+
+ l_f = f; /* set up for writing chars... */
+ l_stream = s; /* out other end of pipe... */
+ gf_filter_init();
+ dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
+ body->size.bytes));
+
+ if(body->contents.text.data)
+ gf_set_so_readc(&gc, (STORE_S *) body->contents.text.data);
+ else
+ return(1);
+
+ /*
+ * Don't add trailing line if it is ExternalText, which already guarantees
+ * a trailing newline.
+ */
+ add_trailing_crlf = !(((STORE_S *) body->contents.text.data)->src == ExternalText);
+
+ so_seek((STORE_S *) body->contents.text.data, 0L, 0);
+
+ if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
+ char *charset;
+
+ if(body->type == TYPETEXT
+ && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)
+ && (charset = parameter_val(body->parameter, "charset"))){
+ if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
+ if(!strucmp(charset, "iso-2022-jp")){
+ ljp.report_err = 0;
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
+ }
+ else{
+ void *table = utf8_rmap(charset);
+
+ if(table){
+ gf_link_filter(gf_convert_utf8_charset,
+ gf_convert_utf8_charset_opt(table,0));
+ }
+ else{
+ /* else, just send it? */
+ set_parameter(&body->parameter, "charset", "UTF-8");
+ }
+ }
+ }
+
+ fs_give((void **)&charset);
+ }
+
+ /*
+ * Convert text pieces to canonical form
+ * BEFORE applying any encoding (rfc1341: appendix G)...
+ * NOTE: almost all filters expect CRLF newlines
+ */
+ if(body->type == TYPETEXT
+ && body->encoding != ENCBASE64
+ && !so_attr((STORE_S *) body->contents.text.data, "rawbody", NULL)){
+ gf_link_filter(gf_local_nvtnl, NULL);
+ }
+
+ switch (body->encoding) { /* all else needs filtering */
+ case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
+ gf_link_filter(gf_8bit_qp, NULL);
+ break;
+
+ case ENCBINARY: /* encode binary into BASE64 */
+ gf_link_filter(gf_binary_b64, NULL);
+ break;
+
+ default: /* otherwise text */
+ break;
+ }
+ }
+
+ if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Encoding Error \"%s\""), encode_error);
+ display_message('x');
+ }
+
+ gf_clear_so_readc((STORE_S *) body->contents.text.data);
+
+ if(encode_error || !l_flush_net(TRUE))
+ return(0);
+
+ send_bytes_sent += gf_bytes_piped();
+ so_release((STORE_S *)body->contents.text.data);
+
+ if(lmc.so && !lmc.all_written && lmc.text_only){
+ if(lmc.text_written){ /* we have some splainin' to do */
+ char tmp[MAILTMPLEN];
+ char *name = NULL;
+
+ if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
+ && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
+ return(0);
+
+ /*
+ * BUG: If this name is not ascii it's going to cause trouble.
+ */
+ name = parameter_val(body->parameter, "name");
+ snprintf(tmp, sizeof(tmp),
+ " A %s/%s%s%s%s segment of about %s bytes.\015\012",
+ body_type_names(body->type),
+ body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "",
+ comatose(body->size.bytes));
+ tmp[sizeof(tmp)-1] = '\0';
+ if(name)
+ fs_give((void **)&name);
+
+ if(!so_puts(lmc.so, tmp))
+ return(0);
+ }
+ else /* suppress everything after first text part */
+ lmc.text_written = (body->type == TYPETEXT
+ && (!body->subtype
+ || !strucmp(body->subtype, "plain")));
+ }
+
+ if(add_trailing_crlf)
+ return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
+ && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
+ else
+ return(1);
+}
+
+
+/*
+ * pine_write_body_header - another c-client clone. This time
+ * so the final encoding labels get set
+ * correctly since it hasn't happened yet,
+ * and to be paranoid about line lengths.
+ *
+ * Returns: TRUE/nonzero on success, zero on error
+ */
+int
+pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ char tmp[MAILTMPLEN];
+ RFC822BUFFER rbuf;
+ int i;
+ unsigned char c;
+ STRINGLIST *stl;
+ STORE_S *so;
+ extern const char *tspecials;
+
+ if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
+ if(!(so_puts(so, "Content-Type: ")
+ && so_puts(so, body_types[body->type])
+ && so_puts(so, "/")
+ && so_puts(so, body->subtype
+ ? body->subtype
+ : rfc822_default_subtype (body->type))))
+ return(pwbh_finish(0, so));
+
+ if(body->parameter){
+ if(!pine_write_params(body->parameter, so))
+ return(pwbh_finish(0, so));
+ }
+ else if(!so_puts(so, "; CHARSET=US-ASCII"))
+ return(pwbh_finish(0, so));
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+
+ if ((body->encoding /* note: encoding 7BIT never output! */
+ && !(so_puts(so, "Content-Transfer-Encoding: ")
+ && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
+ ? ENCBASE64
+ : (body->encoding == ENC8BIT)
+ ? ENCQUOTEDPRINTABLE
+ : (body->encoding <= ENCMAX)
+ ? body->encoding
+ : ENCOTHER])
+ && so_puts(so, "\015\012")))
+ /*
+ * If requested, strip Content-ID headers that don't look like they
+ * are needed. Microsoft's Outlook XP has a bug that causes it to
+ * not show that there is an attachment when there is a Content-ID
+ * header present on that attachment.
+ *
+ * If user has quell-content-id turned on, don't output content-id
+ * unless it is of type message/external-body.
+ * Since this code doesn't look inside messages being forwarded
+ * type message content-ids will remain as is and type multipart
+ * alternative will remain as is. We don't create those on our
+ * own. If we did, we'd have to worry about getting this right.
+ */
+ || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
+ || (body->type == TYPEMESSAGE
+ && body->subtype
+ && !strucmp(body->subtype, "external-body")))
+ && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
+ && so_puts(so, "\015\012")))
+ || (body->description
+ && strlen(body->description) < 5000 /* arbitrary! */
+ && !pine_write_header_line("Content-Description: ", body->description, so))
+ || (body->md5
+ && !(so_puts(so, "Content-MD5: ")
+ && so_puts(so, body->md5)
+ && so_puts(so, "\015\012"))))
+ return(pwbh_finish(0, so));
+
+ if ((stl = body->language) != NULL) {
+ if(!so_puts(so, "Content-Language: "))
+ return(pwbh_finish(0, so));
+
+ do {
+ if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
+ return(pwbh_finish(0, so));
+
+ tmp[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmp;
+ rbuf.cur = tmp;
+ rbuf.end = tmp+sizeof(tmp)-1;
+ rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
+ *rbuf.cur = '\0';
+
+ if(!so_puts(so, tmp)
+ || ((stl = stl->next) && !so_puts(so, ", ")))
+ return(pwbh_finish(0, so));
+ }
+ while (stl);
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+ }
+
+ if (body->disposition.type) {
+ if(!(so_puts(so, "Content-Disposition: ")
+ && so_puts(so, body->disposition.type)))
+ return(pwbh_finish(0, so));
+
+ if(!pine_write_params(body->disposition.parameter, so))
+ return(pwbh_finish(0, so));
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+ }
+
+ /* copy out of so, a line at a time (or less than a K)
+ * and send it down the pike
+ */
+ so_seek(so, 0L, 0);
+ i = 0;
+ while(so_readc(&c, so))
+ if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
+ tmp[i] = '\0';
+ if((f && !(*f)(s, tmp))
+ || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
+ return(pwbh_finish(0, so));
+
+ i = 0;
+ }
+
+ /* Finally, write blank line */
+ if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
+ return(pwbh_finish(0, so));
+
+ return(pwbh_finish(i == 0, so)); /* better of ended on LF */
+ }
+
+ return(0);
+}
+
+
+/*
+ * pine_write_header - convert, encode (if needed) and
+ * write "header-name: field-body"
+ */
+int
+pine_write_header_line(char *hdr, char *val, STORE_S *so)
+{
+ char *cv, *cs, *vp;
+ int rv;
+
+ cs = posting_characterset(val, NULL, HdrText);
+ cv = utf8_to_charset(val, cs, 0);
+ vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) cv, cs);
+
+ rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
+
+ if(cv && cv != val)
+ fs_give((void **) &cv);
+
+
+ return(rv);
+}
+
+
+/*
+ * pine_write_param - convert, encode and write MIME header-field parameters
+ */
+int
+pine_write_params(PARAMETER *param, STORE_S *so)
+{
+ for(; param; param = param->next){
+ int rv;
+ char *cv, *cs;
+ extern const char *tspecials;
+
+ cs = posting_characterset(param->value, NULL, HdrText);
+ cv = utf8_to_charset(param->value, cs, 0);
+ rv = (so_puts(so, "; ")
+ && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
+
+ if(cv && cv != param->value)
+ fs_give((void **) &cv);
+
+ if(!rv)
+ return(0);
+ }
+
+ return(1);
+}
+
+
+
+int
+lmc_body_header_line(char *line, int beginning)
+{
+ if(lmc.so && !lmc.all_written){
+ if(beginning && lmc.text_only && lmc.text_written
+ && (!struncmp(line, "content-type:", 13)
+ || !struncmp(line, "content-transfer-encoding:", 26)
+ || !struncmp(line, "content-disposition:", 20))){
+ /*
+ * "comment out" the real values since our comment isn't
+ * likely the same type, disposition nor encoding...
+ */
+ if(!so_puts(lmc.so, "X-"))
+ return(FALSE);
+ }
+
+ return(so_puts(lmc.so, line));
+ }
+
+ return(TRUE);
+}
+
+
+int
+lmc_body_header_finish(void)
+{
+ if(lmc.so && !lmc.all_written){
+ if(lmc.text_only && lmc.text_written
+ && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
+ return(FALSE);
+
+ return(so_puts(lmc.so, "\015\012"));
+ }
+
+ return(TRUE);
+}
+
+
+
+int
+pwbh_finish(int rv, STORE_S *so)
+{
+ if(so)
+ so_give(&so);
+
+ return(rv);
+}
+
+
+/*
+ * pine_free_body - c-client call wrapper so the body data pointer we
+ * we're using in a way c-client doesn't know about
+ * gets free'd appropriately.
+ */
+void
+pine_free_body(struct mail_bodystruct **body)
+{
+ /*
+ * Preempt c-client's contents.text.data clean up since we've
+ * usurped it's meaning for our own purposes...
+ */
+ pine_free_body_data (*body);
+
+ /* Then let c-client handle the rest... */
+ mail_free_body(body);
+}
+
+
+/*
+ * pine_free_body_data - free pine's interpretations of the body part's
+ * data pointer.
+ */
+void
+pine_free_body_data(struct mail_bodystruct *body)
+{
+ if(body){
+ if(body->type == TYPEMULTIPART){
+ PART *part = body->nested.part;
+ do /* for each part */
+ pine_free_body_data(&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ }
+ else if(body->contents.text.data)
+ so_give((STORE_S **) &body->contents.text.data);
+ }
+}
+
+
+long
+send_body_size(struct mail_bodystruct *body)
+{
+ long l = 0L;
+ PART *part;
+
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ part = body->nested.part; /* first body part */
+ do /* for each part */
+ l += send_body_size(&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ return(l);
+ }
+
+ return(l + body->size.bytes);
+}
+
+
+int
+sent_percent(void)
+{
+ int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
+ / send_bytes_to_send);
+ return(MIN(i, 100));
+}
+
+
+/*
+ * pine_smtp_verbose_out - write
+ */
+void
+pine_smtp_verbose_out(char *s)
+{
+#ifdef _WINDOWS
+ LPTSTR slpt;
+#endif
+ if(verbose_send_output && s){
+ char *p, last = '\0';
+
+ for(p = s; *p; p++)
+ if(*p == '\015')
+ *p = ' ';
+ else
+ last = *p;
+
+#ifdef _WINDOWS
+ /*
+ * The stream is opened in Unicode mode, so we need to fix the
+ * argument to fputs.
+ */
+ slpt = utf8_to_lptstr((LPSTR) s);
+ if(slpt){
+ _fputts(slpt, verbose_send_output);
+ fs_give((void **) &slpt);
+ }
+
+ if(last != '\012')
+ _fputtc(L'\n', verbose_send_output);
+#else
+ fputs(s, verbose_send_output);
+ if(last != '\012')
+ fputc('\n', verbose_send_output);
+#endif
+ }
+
+}
+
+
+/*
+ * pine_header_forbidden - is this name a "forbidden" header?
+ *
+ * name - the header name to check
+ * We don't allow user to change these.
+ */
+int
+pine_header_forbidden(char *name)
+{
+ char **p;
+ static char *forbidden_headers[] = {
+ "sender",
+ "x-sender",
+ "x-x-sender",
+ "date",
+ "received",
+ "message-id",
+ "in-reply-to",
+ "path",
+ "resent-message-id",
+ "resent-date",
+ "resent-from",
+ "resent-sender",
+ "resent-to",
+ "resent-cc",
+ "resent-reply-to",
+ "mime-version",
+ "content-type",
+ "x-priority",
+ "user-agent",
+ NULL
+ };
+
+ for(p = forbidden_headers; *p; p++)
+ if(!strucmp(name, *p))
+ break;
+
+ return((*p) ? 1 : 0);
+}
+
+
+/*
+ * hdr_is_in_list - is there a custom value for this header?
+ *
+ * hdr - the header name to check
+ * custom - the list to check in
+ * Returns 1 if there is a custom value, 0 otherwise.
+ */
+int
+hdr_is_in_list(char *hdr, PINEFIELD *custom)
+{
+ PINEFIELD *pf;
+
+ for(pf = custom; pf && pf->name; pf = pf->next)
+ if(strucmp(pf->name, hdr) == 0)
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * count_custom_hdrs_pf - returns number of custom headers in arg
+ * custom -- the list to be counted
+ * only_nonstandard -- only count headers which aren't standard pine headers
+ */
+int
+count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
+{
+ int ret = 0;
+
+ for(; custom && custom->name; custom = custom->next)
+ if(!only_nonstandard || !custom->standard)
+ ret++;
+
+ return(ret);
+}
+
+
+/*
+ * count_custom_hdrs_list - returns number of custom headers in arg
+ */
+int
+count_custom_hdrs_list(char **list)
+{
+ char **p;
+ char *q = NULL;
+ char *name;
+ char *t;
+ char save;
+ int ret = 0;
+
+ if(list){
+ for(p = list; (q = *p) != NULL; p++){
+ if(q[0]){
+ /* remove leading whitespace */
+ name = skip_white_space(q);
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ save = *t;
+ *t = '\0';
+ if(!pine_header_forbidden(name))
+ ret++;
+
+ *t = save;
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * set_default_hdrval - put the user's default value for this header
+ * into pf->textbuf.
+ * setthis - the pinefield to be set
+ * custom - where to look for the default
+ */
+CustomType
+set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
+{
+ PINEFIELD *pf;
+ CustomType ret = NoMatch;
+
+ if(!setthis || !setthis->name){
+ q_status_message(SM_ORDER,3,7,"Internal error setting default header");
+ return(ret);
+ }
+
+ setthis->textbuf = NULL;
+
+ for(pf = custom; pf && pf->name; pf = pf->next){
+ if(strucmp(pf->name, setthis->name) != 0)
+ continue;
+
+ ret = pf->cstmtype;
+
+ /* turn on editing */
+ if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
+ setthis->canedit = 1;
+
+ if(pf->val)
+ setthis->textbuf = cpystr(pf->val);
+ }
+
+ if(!setthis->textbuf)
+ setthis->textbuf = cpystr("");
+
+ return(ret);
+}
+
+
+/*
+ * pine_header_standard - is this name a "standard" header?
+ *
+ * name - the header name to check
+ */
+FieldType
+pine_header_standard(char *name)
+{
+ int i;
+
+ /* check to see if this is a standard header */
+ for(i = 0; pf_template[i].name; i++)
+ if(!strucmp(name, pf_template[i].name))
+ return(pf_template[i].type);
+
+ return(TypeUnknown);
+}
+
+
+/*
+ * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
+ * Allocates space for each name and addr ptr.
+ * Allocates space for default in textbuf, even if empty.
+ *
+ * head - the first PINEFIELD to fill in
+ * list - the list to parse
+ */
+void
+customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
+{
+ char **p, *q, *t, *name, *value, save;
+ PINEFIELD *pf;
+
+ pf = head;
+
+ if(list){
+ for(p = list; (q = *p) != NULL; p++){
+
+ if(q[0]){
+
+ /* anything after leading whitespace? */
+ if(!*(name = skip_white_space(q)))
+ continue;
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ /* if there is a space in the field-name, skip it */
+ if(isspace((unsigned char)*t)){
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Space not allowed in header name (%s)"),
+ name);
+ continue;
+ }
+
+ save = *t;
+ *t = '\0';
+
+ /* Don't allow any of the forbidden headers. */
+ if(pine_header_forbidden(name)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Not allowed to change header \"%s\""),
+ name);
+
+ *t = save;
+ continue;
+ }
+
+ if(pf){
+ if(pine_header_standard(name) != TypeUnknown)
+ pf->standard = 1;
+
+ pf->name = cpystr(name);
+ pf->type = FreeText;
+ pf->cstmtype = cstmtype;
+ pf->next = pf+1;
+
+#ifdef OLDWAY
+ /*
+ * Some mailers apparently break if we change
+ * user@domain into Fred <user@domain> for
+ * return-receipt-to,
+ * so we'll just call this a FreeText field, too.
+ */
+ /*
+ * For now, all custom headers are FreeText except for
+ * this one that we happen to know about. We might
+ * have to add some syntax to the config option so that
+ * people can tell us their custom header takes addresses.
+ */
+ if(!strucmp(pf->name, "Return-Receipt-to")){
+ pf->type = Address;
+ pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
+ *pf->addr = (ADDRESS *)NULL;
+ }
+#endif /* OLDWAY */
+
+ *t = save;
+
+ /* remove space between name and colon */
+ value = skip_white_space(t);
+
+ /* give them an alloc'd default, even if empty */
+ pf->textbuf = cpystr((*value == ':')
+ ? skip_white_space(++value) : "");
+ if(pf->textbuf && pf->textbuf[0])
+ pf->val = cpystr(pf->textbuf);
+
+ pf++;
+ }
+ else
+ *t = save;
+ }
+ }
+ }
+
+ /* fix last next pointer */
+ if(head && pf != head)
+ (pf-1)->next = NULL;
+}
+
+
+/*
+ * add_defaults_from_list - the PINEFIELDS list given by "head" is already
+ * setup except that it doesn't have default values.
+ * Add those defaults if they exist in "list".
+ *
+ * head - the first PINEFIELD to add a default to
+ * list - the list to get the defaults from
+ */
+void
+add_defaults_from_list(PINEFIELD *head, char **list)
+{
+ char **p, *q, *t, *name, *value, save;
+ PINEFIELD *pf;
+
+ for(pf = head; pf && list; pf = pf->next){
+ if(!pf->name)
+ continue;
+
+ for(p = list; (q = *p) != NULL; p++){
+
+ if(q[0]){
+
+ /* anything after leading whitespace? */
+ if(!*(name = skip_white_space(q)))
+ continue;
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ /* if there is a space in the field-name, skip it */
+ if(isspace((unsigned char)*t))
+ continue;
+
+ save = *t;
+ *t = '\0';
+
+ if(strucmp(name, pf->name) != 0){
+ *t = save;
+ continue;
+ }
+
+ *t = save;
+
+ /*
+ * Found the right header. See if it has a default
+ * value set.
+ */
+
+ /* remove space between name and colon */
+ value = skip_white_space(t);
+
+ if(*value == ':'){
+ char *defval;
+
+ defval = skip_white_space(++value);
+ if(defval && *defval){
+ if(pf->val)
+ fs_give((void **)&pf->val);
+
+ pf->val = cpystr(defval);
+ }
+ }
+
+ break; /* on to next pf */
+ }
+ }
+ }
+}
+
+
+/*
+ * parse_custom_hdrs - allocate PINEFIELDS for custom headers
+ * fill in the defaults
+ * Args - list -- The list to parse.
+ * cstmtype -- Fill the in cstmtype field with this value
+ */
+PINEFIELD *
+parse_custom_hdrs(char **list, CustomType cstmtype)
+{
+ PINEFIELD *pfields;
+ int i;
+
+ /*
+ * add one for possible use by fcc
+ * What is this "possible use"? I don't see it. I don't
+ * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
+ */
+ i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
+ pfields = (PINEFIELD *)fs_get((size_t) i);
+ memset(pfields, 0, (size_t) i);
+
+ /* set up the custom header pfields */
+ customized_hdr_setup(pfields, list, cstmtype);
+
+ return(pfields);
+}
+
+
+/*
+ * Combine the two lists of headers into one list which is allocated here
+ * and freed by the caller. Eliminate duplicates with values from the role
+ * taking precedence over values from the default.
+ */
+PINEFIELD *
+combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
+{
+ PINEFIELD *pfields, *pf, *pf2;
+ int max_hdrs, i;
+
+ max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
+ count_custom_hdrs_pf(dflthdrs,0);
+
+ pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
+ memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
+
+ pf = pfields;
+ for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
+ pf->name = pf2->name ? cpystr(pf2->name) : NULL;
+ pf->type = pf2->type;
+ pf->cstmtype = pf2->cstmtype;
+ pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
+ pf->val = pf2->val ? cpystr(pf2->val) : NULL;
+ pf->standard = pf2->standard;
+ pf->next = pf+1;
+ pf++;
+ }
+
+ /* if these aren't already there, add them */
+ for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
+ /* check for already there */
+ for(i = 0;
+ pfields[i].name && strucmp(pfields[i].name, pf2->name);
+ i++)
+ ;
+
+ if(!pfields[i].name){ /* this is a new one */
+ pf->name = pf2->name ? cpystr(pf2->name) : NULL;
+ pf->type = pf2->type;
+ pf->cstmtype = pf2->cstmtype;
+ pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
+ pf->val = pf2->val ? cpystr(pf2->val) : NULL;
+ pf->standard = pf2->standard;
+ pf->next = pf+1;
+ pf++;
+ }
+ }
+
+ /* fix last next pointer */
+ if(pf != pfields)
+ (pf-1)->next = NULL;
+
+ return(pfields);
+}
+
+
+/*
+ * free_customs - free misc. resources associated with custom header fields
+ *
+ * pf - pointer to first custom field
+ */
+void
+free_customs(PINEFIELD *head)
+{
+ PINEFIELD *pf;
+
+ for(pf = head; pf && pf->name; pf = pf->next){
+
+ fs_give((void **)&pf->name);
+
+ if(pf->val)
+ fs_give((void **)&pf->val);
+
+ /* only true for FreeText */
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ /* only true for Address */
+ if(pf->addr && *pf->addr)
+ mail_free_address(pf->addr);
+ }
+
+ fs_give((void **)&head);
+}
+
+
+/*
+ * encode_whole_header
+ *
+ * Returns 1 if whole value should be encoded
+ * 0 to encode only within comments
+ */
+int
+encode_whole_header(char *field, METAENV *header)
+{
+ int retval = 0;
+ PINEFIELD *pf;
+
+ if(field && (!strucmp(field, "Subject") ||
+ !strucmp(field, "Comment") ||
+ !struncmp(field, "X-", 2)))
+ retval++;
+ else if(field && *field && header && header->custom){
+ for(pf = header->custom; pf && pf->name; pf = pf->next){
+
+ if(!pf->standard && !strucmp(pf->name, field)){
+ retval++;
+ break;
+ }
+ }
+ }
+
+ return(retval);
+}
+
+
+/*----------------------------------------------------------------------
+ Post news via NNTP or inews
+
+Args: env -- envelope of message to post
+ body -- body of message to post
+
+Returns: -1 if failed or cancelled, 1 if succeeded
+
+WARNING: This call function has the side effect of writing the message
+ to the lmc.so object.
+ ----*/
+int
+news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *error_mess, error_buf[200], **news_servers;
+ char **servers_to_use;
+ int we_cancel = 0, server_no = 0, done_posting = 0;
+ void *orig_822_output;
+ BODY *bp = NULL;
+
+ error_buf[0] = '\0';
+ we_cancel = busy_cue("Posting news", NULL, 0);
+
+ dprint((4, "Posting: [%s]\n",
+ (header && header->env && header->env->newsgroups)
+ ? header->env->newsgroups : "?"));
+
+ if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
+ || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
+ && ps_global->VAR_NNTP_SERVER[0][0])){
+ /*---------- NNTP server defined ----------*/
+ error_mess = NULL;
+ servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
+ && alt_nntp_servers[0][0])
+ ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
+
+ /*
+ * Install our rfc822 output routine
+ */
+ orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
+ (void) mail_parameters(NULL, SET_RFC822OUTPUT,
+ (void *)post_rfc822_output);
+
+ server_no = 0;
+ news_servers = (char **)fs_get(2 * sizeof(char *));
+ news_servers[1] = NULL;
+ while(!done_posting && servers_to_use[server_no] &&
+ servers_to_use[server_no][0]){
+ news_servers[0] = servers_to_use[server_no];
+ ps_global->noshow_error = 1;
+#ifdef DEBUG
+ sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
+#else
+ sending_stream = nntp_open(news_servers, 0L);
+#endif
+ ps_global->noshow_error = 0;
+
+ if(sending_stream != NULL) {
+ unsigned short save_encoding, added_encoding;
+
+ /*
+ * Fake that we've got clearance from the transport agent
+ * for 8bit transport for the benefit of our output routines...
+ */
+ if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
+ && (bp = first_text_8bit(body))){
+ int i;
+
+ for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
+ ;
+
+ if(i > ENCMAX){ /* no empty encoding slots! */
+ bp = NULL;
+ }
+ else {
+ added_encoding = i;
+ body_encodings[added_encoding] = body_encodings[ENC8BIT];
+ save_encoding = bp->encoding;
+ bp->encoding = added_encoding;
+ }
+ }
+
+ /*
+ * Set global header pointer so we can get at it later...
+ */
+ send_header = header;
+ ps_global->noshow_error = 1;
+ if(nntp_mail(sending_stream, header->env, body) == 0)
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Error posting message: %s"),
+ sending_stream->reply);
+ else{
+ done_posting = 1;
+ error_buf[0] = '\0';
+ error_mess = NULL;
+ }
+
+ error_buf[sizeof(error_buf)-1] = '\0';
+ smtp_close(sending_stream);
+ ps_global->noshow_error = 0;
+ sending_stream = NULL;
+ if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
+ body_encodings[added_encoding] = NULL;
+ bp->encoding = save_encoding;
+ }
+
+ } else {
+ /*---- Open of NNTP connection failed ------ */
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Error connecting to news server: %s"),
+ ps_global->c_client_error);
+ error_buf[sizeof(error_buf)-1] = '\0';
+ dprint((1, error_buf));
+ }
+ server_no++;
+ }
+ fs_give((void **)&news_servers);
+ (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
+ } else {
+ /*----- Post via local mechanism -------*/
+#ifdef _WINDOWS
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Can't post, NNTP-server must be defined!"));
+#else /* UNIX */
+ error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
+ pipecb_f);
+#endif
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(error_mess){
+ if(lmc.so && !lmc.all_written)
+ so_give(&lmc.so); /* clean up any fcc data */
+
+ q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
+ return(-1);
+ }
+
+ lmc.all_written = 1;
+ return(1);
+}
+
+
+/* ----------------------------------------------------------------------
+ Figure out command to start local SMTP agent
+
+ Args: errbuf -- buffer for reporting errors (assumed non-NULL)
+
+ Returns an alloc'd copy of the local SMTP agent invocation or NULL
+
+ ----*/
+char *
+smtp_command(char *errbuf, size_t errbuflen)
+{
+#ifdef _WINDOWS
+ if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
+ && ps_global->VAR_SMTP_SERVER[0][0]))
+ strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
+
+ errbuf[errbuflen-1] = '\0';
+#else /* UNIX */
+# if defined(SENDMAIL) && defined(SENDMAILFLAGS)
+ char tmp[256];
+
+ snprintf(tmp, sizeof(tmp), "%.*s %.*s", (sizeof(tmp)-3)/2, SENDMAIL,
+ (sizeof(tmp)-3)/2, SENDMAILFLAGS);
+ return(cpystr(tmp));
+# else
+ strncpy(errbuf, _("No default posting command."), errbuflen);
+ errbuf[errbuflen-1] = '\0';
+# endif
+#endif
+
+ return(NULL);
+}
+
+
+
+#ifndef _WINDOWS
+/*----------------------------------------------------------------------
+ Hand off given message to local posting agent
+
+ Args: envelope -- The envelope for the BCC and debugging
+ header -- The text of the message header
+ errbuf -- buffer for reporting errors (assumed non-NULL)
+ len -- Length of errbuf
+
+ ----*/
+int
+mta_handoff(METAENV *header, struct mail_bodystruct *body,
+ char *errbuf, size_t len,
+ void (*bigresult_f) (char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+#ifdef DF_SENDMAIL_PATH
+ char cmd_buf[256];
+#endif
+ char *cmd = NULL;
+
+ /*
+ * A bit of complicated policy implemented here.
+ * There are two posting variables sendmail-path and smtp-server.
+ * Precedence is in that order.
+ * They can be set one of 4 ways: fixed, command-line, user, or globally.
+ * Precedence is in that order.
+ * Said differently, the order goes something like what's below.
+ *
+ * NOTE: the fixed/command-line/user precendence handling is also
+ * indicated by what's pointed to by ps_global->VAR_*, but since
+ * that also includes the global defaults, it's not sufficient.
+ */
+
+ if(ps_global->FIX_SENDMAIL_PATH
+ && ps_global->FIX_SENDMAIL_PATH[0]){
+ cmd = ps_global->FIX_SENDMAIL_PATH;
+ }
+ else if(!(ps_global->FIX_SMTP_SERVER
+ && ps_global->FIX_SMTP_SERVER[0])){
+ if(ps_global->COM_SENDMAIL_PATH
+ && ps_global->COM_SENDMAIL_PATH[0]){
+ cmd = ps_global->COM_SENDMAIL_PATH;
+ }
+ else if(!(ps_global->COM_SMTP_SERVER
+ && ps_global->COM_SMTP_SERVER[0])){
+ if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
+ (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
+ if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
+ cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
+ else
+ cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
+ }
+ else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
+ && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
+ (ps_global->vars[V_SMTP_SERVER].main_user_val.l
+ && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
+ if(ps_global->GLO_SENDMAIL_PATH
+ && ps_global->GLO_SENDMAIL_PATH[0]){
+ cmd = ps_global->GLO_SENDMAIL_PATH;
+ }
+#ifdef DF_SENDMAIL_PATH
+ /*
+ * This defines the default method of posting. So,
+ * unless we're told otherwise use it...
+ */
+ else if(!(ps_global->GLO_SMTP_SERVER
+ && ps_global->GLO_SMTP_SERVER[0])){
+ strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
+ cmd_buf[sizeof(cmd_buf)-1] = '\0';
+ }
+#endif
+ }
+ }
+ }
+
+ *errbuf = '\0';
+ if(cmd){
+ dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
+
+ (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
+ return(1);
+ }
+ else
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Hand off given message to local posting agent
+
+ Args: envelope -- The envelope for the BCC and debugging
+ header -- The text of the message header
+ errbuf -- buffer for reporting errors (assumed non-NULL)
+ errbuflen -- Length of errbuf
+
+ Fork off mailer process and pipe the message into it
+ Called to post news via Inews when NNTP is unavailable
+
+ ----*/
+char *
+post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
+ size_t errbuflen,
+ void (*bigresult_f) (char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *err = NULL;
+#ifdef SENDNEWS
+ char *s;
+ char tmp[200];
+
+ if(s = strstr(header->env->date," (")) /* fix the date format for news */
+ *s = '\0';
+
+ if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
+ strncpy(tmp, err, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
+ SENDNEWS, tmp);
+ }
+
+ if(s)
+ *s = ' '; /* restore the date */
+
+#else /* !SENDNEWS */ /* this is the default case */
+ strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
+ err[errbuflen-1] = '\0';
+#endif /* !SENDNEWS */
+ return(err);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Hand off message to local MTA; it parses recipients from 822 header
+
+ Args: header -- struct containing header data
+ body -- struct containing message body data
+ cmd -- command to use for handoff (%s says where file should go)
+ errs -- pointer to buf to hold errors
+
+ ----*/
+char *
+mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
+ char *errs, size_t errslen, void (*bigresult_f)(char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *result = NULL;
+ PIPE_S *pipe;
+
+ dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
+
+ if((pipe = open_system_pipe(cmd, &result, NULL,
+ PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
+ 0, pipecb_f, pipe_report_error)) != NULL){
+ if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
+ (TCPSTREAM *) pipe)){
+ strncpy(errs, _("Error posting."), errslen-1);
+ errs[errslen-1] = '\0';
+ }
+
+ if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
+ snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
+ if(result && bigresult_f)
+ (*bigresult_f)(result, CM_BR_ERROR);
+ }
+ }
+ else
+ snprintf(errs, errslen, _("Error running \"%s\""), cmd);
+
+ if(result){
+ our_unlink(result);
+ fs_give((void **)&result);
+ }
+
+ return(*errs ? errs : NULL);
+}
+
+
+/*
+ * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+pine_pipe_soutr_nl (void *stream, char *s)
+{
+ long rv = T;
+ char *p;
+ size_t n;
+
+ while(*s && rv){
+ if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0)
+ while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
+ if(rv < 0){
+ if(errno != EINTR){
+ rv = 0;
+ break;
+ }
+ }
+ else{
+ s += rv;
+ n -= rv;
+ }
+
+ if(p && rv){
+ s = p + 2; /* write UNIX EOL */
+ while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
+ if(rv < 0 && errno != EINTR){
+ rv = 0;
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ return(rv);
+}
+#endif
+
+
+/**************** "PIPE" READING POSTING I/O ROUTINES ****************/
+
+
+/*
+ * helpful def's
+ */
+#define S(X) ((PIPE_S *)(X))
+#define GETBUFLEN (4 * MAILTMPLEN)
+
+
+void *
+piped_smtp_open (char *host, char *service, long unsigned int port)
+{
+ char *postcmd;
+ void *rv = NULL;
+
+ if(strucmp(host, "localhost")){
+ char tmp[MAILTMPLEN];
+
+ snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
+ sizeof(tmp)-50, host);
+ tmp[sizeof(tmp)-1] = '\0';
+ mm_log(tmp, ERROR);
+ }
+ else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
+ rv = open_system_pipe(postcmd, NULL, NULL,
+ PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
+ 0, NULL, pipe_report_error);
+ fs_give((void **) &postcmd);
+ }
+ else
+ mm_log(ps_global->c_client_error, ERROR);
+
+ return(rv);
+}
+
+
+void *
+piped_aopen (NETMBX *mb, char *service, char *user)
+{
+ return(NULL);
+}
+
+
+/*
+ * piped_soutr - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+piped_soutr (void *stream, char *s)
+{
+ return(piped_sout(stream, s, strlen(s)));
+}
+
+
+/*
+ * piped_sout - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+piped_sout (void *stream, char *s, long unsigned int size)
+{
+ int i, o;
+
+ if(S(stream)->out.d < 0)
+ return(0L);
+
+ if((i = (int) size) != 0){
+ while((o = write(S(stream)->out.d, s, i)) != i)
+ if(o < 0){
+ if(errno != EINTR){
+ piped_abort(stream);
+ return(0L);
+ }
+ }
+ else{
+ s += o; /* try again, fix up counts */
+ i -= o;
+ }
+ }
+
+ return(1L);
+}
+
+
+/*
+ * piped_getline - Replacement for tcp_getline that reads one
+ * of our pipes rather than a tcp pipe
+ *
+ * C-client expects that the \r\n will be stripped off.
+ */
+char *
+piped_getline (void *stream)
+{
+ static int cnt;
+ static char *ptr;
+ int n, m;
+ char *ret, *s, *sp, c = '\0', d;
+
+ if(S(stream)->in.d < 0)
+ return(NULL);
+
+ if(!S(stream)->tmp){ /* initialize! */
+ /* alloc space to collect input (freed in close_system_pipe) */
+ S(stream)->tmp = (char *) fs_get(GETBUFLEN);
+ memset(S(stream)->tmp, 0, GETBUFLEN);
+ cnt = -1;
+ }
+
+ while(cnt < 0){
+ while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
+ if(errno != EINTR){
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ if(cnt == 0){
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ ptr = S(stream)->tmp;
+ }
+
+ s = ptr;
+ n = 0;
+ while(cnt--){
+ d = *ptr++;
+ if((c == '\015') && (d == '\012')){
+ ret = (char *)fs_get (n--);
+ memcpy(ret, s, n);
+ ret[n] = '\0';
+ return(ret);
+ }
+
+ n++;
+ c = d;
+ }
+ /* copy partial string from buffer */
+ memcpy((ret = sp = (char *) fs_get (n)), s, n);
+ /* get more data */
+ while(cnt < 0){
+ memset(S(stream)->tmp, 0, GETBUFLEN);
+ while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
+ if(errno != EINTR){
+ fs_give((void **) &ret);
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ if(cnt == 0){
+ if(n > 0)
+ ret[n-1] = '\0'; /* to try to get error message logged */
+ else{
+ piped_abort(stream);
+ return(NULL);
+ }
+ }
+
+ ptr = S(stream)->tmp;
+ }
+
+ if(c == '\015' && *ptr == '\012'){
+ ptr++;
+ cnt--;
+ ret[n - 1] = '\0'; /* tie off string with null */
+ }
+ else if ((s = piped_getline(stream)) != NULL) {
+ ret = (char *) fs_get(n + 1 + (m = strlen (s)));
+ memcpy(ret, sp, n); /* copy first part */
+ memcpy(ret + n, s, m); /* and second part */
+ fs_give((void **) &sp); /* flush first part */
+ fs_give((void **) &s); /* flush second part */
+ ret[n + m] = '\0'; /* tie off string with null */
+ }
+
+ return(ret);
+}
+
+
+/*
+ * piped_close - Replacement for tcp_close that closes pipes to our
+ * child rather than a tcp connection
+ */
+void
+piped_close(void *stream)
+{
+ /*
+ * Uninstall our hooks into smtp_send since it's being used by
+ * the nntp driver as well...
+ */
+ (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
+}
+
+
+/*
+ * piped_abort - close down the pipe we were using to post
+ */
+void
+piped_abort(void *stream)
+{
+ if(S(stream)->in.d >= 0){
+ close(S(stream)->in.d);
+ S(stream)->in.d = -1;
+ }
+
+ if(S(stream)->out.d){
+ close(S(stream)->out.d);
+ S(stream)->out.d = -1;
+ }
+}
+
+
+char *
+piped_host(void *stream)
+{
+ return(ps_global->hostname ? ps_global->hostname : "localhost");
+}
+
+
+unsigned long
+piped_port(void *stream)
+{
+ return(0L);
+}