/*
 * ========================================================================
 * Copyright 2013-2022 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * 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 "../pith/rules.h"

#include "../c-client/smtp.h"
#include "../c-client/nntp.h"
#ifndef _WINDOWS
int maildir_file_path(char *name, char *tmp, size_t sizeoftmp);
#endif /* _WINDOWS */


/* 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,	1, 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, char **, 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"


/*
 * 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 a lot 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));
#ifndef _WINDOWS
        if (!struncmp(tmp, "#md/",4) || !struncmp(tmp, "#mc/", 4)){
	    char tmp2[MAILTMPLEN];
	    maildir_file_path(tmp, tmp2, sizeof(tmp2));
	    strcpy(tmp, tmp2);
	}
#endif
	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)
	  alpine_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 empty 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(role ? *role : NULL);
	}

	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 	/* these types are transformed to text/plain */
		&& strucmp(b->subtype, "richtext")
		&& strucmp(b->subtype, "enriched")
		&& strucmp(b->subtype, "html"))
	      (*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+4; *p != '\0'; p++)
	    *(p-4) = *p;
	  *(p-4) = '\0';
	}

        pf->type        = pf_template[i].type;
	pf->canedit     = (i == N_FROM) ? CAN_EDIT(ps_global) : 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);
	}
    }
    return pf;
}


/*----------------------------------------------------------------------
    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;

	/* TRANSLATORS: program is busy updating the Answered flags so warns user */
	we_cancel = reply->forwarded
		    ? busy_cue(_("Setting \"Forwarded\" Keyword"), NULL, 0)
		    : 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);
    }
}


/*----------------------------------------------------------------------
     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, choice, num_rules = 0, added_rules = -1;
    long	 smtp_opts = 0L;
    char	*verbose_file = NULL, **smtp_list;
    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_sign){
	  bp = F_ON(F_ENABLE_8BIT, ps_global) ? first_text_8bit(body) : NULL;
	  result = sign_outgoing_message(header, &body, 0, &bp);
	}

	/* need to free new body from encrypt if sign fails? */
	if(result && ps_global->smime->do_encrypt)
    	  result = encrypt_outgoing_message(header, &body);

	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), alt_smtp_servers, 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, 
     * First we check for rules and make a list using the rules.
     */
    if(ps_global->VAR_SMTP_RULES && ps_global->VAR_SMTP_RULES[0]
        && ps_global->VAR_SMTP_RULES[0][0])
        while (ps_global->VAR_SMTP_RULES[num_rules]) num_rules++;

    if(num_rules){
	int i, j;

        added_rules = 0;
        smtp_list = (char **) fs_get ((num_rules + 1)*sizeof(char*));
        for (i = 0, j = 0; i < num_rules; i++){
	   RULELIST *rule = get_rulelist_from_code(V_SMTP_RULES,
                                                      ps_global->rule_list);
	   RULE_S *prule = get_rule(rule, i);
           if(prule){
	     char *rule_result = process_rule(prule, FOR_COMPOSE, header->env);
	     if(rule_result && *rule_result){
		smtp_list[j++] = cpystr(rule_result);
		added_rules++;
	     }
	   }
	}
    }

    if (added_rules < 0){
	smtp_list = (char **) fs_get (sizeof(char*));
	added_rules = 0;
    }
    smtp_list[added_rules] = NULL;

    choice = smtp_list && smtp_list[0] && smtp_list[0][0] ? 3 :
	(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0] ? 2 :
	(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0] 
		&& ps_global->VAR_SMTP_SERVER[0][0] ? 1 : -1));

    if(choice > 0){
        /*---------- SMTP ----------*/
       dprint((4, "call_mailer: via TCP (%s)\n",smtp_list[0]));
        TIME_STAMP("smtp-open start (tcp)", 1);
        sending_stream = smtp_open(choice == 3 ? smtp_list
				: (choice == 2 ? alt_smtp_servers
				: 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 = 0, added_encoding = 0;

	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;
#ifdef SMIME
	    if(ps_global->smime && ps_global->smime->do_sign 
		&& body->nested.part->next
		&& body->nested.part->next->body.contents.text.data
		&& body->nested.part->next->body.mime.text.data){
		  STORE_S *so;

		  so = (STORE_S *) body->nested.part->next->body.contents.text.data;
		  so_give(&so);
		  body->nested.part->next->body.contents.text.data = body->nested.part->next->body.mime.text.data;
		  body->nested.part->next->body.mime.text.data = NULL;
		}
#endif /* SMIME */
	    }
	}

	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));
	    if (ps_global->send_immediately)
	      printf("%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 -- suppress 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 existence...
     */
    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   flags;
    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.
	     *
	     * Also, some users are adding long text without spaces to the subject
	     * line (e.g. URLs) so we up that number too. For the moment this is
	     * going to 512 (a url that is about 6 lines long.)
	     */
	    flags = FLD_CRLF;
	    if(field && !strucmp("Subject", field)){
	      fold_by = 75;
	      flags |= FLD_NEXTSPC;
	    }
	    else if(field && !strucmp("References", field))
	      fold_by = 256;
	    else
	      fold_by = big;

	    folded = fold(value, fold_by, big, actual_field, " ", flags);

	    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)
      alpine_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 message 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 = NULL;

	    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){
	      if (ucs != NULL) fs_give((void **) &ucs);
	      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]);
}

void
remove_parameter(PARAMETER **param, char *paramname)
{
    PARAMETER *pm;

    if(param == NULL || *param == NULL 
	|| paramname == NULL || *paramname == '\0')
      return;

    for(pm = *param;
	pm != NULL && pm->attribute != NULL && strucmp(pm->attribute, paramname);
	pm = pm->next);

    if(pm){
	PARAMETER om, *qm;
	om.next = *param;
	for(qm = &om; qm->next != pm; qm = qm->next);
	qm->next = copy_parameters(pm->next);
	mail_free_body_parameter(&pm);
	*param = om.next;
    }
}


/*
 * 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)
{
    STORE_S *bodyso;
    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));

    bodyso = (STORE_S *) body->contents.text.data;

    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. We do not add this when signing a
	 * message, though...
	 */
#ifdef SMIME
	if(!ps_global->smime || !ps_global->smime->do_sign)
#endif /* SMIME */
	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);
#if 0	/* temporariy disable this code */
	    if(part == body->nested.part 
		&& ps_global->smime 
		&& ps_global->smime->do_sign
		&& ((f && !(*f)(s, "\015\012"))
		    || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012"))))
	      return 0;
#endif /* SMIME */
	} while ((part = part->next) != NULL);	/* until done */
					/* output trailing cookie */
	snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
	tmp[sizeof(tmp)-1] = '\0';
#ifdef SMIME
	if(ps_global->smime && ps_global->smime->do_sign 
	    && strlen(tmp) < sizeof(tmp)-2)
	  strncat(tmp, "\015\012", sizeof(tmp) - strlen(tmp) - 1);
#endif
	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(bodyso)
      gf_set_so_readc(&gc, bodyso);
    else
      return(1);

    /*
     * Don't add trailing line if it is ExternalText, which already guarantees
     * a trailing newline.
     */
    add_trailing_crlf = !(bodyso->src == ExternalText);

    so_seek(bodyso, 0L, 0);

    if(body->type != TYPEMESSAGE){ 	/* NOT encapsulated message */
	char *charset;

	if(body->type == TYPETEXT
	   && so_attr(bodyso, "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(bodyso, "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(bodyso);

    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);
}

char *
ToLower(char *s, char *t)
{
 int i;

 for(i = 0; s != NULL && s[i] != '\0'; i++)
   t[i] = s[i] + ((s[i] >= 'A' && s[i] <= 'Z') ? ('a' - 'A') : 0);
 t[i] = '\0';

 return t;
}

/*
 * 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, ToLower(body_types[body->type], tmp))
	     && so_puts(so, "/")
	     && so_puts(so, ToLower(body->subtype
				? body->subtype
			 	: rfc822_default_subtype (body->type),tmp))))
	  return(pwbh_finish(0, so));
	    
	if(body->parameter){
	    if(!pine_write_params(body->parameter, so))
	      return(pwbh_finish(0, so));
	}
	else if ((body->type != TYPEMESSAGE
		 || (body->subtype && strucmp(body->subtype, "RFC822")))
		&& (!so_puts(so, "; CHARSET=US-ASCII")))
	  return(pwbh_finish(0, so));
	    
	if(!so_puts(so, "\015\012"))
	  return(pwbh_finish(0, so));

			/* do not change the encoding of a TYPEMESSAGE part  */
	if ((body->encoding		/* note: encoding 7BIT never output! */
	     && !(so_puts(so, "Content-Transfer-Encoding: ")
		  && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
			? (body->type == TYPEMESSAGE ? ENCBINARY : ENCBASE64)
			: (body->encoding == ENC8BIT)
			  ?(body->type == TYPEMESSAGE ? 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",
	"list-help",		/* rfc 2369, section 3 */
	"list-unsubscribe",
	"list-subscribe",
	"list-post",
	"list-owner",
	"list-archive",
	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) : "");
		    /* The instruction to remove the double quotes existed in
		     * pine, but it was removed in alpine, but I do not know
		     * why, so we will restore it until we understand why it
		     * was removed. Also see:
		     * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=981781
		     */
		    if(pf->textbuf && pf->textbuf[0]){
		      removing_double_quotes(pf->textbuf);
		      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)
#define SENDTMPLEN 256
    char tmp[SENDTMPLEN];
			/* SENDTMPLEN == sizeof(tmp) */
    snprintf(tmp, sizeof(tmp), "%.*s %.*s", (SENDTMPLEN-3)/2, SENDMAIL,
	    (SENDTMPLEN-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,
	    char **alt_smtp_servers,
	    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 precedence 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])
		&& !(alt_smtp_servers
		      && alt_smtp_servers[0]
		      && alt_smtp_servers[0][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," (")) != NULL) /* 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];
				/* MAILTMPLEN = sizeof(tmp) */
	snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"), 
		MAILTMPLEN-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);
}
