#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: send.c,v 4.183 1994/10/10 20:01:15 mikes Exp $";
#endif
/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Builiding, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"

   Copyright 1989-1994  University of Washington

    Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee to the University of
   Washington is hereby granted, provided that the above copyright notice
   appears in all copies and that both the above copyright notice and this
   permission notice appear in supporting documentation, and that the name
   of the University of Washington not be used in advertising or publicity
   pertaining to distribution of the software without specific, written
   prior permission.  This software is made available "as is", and
   THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
   WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
   NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
   INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
   (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
   WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
   Pine and Pico are trademarks of the University of Washington.
   No commercial use of these trademarks may be made without prior
   written permission of the University of Washington.

   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  Revision: 2.13                             *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
       send.c
       Functions for composing and sending mail

 ====*/

#include "headers.h"
#include "smtp.h"
#include "nntp.h"


#ifndef TCPSTREAM
#define TCPSTREAM void
#endif

#define	MIME_VER	"MIME-Version: 1.0\015\012"


#ifdef ANSI
int	   redraft(CONTEXT_S *, char *, ENVELOPE **, BODY **, char **,
		   PINEFIELD **);
void       outgoing2strings(METAENV *, BODY *, void **, PATMT **);
void       strings2outgoing(METAENV *, BODY **, PATMT *);
void       simple_header_parse(char *, char **, char **);
int        write_fcc(char *, CONTEXT_S *, STORE_S *, char *);
void       create_message_body(BODY **, PATMT *);
ADDRESS   *generate_from();
void       free_attachment_list(PATMT **);
PINEFIELD *get_dflt_custom_hdrs();
void       free_customs(PINEFIELD *);
int        view_as_rich(char *, int);
int        count_custom_hdrs(void);
int        is_a_forbidden_hdr(char *);
int        is_a_std_hdr(char *);
void       set_default_hdrval(PINEFIELD *);
void       customized_hdr_setup(PINEFIELD *);
void       update_message_id(ENVELOPE *, char *);
int	   valid_subject(char *, char **, char **, char **);

/*
 * Pine's mutated versions of c-client routines, but changed to allow
 * piping attachments thru encoding filters on the way out the
 * SMTP mail slot or into file handed to sendmail... (mss, 4 Nov 1992)
 */
void       pine_write_body_header(char **, BODY  *);
void       pine_encode_body(BODY *);
int        pine_rfc822_header(METAENV *, BODY *, soutr_t, TCPSTREAM *);
int        pine_header_line(char *, char *, METAENV *, char *, soutr_t,
			    TCPSTREAM *);
int	   pine_address_line(char *, char *, METAENV *, ADDRESS *,
			     soutr_t, TCPSTREAM *);
int        pine_rfc822_output(METAENV *, BODY *, soutr_t, TCPSTREAM *);
long       pine_rfc822_output_body(BODY *,soutr_t,TCPSTREAM *);
long       pine_smtp_mail(SMTPSTREAM *,char *,METAENV *,BODY *);
void       pine_free_body(BODY **);
void       pine_free_body_data(BODY *);
void       pine_free_body_part(PART **);
void       set_mime_types(BODY *);
int        call_mailer(METAENV *, BODY *);
int        news_poster(METAENV *, BODY *);
char      *tidy_smtp_mess(char *, char *, char *);
FileTypes  file_type(void *, long);
char      *mime_stats(BODY *);
void       mime_recur(BODY *);
int        pine_nntp_mail(SMTPSTREAM *, METAENV *, BODY *);
int        open_fcc(char *, CONTEXT_S **, int);

#else /* ANSI */

int	   redraft();
void       outgoing2strings();
void	   strings2outgoing();
void       create_message_body();
ADDRESS   *generate_from();
void       free_attachment_list();
PINEFIELD *get_dflt_custom_hdrs();
void       free_customs();
int        view_as_rich();
int        count_custom_hdrs();
int        is_a_forbidden_hdr();
int        is_a_std_hdr();
void       set_default_hdrval();
void       customized_hdr_setup();
void       update_message_id();
int	   valid_subject();
void       simple_header_parse();
int        write_fcc();
void       pine_write_body_header();
void       pine_encode_body();
int        pine_rfc822_header();
int        pine_header_line();
int        pine_address_line();
int        pine_rfc822_output();
long       pine_rfc822_output_body();
long       pine_smtp_mail();
void       pine_free_body();
void       pine_free_body_data();
void       pine_free_body_part();
void       set_mime_types();
int        call_mailer();
int        news_poster();
char      *tidy_smtp_mess();
FileTypes  file_type();
char      *mime_stats();
void       mime_recur();
int        pine_nntp_mail();
int        open_fcc();
#endif   /* ANSI */


/*
 * Buffer to hold pointers into pine data that's needed by pico. 
 * Defined here so as not to take up room on the stack.  Better malloc'd?
 */
static	PICO	pbuf;


/* 
 * Storage object where the FCC 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.
 */
static STORE_S *fcc_so      = NULL;
static int	fcc_written = 0;


/*
 * 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 SMTPSTREAM *sending_stream = NULL;


#ifdef	DOS
#define	FCC_SOURCE	FileStar
#define	FCC_STREAM_MODE	OP_SHORTCACHE
#else
#define	FCC_SOURCE	CharStar
#define	FCC_STREAM_MODE	0L
#endif


#define	INTRPT_PMT \
	    "Continue interrupted composition (answering \"n\" won't erase it)"
#define	PSTPND_PMT \
	    "Continue postponed composition (answering \"No\" won't erase it)"
#define	POST_PMT   \
	    "Posted message may go to thousands of readers. Really post"



/*----------------------------------------------------------------------
    Compose screen (not forward or reply). Set up envelope, call composer
  
   Args: pine_state -- The usual pine structure
 
  Little front end for the compose screen
  ---*/
void
compose_screen(pine_state)
    struct pine *pine_state;
{
    ps_global = pine_state;
    mailcap_free(); /* free resources we won't be using for a while */
    pine_state->next_screen = pine_state->prev_screen;
    compose_mail(NULL, NULL);
}



/*----------------------------------------------------------------------
     Format envelope for outgoing message and call editor

 Args:  given_to -- An address to send mail to (usually from command line 
                       invocation)
        fcc_arg  -- The fcc that goes with this address.
 
 If a "To" line is given format that into the envelope and get ready to call
           the composer
 If there's a message postponed, offer to continue it, and set it up,
 otherwise just fill in the outgoing envelope as blank.

 NOTE: we ignore postponed and interrupted messages in nr mode
 ----*/
void 
compose_mail(given_to, fcc_arg)
char *given_to,
     *fcc_arg;
{
    BODY      *body;
    ENVELOPE  *outgoing = NULL;
    PINEFIELD *custom   = NULL;
    char      *p,
	      *fcc_to_free,
	      *fcc      = NULL,
	      *sig      = NULL;
    int        fcc_is_sticky = 0;

    dprint(1, (debugfile,
                 "\n\n    ---- COMPOSE SCREEN (not in pico yet) ----\n"));

    /*-- Check for INTERRUPTED mail  --*/
    if(!ps_global->anonymous && !given_to){
	char	     file_path[MAXPATH+1];
	int	     ret = 'n';

	/* build filename and see if it exists.  build_path creates
	 * an explicit local path name, so all c-client access is thru
	 * local drivers.
	 */
	file_path[0] = '\0';
	build_path(file_path, ps_global->home_dir, INTERRUPTED_MAIL);

	/* check to see if the folder exists, the user wants to continue
	 * and that we can actually read something in...
	 */
	if(folder_exists("[]", file_path)
	   && (ret = want_to(INTRPT_PMT, 'y', 'x', NO_HELP, 0, 0)) == 'y'
	   && !redraft(NULL, file_path, &outgoing, &body, &fcc, &custom)){
	    return;
	}
	else if(ret == 'x'){
	    q_status_message(1, 0, 3, "Composition cancelled");
	    return;
	}
    }

    /*-- Check for postponed mail --*/
    if(!outgoing				/* not replying/forwarding */
       && !ps_global->anonymous			/* not special anon mode */
       && !given_to				/* not command line send */
       && ps_global->VAR_POSTPONED_FOLDER	/* folder to look in */
       && ps_global->VAR_POSTPONED_FOLDER[0]){
	CONTEXT_S   *p_cntxt;
	int	     ret = 'n';

	/* find default context to look for folder */
	if(!(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...
	 */
	if(folder_exists(p_cntxt ? p_cntxt->context : "[]", 
			 ps_global->VAR_POSTPONED_FOLDER)
	   && (ret = want_to(PSTPND_PMT, 'y', 'x', NO_HELP, 0, 0)) == 'y'
	   && !redraft(p_cntxt, ps_global->VAR_POSTPONED_FOLDER,
		       &outgoing, &body, &fcc, &custom)){
	    return;
	}
	else if(ret == 'x'){
	    q_status_message(1, 0, 3, "Composition cancelled");
	    return;
	}
    }

    /*-- normal composition --*/
    if(!outgoing){
        /*=================  Compose new message ===============*/
        body         = mail_newbody();
        outgoing     = mail_newenvelope();

        if(given_to)
	  rfc822_parse_adrlist(&outgoing->to, given_to, ps_global->maildomain);

        outgoing->message_id = generate_message_id(ps_global);

	/*
	 * The type of storage object allocated below is vitally
	 * important.  See SIMPLIFYING ASSUMPTION #37
	 */
	if(!(body->contents.binary=(void *)so_get(PicoText,NULL,EDIT_ACCESS))){
	    q_status_message(1, 2, 4,
			     "\007Problem creating space for message text.");
	    return;
	}

	if((sig = get_signature()) && *sig){
	    so_puts(body->contents.binary, sig);
	    fs_give((void **)&sig);
	}

	body->type = TYPETEXT;
	
    }

    ps_global->prev_screen = compose_screen;
    if(!(fcc_to_free = fcc))		/* Didn't pick up fcc, use given  */
      fcc = fcc_arg;

    /*
     * check whether a build_address-produced fcc is different from
     * fcc.  If same, do nothing, if different, set sticky bit in pine_send.
     */
    if(outgoing->to){
	ADDRESS *addr;
	char    *to;
	char    *tmp_fcc;

	/* pick off the first addr in list */
	addr = copyaddr(outgoing->to);
	to   = cpystr(addr_string(addr));
	if(addr)
	    mail_free_address(&addr);

	(void)build_address(to, NULL, NULL, &tmp_fcc);

	fs_give((void **)&to);

	if(strucmp(fcc, tmp_fcc))
	    fcc_is_sticky++;  /* cause sticky bit to get set */

	fs_give((void **)&tmp_fcc);
    }

    pine_send(outgoing, &body, "COMPOSE MESSAGE", fcc, NULL, NULL, custom,
	      fcc_is_sticky);

    if(fcc_to_free)
      fs_give((void **)&fcc_to_free);

    mail_free_envelope(&outgoing);
    pine_free_body(&body);
}



/*----------------------------------------------------------------------
    Given context and folder, offer the context for redraft...

 Args:	cntxt -- 
	mbox -- 
	outgoing -- 
	body -- 
	fcc -- 
	custom -- 
 
 ----*/
int
redraft(cntxt, mbox, outgoing, body, fcc, custom)
    CONTEXT_S  *cntxt;
    char       *mbox;
    ENVELOPE  **outgoing;
    BODY      **body;
    char      **fcc;
    PINEFIELD **custom;
{
    MAILSTREAM	*stream;
    ENVELOPE	*e = NULL;
    BODY	*b;
    PART        *part;
    STORE_S	*so = NULL;
    PINEFIELD   *pf;
    gf_io_t	 pc;
    char	*extras, **fields, **values, *c_string;
    long	 cont_msg = 1L;
    int		 i, j, rv = 0, pine_generated = 0;
    char	*news = NULL;
#define	TMP_BUF1	(tmp_20k_buf)
#define	TMP_BUF2	(tmp_20k_buf + MAXPATH)

    /*
     * if we have a context AND we're looking at an ambiguous name, 
     * set the context string.  Otherwise, our context "relativeness"
     * code in context.c may cause the fully qualified name to still
     * get interpreted relative to the provided context...
     */
    c_string = (cntxt && context_isambig(mbox)) ? cntxt->context : "[]";

    /*
     * Need to check here to see if the folder being opened for redraft
     * is the currently opened folder.  If so, just use the current
     * mail_stream...
     */
    context_apply(TMP_BUF1, c_string, mbox);
    if(context_isambig(ps_global->cur_folder))
      context_apply(TMP_BUF2, ps_global->context_current->context,
		    ps_global->cur_folder);
    else
      strcpy(TMP_BUF2, ps_global->cur_folder);

    if(!strcmp(TMP_BUF1, TMP_BUF2)){
	stream = ps_global->mail_stream;
    }
    else{
	if((stream = context_open(c_string, NULL, mbox, FCC_STREAM_MODE))
	   && (extras = strindex(stream->mailbox, '<'))
	   && !strcmp(extras + 1, "no_mailbox>"))
	  stream = NULL;
    }

    if(stream){
	/*
	 * If the we're manipulating the current folder, don't bother
	 * with index
	 */
	
	if(!stream->nmsgs){
	    q_status_message(0, 2, 5,
			   "\007Empty folder.  No messages really postponed!");
	    if(stream != ps_global->mail_stream)
	      mail_close(stream);
	    else{
		q_status_message1(0, 3, 3,
			 "Returning to \"%s\"", ps_global->inbox_name);
		mail_close(stream);
		ps_global->mail_stream = NULL;
		do_broach_folder(ps_global->inbox_name,
				 ps_global->context_list);
		ps_global->next_screen = mail_index_screen;
		stream = ps_global->mail_stream;
	    }

	    context_delete(c_string, stream, mbox);

	    return(0);
	}
	else if(stream == ps_global->mail_stream){
	    /*
	     * Since the user's got this folder already opened and they're
	     * on a selected message, pick that one rather than rebuild
	     * another index screen...
	     */
	    cont_msg = mn_m2raw(ps_global->msgmap,
				mn_get_cur(ps_global->msgmap));
	}
	else if(stream->nmsgs > 1L){		/* offer browser ? */
	    MSGNO_S *msgmap = NULL;

	    mn_init(&msgmap, stream->nmsgs);
	    mn_set_cur(msgmap, 1L);

	    clear_index_cache();
	    rv = index_lister(ps_global, cntxt, mbox, stream, msgmap);
	    clear_index_cache();
	    cont_msg = mn_get_cur(msgmap);
	    mn_give(&msgmap);

	    if(rv){
		q_status_message(0, 0, 3, "Composition cancelled");
		mail_close(stream);
		return(0);
	    }
	}

	if((e = mail_fetchstructure(stream, cont_msg, &b))
	   && (so = (void *)so_get(PicoText, NULL, EDIT_ACCESS))){
	    gf_set_so_writec(&pc, so);

	    *custom = get_dflt_custom_hdrs();
	    i       = (count_custom_hdrs() + 3) * sizeof(char *);

	    /*
	     * 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 reuse c-client's
	     * PARAMETER struct which is a simple char * pairing, but that
	     * doesn't make sense to pass to fetchheader_lines()...
	     */
	    fields  = fs_get((size_t) i);
	    values  = fs_get((size_t) i);
	    memset(fields, 0, (size_t) i);
	    memset(values, 0, (size_t) i);

	    fields[i = 0] = "Fcc";		/* fcc special case... */

	    /*
	     * BOGUS ALERT!  A c-client DEFICIENCY doesn't give us the
	     * newsgroups if the folder's accessed via IMAP, so if
	     * fetchstructure says there aren't any, we need to go to 
	     * the trouble of checking ourselves.  Sheesh.
	     */
	    if(!e->newsgroups)
	      fields[++i] = news = "Newsgroups";

	    for(pf = *custom, ++i; pf && pf->name; pf = pf->next, i++)
	      fields[i] = pf->name;		/* assign custom fields */

	    if(extras = xmail_fetchheader_lines(stream, cont_msg, fields)){
		simple_header_parse(extras, fields, values);
		fs_give((void **)&extras);

		for(pf = *custom, i = (e->newsgroups ? 1 : 2);
		    pf && pf->name;
		    pf = pf->next, i++){
		    if(values[i]){
			fs_give((void **)&pf->text);
			pf->text = values[i]; /* freed in pine_send! */
		    }
		}

		if(values[0])			/* If "Fcc:" was there...  */
		  pine_generated = 1;		/* we put it there? */

		if(fcc)				/* fcc: special case... */
		  *fcc = values[0] ? values[0] : cpystr("");

		if(!e->newsgroups && values[1])
		  e->newsgroups = values[1];
	    }

	    fs_give((void **)&fields);
	    fs_give((void **)&values);

	    *outgoing = copy_envelope(e);

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

		/*
		 * Generate a fresh message id for pretty much the same
		 * reason From and such got wacked...
		 */
		if((*outgoing)->message_id)
		  fs_give((void **)&(*outgoing)->message_id);

		(*outgoing)->message_id = generate_message_id(ps_global);
	    }

	    if(b && b->type != TYPETEXT){	/* already TYPEMULTIPART */
		*body			   = copy_body(NULL, b);
		part			   = (*body)->contents.part;
		part->body.type		   = TYPETEXT;
		part->body.contents.binary = (void *)so;
		get_body_part_text(stream, &b->contents.part->body,
				   cont_msg, "1", pc, "", NULL);

		if(!fetch_contents(stream, cont_msg, *body, *body))
		  q_status_message(1, 2, 4,
				   "\007Error including all message parts");
	    }
	    else{
		*body			 = mail_newbody();
		(*body)->type		 = TYPETEXT;
		(*body)->contents.binary = (void *)so;
		get_body_part_text(stream, b, cont_msg, "1", pc, "", NULL);
	    }

	    /* We have what we want, blast this message... */
	    mail_setflag(stream, long2string(cont_msg), "\\DELETED");
	    mail_expunge(stream);
	}

	cont_msg = stream->nmsgs;		/* overload: "cont_msg" */
	if(stream != ps_global->mail_stream)
	  mail_close(stream);

	if(!cont_msg){				/* blast empty folder */
	    if(stream == ps_global->mail_stream){
		q_status_message1(0, 3, 3,
			 "No more postponed messages, returning to \"%s\"",
			 ps_global->inbox_name);
		mail_close(stream);
		ps_global->mail_stream = NULL;
		do_broach_folder(ps_global->inbox_name,
				 ps_global->context_list);
		ps_global->next_screen = mail_index_screen;
	    }

	    /* try to piggy back on another stream */
	    stream = context_same_stream(c_string, mbox,
					 ps_global->mail_stream);
	    if(!stream && ps_global->mail_stream!=ps_global->inbox_stream)
	      stream = context_same_stream(c_string, mbox,
					   ps_global->inbox_stream);

	    context_delete(c_string, stream, mbox);
	}

	rv = (e && so) ? 1 : 0;
    }

    return(rv);
}



/*----------------------------------------------------------------------
    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(text, fields, values)
    char   *text, **fields, **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')
		    break;

		  if(!isspace(*p))		/* order here is key... */
		    break;
	      }

	      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 && !(*p == '\015' && *(p+1) == '\012'))
		    *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;
	  }
    }
}

 

#ifdef	DOS
/*----------------------------------------------------------------------
    Verify that the necessary pieces are around to allow for
    message sending under DOS

Args:  strict -- tells us if a remote stream is required before
		 sending is permitted.

The idea is to make sure pine knows enough to put together a valid 
from line.  The things we MUST know are a user-id, user-domain and
smtp server to dump the message off on.  Typically these are 
provided in pine's configuration file, but if not, the user is
queried here.
 ----*/
int
dos_valid_from(strict)
    int strict;
{
    char        prompt[80], answer[60], *p;
    int         rc, i;
    HelpType    help;

    /*
     * If we're told to, require that a remote stream exist before
     * permitting mail to get sent.  Someday this will be removed and
     * sent mail sans a stream will get stuffed into an "outbox"...
     */
    if(strict && (!(ps_global->mail_stream
		    && !ps_global->mail_stream->anonymous
		    && mail_valid_net(ps_global->mail_stream->mailbox,
				      ps_global->mail_stream->dtb,NULL,NULL))
		  && !(ps_global->inbox_stream
		       && ps_global->inbox_stream != ps_global->mail_stream
		       && !ps_global->inbox_stream->anonymous
		       && mail_valid_net(ps_global->inbox_stream->mailbox,
				    ps_global->inbox_stream->dtb,NULL,NULL)))){
	q_status_message(0, 2, 2,
			 "Can't send message without an open remote folder");
	return(0);
    }

    /*
     * query for user name portion of address, use IMAP login
     * name as default
     */
    if(!ps_global->VAR_USER_ID || ps_global->VAR_USER_ID[0] == '\0'){
	if(ps_global->mail_stream
	   && (p = imap_user(ps_global->mail_stream->mailbox)))
	  strcpy(answer, p);
	else
	  answer[0] = '\0';

	sprintf(prompt,"User-id for From address : "); 

	help = NO_HELP;
	while(1) {
	    rc = optionally_enter(answer,-3,0,79,1,0,prompt,NULL,help,0);
	    if(rc == 2)
	      continue;

	    if(rc == 3){
		help = (help == NO_HELP) ? h_sticky_user_id : NO_HELP;
		continue;
	    }

	    if(rc != 4)
	      break;
	}

	if(rc == 1 || (rc == 0 && !answer)) {
	    q_status_message(0, 0, 2,
		   "Send cancelled (User-id must be provided before sending)");
	    return(0);
	}

	/* save the name */
	sprintf(prompt, "Preserve %s as \"user-id\" in PINERC", answer);
	if(ps_global->blank_user_id
	   && want_to(prompt, 'y', 'n', NO_HELP, 0, 0) == 'y'){
	    set_variable(V_USER_ID, answer, 1);
	}
	else{
            fs_give((void **)&(ps_global->VAR_USER_ID));
	    ps_global->VAR_USER_ID = cpystr(answer);
	}
    }

    /* query for personal name */
    if(!ps_global->VAR_PERSONAL_NAME || ps_global->VAR_PERSONAL_NAME[0]=='\0'){
	answer[0] = '\0';
	sprintf(prompt,"Personal name for From address : "); 

	help = NO_HELP;
	while(1) {
	    rc = optionally_enter(answer,-3,0,79,1,0,prompt,NULL,help,0);
	    if(rc == 2)
	      continue;

	    if(rc == 3){
		help = (help == NO_HELP) ? h_sticky_personal_name : NO_HELP;
		continue;
	    }

	    if(rc != 4)
	      break;
    	}

	if(rc == 0 && answer){		/* save the name */
	    sprintf(prompt, "Preserve %s as \"personal-name\" in PINERC",
		    answer);
	    if(ps_global->blank_personal_name
	       && want_to(prompt, 'y', 'n', NO_HELP, 0, 0) == 'y'){
		set_variable(V_PERSONAL_NAME, answer, 1);
	    }
	    else{
        	fs_give((void **)&(ps_global->VAR_PERSONAL_NAME));
		ps_global->VAR_PERSONAL_NAME = cpystr(answer);
	    }
	}
    }

    /* 
     * query for host/domain portion of address, using IMAP
     * host as default
     */
    if(ps_global->blank_user_domain 
       || ps_global->maildomain == ps_global->localdomain
       || ps_global->maildomain == ps_global->hostname){
	if(ps_global->inbox_name[0] == '{'){
	    for(i=0;ps_global->inbox_name[i+1] != '}'; i++)
		answer[i] = ps_global->inbox_name[i+1];

	    answer[i] = '\0';
	}
	else
	  answer[0] = '\0';

	sprintf(prompt,"Host/domain for From address : "); 

	help = NO_HELP;
	while(1) {
	    rc = optionally_enter(answer,-3,0,79,1,0,prompt,NULL,help,0);
	    if(rc == 2)
	      continue;

	    if(rc == 3){
		help = (help == NO_HELP) ? h_sticky_domain : NO_HELP;
		continue;
	    }

	    if(rc != 4)
	      break;
	}

	if(rc == 1 || (rc == 0 && !answer)) {
	    q_status_message(0, 0, 2,
	  "Send cancelled (Host/domain name must be provided before sending)");
	    return(0);
	}

	/* save the name */
	sprintf(prompt, "Preserve %s as \"user-domain\" in PINERC",
		answer);
	if(!ps_global->userdomain && !ps_global->blank_user_domain
	   && want_to(prompt, 'y', 'n', NO_HELP, 0, 0) == 'y'){
	    set_variable(V_USER_DOMAIN, answer, 1);
	    fs_give((void **)&(ps_global->maildomain));	/* blast old val */
	    ps_global->userdomain      = cpystr(answer);
	    ps_global->maildomain      = ps_global->userdomain;
	}
	else{
            fs_give((void **)&(ps_global->maildomain));
            ps_global->userdomain = cpystr(answer);
	    ps_global->maildomain = ps_global->userdomain;
	}
    }

    /* check for smtp server */
    if(!ps_global->VAR_SMTP_SERVER ||
       !ps_global->VAR_SMTP_SERVER[0] ||
       !ps_global->VAR_SMTP_SERVER[0][0]){
	char **list;

	if(ps_global->inbox_name[0] == '{'){
	    for(i=0;ps_global->inbox_name[i+1] != '}'; i++)
	      answer[i] = ps_global->inbox_name[i+1];
	    answer[i] = '\0';
	}
	else
          answer[0] = '\0';

        sprintf(prompt,"SMTP server to forward message : "); 

	help = NO_HELP;
        while(1) {
            rc = optionally_enter(answer,-3,0,79,1,0,prompt,NULL,help,0);
            if(rc == 2)
                  continue;

	    if(rc == 3){
		help = (help == NO_HELP) ? h_sticky_smtp : NO_HELP;
		continue;
	    }

            if(rc != 4)
                  break;
        }

        if(rc == 1 || (rc == 0 && answer[0] == '\0')) {
            q_status_message(0, 0, 2,
	       "Send cancelled (SMTP server must be provided before sending)");
            return(0);
        }

	/* save the name */
        list    = (char **) fs_get(2 * sizeof(char *));
	list[0] = cpystr(answer);
	list[1] = NULL;
	set_variable_list(V_SMTP_SERVER, list);
	fs_give((void *)&list[0]);
	fs_give((void *)list);
    }

    return(1);
}
#endif


/* this is for initializing the fixed header elements in pine_send() */
/*
prompt::name::help::prlen::maxlen::realaddr::
builder::affected_entry::selector::key_label::display_it::break_on_comma::
is_attach::rich_header::only_file_chars::single_space::sticky::
hd_text
*/
static struct headerentry he_template[]={
  {"From    : ",  "From",        h_composer_from,       10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 1, 0, 1, 0},
  {"Reply To: ",  "Reply To",    h_composer_reply_to,   10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 1, 0, 1, 0},
  {"To      : ",  "To",          h_composer_to,         10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 0, 0, 1, 0},
  {"Cc      : ",  "Cc",          h_composer_cc,         10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 0, 0, 1, 0},
  {"Bcc     : ",  "Bcc",         h_composer_bcc,        10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 1, 0, 1, 0},
  {"Newsgrps: ",  "Newsgroups",  h_composer_news,        10, 0, NULL,
   news_build,    NULL, news_group_selector,  "To NwsGrps",0, 1, 0, 1, 0, 1, 0},
  {"Fcc     : ",  "Fcc",         h_composer_fcc,        10, 0, NULL,
   NULL,          NULL, folders_for_fcc,      "To Fldrs",  0, 0, 0, 1, 1, 1, 0},
  {"Attchmnt: ",  "Attchmnt",    h_composer_attachment, 10, 0, NULL,
   NULL,          NULL, NULL,                 "To Files",  0, 1, 1, 0, 0, 1, 0},
  {"Subject : ",  "Subject",     h_composer_subject,    10, 0, NULL,
   valid_subject, NULL, NULL,                 NULL,        0, 0, 0, 0, 0, 0, 0}
};

static struct headerentry he_custom_addr_templ={
   NULL,          NULL,          h_composer_custom_addr,10, 0, NULL,
   build_address, NULL, addr_book_compose,    "To AddrBk", 0, 1, 0, 1, 0, 1, 0};
static struct headerentry he_custom_free_templ={
   NULL,          NULL,          h_composer_custom_free,10, 0, NULL,
   NULL,          NULL, NULL,                 NULL,        0, 0, 0, 1, 0, 0, 0};

#define N_FROM    0
#define N_REPLYTO 1
#define N_TO      2
#define N_CC      3
#define N_BCC     4
#define N_NEWS    5
#define N_FCC     6
#define N_ATTCH   7
#define N_SUBJ    8
/* this is used in pine_send and pine_simple_send */
static PINEFIELD pf_template[] = {
  {"From",       Address},
  {"Reply-To",   Address},
  {"To",         Address},
  {"cc",         Address},
  {"bcc",        Address},
  {"Newsgroups", FreeText},
  {"Fcc",        Fcc},
  {"Attchmnt",   Attachment},
  {"Subject",    FreeText},
  {NULL,         FreeText}
};


/*----------------------------------------------------------------------
     Get addressee for message, then post message

  Args:  outgoing -- Partially formatted outgoing ENVELOPE
         body     -- Body of outgoing message

  Result: message "To: " field is provided and message is sent or cancelled.

  Fields:
     remail                -
     return_path           -
     date                 added here
     from                 added here
     sender                -
     reply_to              -
     subject              passed in, NOT edited but maybe cannonized here
     to                   possibly passed in, edited and cannonized here
     cc                    -
     bcc                   -
     in_reply_to           -
     message_id            -
  
Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART
with the first part TYPETEXT! All newlines in the text here also end with
CRLF.
  ----*/
void
pine_simple_send(outgoing, body)
    ENVELOPE  *outgoing;  /* envelope for outgoing message */
    BODY     **body;   
{
    char     **tobufp;
    void      *messagebuf;
    int        done = 0;
    int        cnt, i;
    PINEFIELD *pfields, *pf;
    METAENV    header;
    HelpType   help;

    dprint(1, (debugfile,"\n === simple send called === \n"));

    /* how many fields are there? */
    cnt = (sizeof(pf_template)/sizeof(pf_template[0])) - 1;

    /* temporary PINEFIELD array */
    i = (cnt + 1) * sizeof(PINEFIELD);
    pfields = (PINEFIELD *)fs_get((size_t) i);
    memset(pfields, 0, (size_t) i);

    header.env    = outgoing;
    header.local  = pfields;
    header.custom = NULL;

    /*----- Fill in a few general parts of the envelope ----*/
    if(!outgoing->date){
	rfc822_date(tmp_20k_buf);		/* format and copy new date */
	outgoing->date = cpystr(tmp_20k_buf);
    }

    outgoing->from	  = generate_from();
    outgoing->return_path = rfc822_cpy_adr(outgoing->from);

    /* initialize pfield */
    for(i=0; i < cnt; i++){

        pf = &pfields[i];

        pf->name        = cpystr(pf_template[i].name);
        pf->type        = pf_template[i].type;
        pf->he          = NULL; /* unused */
        pf->next        = pf + 1;

        switch(pf->type){
          case FreeText:
            switch(i){
              case N_SUBJ:
		pf->text = outgoing->subject;
                break;

              case N_NEWS:
		pf->text = outgoing->newsgroups;
                break;

              default:
                q_status_message1(0,1,3,"Internal error: 1)FreeText header %d",
								(void *)i);
                break;
            }
            break;

          case Attachment:
            break;

          case Address:
            switch(i){
              case N_FROM:     pf->addr = &outgoing->from;
                               break;
              case N_TO:       pf->addr = &outgoing->to;
			       tobufp   = &pf->text;
                               break;
              case N_CC:       pf->addr = &outgoing->cc;
                               break;
              case N_BCC:      pf->addr = &outgoing->bcc;
                               break;
              case N_REPLYTO:  pf->addr = &outgoing->reply_to;
                               break;
              default:
                q_status_message1(0,1,3,"Internal error: Address header %d",
								(void *) i);
                break;
            }
            break;

          case Fcc:
            break;

          default:
            q_status_message1(0,1,3,
		"Unknown header type %d in pine_simple_send", (void *)pf->type);
            break;
        }
    }
    pf->next = NULL;

    /*----------------------------------------------------------------------
       Loop editing the "To: " field until everything goes well
     ----*/
    help = NO_HELP;
    while(!done){
	flush_status_messages();

	outgoing2strings(&header, *body, &messagebuf, NULL);

	fs_resize((void **)tobufp, MAXPATH+1);

	switch(optionally_enter(*tobufp, -3, 0, MAXPATH, 1, 0,
				outgoing->remail == NULL 
				    ? "FORWARD (as e-mail) to : "
				    : "BOUNCE (redirect) message to : ",
				NULL, help, 0)){
	  case -1 :
	    q_status_message(0,2,2 ,"\007Internal problem encountered");
	    done++;
	    break;

	  case 0 :
	    if(**tobufp != '\0'){
		char *errbuf, *addr;
		int   tolen;

/*
 * BUG: Not getting the fcc from build_address here because I haven't taken
 * the time to figure out the comment below about fcc.
 * Since the only calls to pine_simple_send are from anonymous and bounce,
 * maybe we don't need fcc here.
 */
		if(build_address(*tobufp, &addr, &errbuf, NULL) >= 0){
		    if(strlen(*tobufp) < (tolen = strlen(addr) + 1))
		      fs_resize((void **)tobufp, (size_t) tolen);

		    strcpy(*tobufp, addr);
		    if(addr)
			fs_give((void **)&addr);
		    strings2outgoing(&header, body, NULL);

		    if(check_addresses(outgoing)) {
			/*--- Addresses didn't check out---*/
			display_message('x');
			sleep(3);
			continue;
		    }

		    if(!call_mailer(&header, *body))
		      continue;
		    /* Add code here to fix FCC. See call_mailer in pine_send*/
		}
		else{
		    q_status_message1(0,2,2,"\007Error in address: %s",
				      errbuf);
		    continue;
		}

	    }
	    else
		q_status_message(0,2,2,"\007No addressee!  No e-mail sent.");
	    done++;
	    break;

	  case 1 :
	    q_status_message(0, 0, 3, "Send cancelled");
	    done++;
	    break;

	  case 2 :				/* no place to escape to */
	  case 3 :
	    help = (help == NO_HELP)
			    ? (outgoing->remail == NULL
				? h_anon_forward
				: h_bounce)
			    : NO_HELP;
	    break;

	  case 4 :				/* can't suspend */
	  default :
	    break;
	}
    }

    for(i=0; i < cnt; i++)
      fs_give((void **)&pfields[i].name);

    fs_give((void **)&pfields);
    display_message('\0');
}


/*----------------------------------------------------------------------
     Prepare data structures for pico, call pico, then post message

  Args:  outgoing     -- Partially formatted outgoing ENVELOPE
         body         -- Body of outgoing message
         editor_title -- Title for anchor line in composer
         fcc          -- The file carbon copy field
	 reply_list   -- List of raw c-client message numbers of
			 we're replying to.  This should be GIDs
			 after IMAP4.
	 ref_list     -- News references list of message id's
	 custom -- custom header list. Passed void * so routines outside
		   send.c don't need to know what PINEFIELD is...

  Result: message is edited, then postponed, cancelled or sent.

  Fields:
     remail                -
     return_path           -
     date                 added here
     from                 added here
     sender                -
     reply_to              -
     subject              passed in, edited and cannonized here
     to                   possibly passed in, edited and cannonized here
     cc                   possibly passed in, edited and cannonized here
     bcc                  edited and cannonized here
     in_reply_to          generated in reply() and passed in
     message_id            -
  
 Storage for these fields comes from anywhere outside. It is remalloced
 here so the composer can realloc them if needed. The copies here are also 
 freed here.

Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART
with the first part TYPETEXT! All newlines in the text here also end with
CRLF.

There's a further assumption that the text in the TYPETEXT part is 
stored in a storage object (see filter.c).
  ----*/
void
pine_send(outgoing, body, editor_title, fcc_arg, reply_list, ref_list, custom,
	  sticky_fcc)
    ENVELOPE  *outgoing;  /* c-client envelope for outgoing message */
    BODY     **body;   
    char      *editor_title;
    char      *fcc_arg;
    long      *reply_list;
    char      *ref_list;
    void      *custom;
    int        sticky_fcc;
{
    int                 editor_result, orig_cols, second_time = 0;
    int                 i, fixed_cnt, total_cnt;
    int                 index;			/* index into header */
    struct headerentry *he, *headents, *he_attch, *he_to, *he_fcc;
    PINEFIELD          *pfields, *pf;
    METAENV             header;
    char               *fcc, *tmp;
#ifdef	DOS
    char               *reserve;
#endif

    dprint(1, (debugfile,"\n=== send called ===\n"));

    /*
     * Cancel any pending initial commands since pico uses a different
     * input routine.  If we didn't cancel them, they would happen after
     * we returned from the editor.
     */
    if (ps_global->in_init_seq) {
	ps_global->in_init_seq = 0;
	ps_global->save_in_init_seq = 0;
        if (ps_global->initial_cmds)
	    *ps_global->initial_cmds = 0;
	F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys);
    }

#ifdef	DOS
    if(!dos_valid_from(1))
      return;
#endif

    pbuf.raw_io        = Raw;
    pbuf.showmsg       = display_message;
    pbuf.clearcur      = clear_cursor_pos;
    pbuf.newmail       = new_mail;
    pbuf.keybinit      = init_keyboard;
    pbuf.helper        = helper;
    pbuf.alt_ed        = ps_global->VAR_EDITOR;
    pbuf.ins_help      = h_composer_ins;
    pbuf.search_help   = h_composer_search;
    pbuf.browse_help   = h_composer_browse;
    pbuf.composer_help = h_composer;
    pbuf.pine_anchor   = set_titlebar(editor_title, ps_global->mail_stream,
				      ps_global->context_current,
				      ps_global->cur_folder,ps_global->msgmap, 
				      0, FolderName, 0, 0),
    pbuf.pine_version  = pine_version;
    pbuf.pine_flags    = 0;
    pbuf.pine_flags   |= (F_ON(F_CAN_SUSPEND,ps_global))   ? P_SUSPEND : 0;
    pbuf.pine_flags   |= (F_ON(F_USE_FK,ps_global))	   ? P_FKEYS : 0;
    pbuf.pine_flags   |= (ps_global->restricted		   ? P_SECURE : 0);
    pbuf.pine_flags   |= F_ON(F_ENABLE_ALT_ED,ps_global)   ? P_ADVANCED : 0;
    pbuf.pine_flags   |= F_ON(F_ALT_ED_NOW,ps_global)	   ? P_ALTNOW : 0;
    pbuf.pine_flags   |= F_ON(F_USE_CURRENT_DIR,ps_global) ? P_CURDIR : 0;
    pbuf.pine_flags   |= ((reply_list && reply_list[0] != -1) || second_time)
			  ? P_BODY : (reply_list) ? P_HEADEND : 0;

    dprint(9, (debugfile, "flags: %x\n", pbuf.pine_flags));

    /*
     * When user runs compose and the current folder is a newsgroup,
     * offer to post to the current newsgroup.
     */
    if(!outgoing->to && !(outgoing->newsgroups && *outgoing->newsgroups)
       && ps_global->mail_stream && ps_global->mail_stream->mailbox
       && ps_global->mail_stream->mailbox[0] == '*'){
	char  prompt[200];
	char  newsgroup_name[MAILTMPLEN];

	newsgroup_name[0] = '\0';

	/* if remote, c-client has a function to parse out mailbox */
	if(ps_global->mail_stream->mailbox[1] == '{')
	    mail_valid_net(ps_global->mail_stream->mailbox,
		ps_global->mail_stream->dtb,
		NULL,
		newsgroup_name);
	else
	    (void)strcpy(newsgroup_name, &(ps_global->mail_stream->mailbox[1]));

	/*
	 * Replies don't get this far because To or Newsgroups will already
	 * be filled in.  So must be either ordinary compose or forward.
	 * Forward sets subject, so use that to tell the difference.
	 */
	if(newsgroup_name[0] && !outgoing->subject){
	    int ch = 'y';

	    if(F_OFF(F_COMPOSE_TO_NEWSGRP,ps_global)){
		sprintf(prompt,
		    "Post to current newsgroup (%s)", newsgroup_name);
		ch = want_to(prompt, 'y', 'x', NO_HELP, 0, 0);
	    }
	    switch(ch){
	      case 'y':
		if(outgoing->newsgroups)
		    fs_give((void **)&(outgoing->newsgroups)); 
		outgoing->newsgroups = cpystr(newsgroup_name);
		break;

	      case 'x': /* ^C */
		q_status_message(0, 0, 3, "Message cancelled");
		dprint(4, (debugfile, "=== send: cancelled\n"));
		return;

	      case 'n':
		break;

	      default:
		break;
	    }
	}
    }

    /* how many fixed fields are there? */
    fixed_cnt = (sizeof(pf_template)/sizeof(pf_template[0])) - 1;

    total_cnt = fixed_cnt + count_custom_hdrs();

    /* the fixed part of the PINEFIELDs */
    i       = fixed_cnt * sizeof(PINEFIELD);
    pfields = (PINEFIELD *)fs_get((size_t) i);
    memset(pfields, 0, (size_t) i);

    /* temporary headerentry array for pico */
    i        = (total_cnt + 1) * sizeof(struct headerentry);
    headents = (struct headerentry *)fs_get((size_t) i);
    memset(headents, 0, (size_t) i);

    pbuf.headents = headents;
    header.env    = outgoing;
    header.local  = pfields;

    /* custom part of PINEFIELDs */
    header.custom = (custom) ? (PINEFIELD *)custom : get_dflt_custom_hdrs();

    he = headents;
    pf = pfields;

    /* initialize the fixed header elements of the two temp arrays */
    for(i=0; i < fixed_cnt; i++, pf++){

	/* different order if sending to news */
	if(outgoing->newsgroups && *outgoing->newsgroups){
	    index = (i == 0) ? N_FROM :
		      (i == 1) ? N_REPLYTO :
		        (i == 2) ? N_NEWS :
			  (i == 3) ? N_TO :
			    (i == 4) ? N_CC :
			      (i == 5) ? N_BCC :
			        (i == 6) ? N_FCC :
				  (i == 7) ? N_ATTCH :
				    (i == 8) ? N_SUBJ : i;
									
	    if(i > 8)
	      q_status_message1(0,1,3,"Internal error: i=%d in pine_send",
				(void *)i);
	}
	else
	  index = i;

	/* copy the template */
	*he             = he_template[index];

	pf->name        = cpystr(pf_template[index].name);
	pf->type        = pf_template[index].type;
        pf->noedit      = 1;  /* only used by from and reply-to */
	pf->he          = he;
	pf->next        = pf + 1;

	he->rich_header = view_as_rich(pf->name, he->rich_header);

	switch(pf->type){
	  case FreeText:   		/* realaddr points to c-client env */
	    if(index == N_SUBJ){
		he->realaddr = &outgoing->subject;
	    }
	    else if(index == N_NEWS){
		he->realaddr = &outgoing->newsgroups;

		/* If there is a newsgrp already, we'd better show them */
		if(outgoing->newsgroups && *outgoing->newsgroups)
		  he->rich_header = 0; /* force on by default */

		if(F_ON(F_NO_NEWS_VALIDATION, ps_global))
		   he->builder = NULL;
	    }
	    else{
		q_status_message(0,1,3,
				 "\007Botched: Unmatched free text header");
		break;
	    }

	    /*
	     * If this field doesn't already have a value, then we want
	     * to check for a default value assigned by the user.  If no
	     * default, give it an alloced empty string.
	     * Also, cap off pf->text so we don't get confused when
	     * it's useless on pico's return.
	     */
	    if(!*he->realaddr){ 		/* no value */
		set_default_hdrval(pf);	/* default in pf->text */
		*he->realaddr = (pf->text) ? pf->text : cpystr("");
		pf->text      = NULL;
	    }

	    break;

	  /* can't do a default for this one */
	  case Attachment:
	    he_attch = he;  /* just a shorthand we use below */
	    /* If there is an attachment already, we'd better show them */
            if(body && *body && (*body)->type != TYPETEXT)
		he->rich_header = 0; /* force on by default */
	    break;

	  case Address:
	    switch(index){
	      case N_FROM:     pf->addr = &outgoing->from;
			       break;
	      case N_TO:       pf->addr = &outgoing->to;
			       he_to = he;
			       break;
	      case N_CC:       pf->addr = &outgoing->cc;
			       break;
	      case N_BCC:      pf->addr = &outgoing->bcc;
			       break;
	      case N_REPLYTO:  pf->addr = &outgoing->reply_to;
			       break;
	      default:
		q_status_message1(0,1,3,"Internal error: Address header %d",
								(void *)index);
		break;
	    }

	    /*
	     * If this is a reply to news, don't show the regular email
	     * recipient headers (unless they are non-empty).
	     */
	    if((outgoing->newsgroups && *outgoing->newsgroups)
	       && (index == N_TO || index == N_CC || index == N_BCC)
	       && (!*pf->addr)){
		he->rich_header = 1; /* hide */

	    /*
	     * If this address doesn't already have a value, then we check
	     * for a default value assigned by the user.
	     */
	    }
	    else if(!*pf->addr
		    || !(*pf->addr)->mailbox
		    || !(*pf->addr)->mailbox[0]){
		char *addr;

		set_default_hdrval(pf);

#ifndef ALLOW_CHANGING_FROM
		if(index != N_FROM){  /* don't set default for From */
#endif
		    if(pf->text && *pf->text){
		        /* strip quotes around whole default */
		        if(*pf->text == '"'){
		          char *p;
		          /* strip trailing whitespace */
		          p = pf->text + strlen(pf->text) - 1;
		          while(isspace(*p) && p > pf->text)
			    p--;
		          /* a set of surrounding quotes we want to strip */
		          if(*p == '"'){
			    *p = '\0';
			    for(p=pf->text; *(p+1); p++)
			      *p = *(p+1);
			    *p = '\0';
		          }
		        }
		        build_address(pf->text, &addr, NULL, NULL);
		        rfc822_parse_adrlist(pf->addr,
			    addr, ps_global->maildomain);
		        fs_give((void **)&addr);
		    }
#ifndef ALLOW_CHANGING_FROM
		}
#endif

		/* if we still don't have a from */
		if(index == N_FROM && !*pf->addr)
		  *pf->addr = generate_from();

	    }else if(index == N_FROM || index == N_REPLYTO){
		/* side effect of this sets noedit */
		set_default_hdrval(pf);
	    }

	    if(!outgoing->return_path)
	      outgoing->return_path = rfc822_cpy_adr(outgoing->from);

	    if(pf->text)		/* free default value in any case */
	      fs_give((void **)&pf->text);

	    /* outgoing2strings will alloc the string pf->text again below */
	    he->realaddr = &pf->text;
	    break;

	  case Fcc:
	    if(fcc_arg){
		fcc = cpystr(fcc_arg);
		if(sticky_fcc)
		    he->sticky = 1;
		if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC) != 0)
		    he->display_it = 1;  /* start this off showing */
	    }
	    else
		fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
	    he->realaddr = &fcc;
	    he_fcc = he;
	    break;

	  default:
	    q_status_message1(0,1,3,"Unknown header type %d in pine_send",
							(void *)pf->type);
	    break;
	}

	/*
	 * We may or may not want to give the user the chance to edit
	 * the From and Reply-To lines.  If they are listed in either
	 * Default-composer-hdrs or Customized-hdrs, then they can edit
	 * them, else no.
	 *
	 * If noedit is set, that means that this header is not in
	 * the user's customized-hdrs.  If rich_header is set, that
	 * means that this header is not in the user's
	 * default-composer-hdrs (since From and Reply-To are rich
	 * by default).  So, don't give it an he to edit with in that case.
	 */
	switch(index){
	  case N_FROM:
/* to allow it, we let this fall through to the reply-to case below */
#ifndef ALLOW_CHANGING_FROM
	    if(!pf->noedit || !he->rich_header)
		q_status_message(0, 1, 2,
		    "Not allowed to change header \"From\"");
	    memset(he, 0, (size_t)sizeof(*he));
	    break;
#endif

	  case N_REPLYTO:
	    if(pf->noedit && he->rich_header)
		memset(he, 0, (size_t)sizeof(*he));
	    else
		he++;
	    break;

	  default:
	    he++;
	    break;
	}
    }

    /*
     * This is so build_address can tell the composer to fill in an fcc
     * value based on the value in the To field.
     */
    he_to->affected_entry = he_fcc;

    (--pf)->next = (total_cnt != fixed_cnt) ? header.custom : NULL;

    /* set up headerentries for custom fields */
    for(pf = pf->next; pf; pf = pf->next, he++){
	char *addr;

	pf->he = he;
	
	switch(pf->type){
	  case Address:
	    *he = he_custom_addr_templ;
	    /* change default text into an ADDRESS */
	    /* strip quotes around whole default */
	    if(*pf->text == '"'){
	      char *p;
	      /* strip trailing whitespace */
	      p = pf->text + strlen(pf->text) - 1;
	      while(isspace(*p) && p > pf->text)
		p--;
	      /* this is a set of surrounding quotes we want to strip */
	      if(*p == '"'){
		*p = '\0';
		for(p=pf->text; *(p+1); p++)
		  *p = *(p+1);
		*p = '\0';
	      }
	    }
	    build_address(pf->text, &addr, NULL, NULL);
	    rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
	    fs_give((void **)&addr);
	    if(pf->text)
		fs_give((void **)&pf->text);
	    he->realaddr = &pf->text;
	    break;

	  case FreeText:
	    *he = he_custom_free_templ;
	    he->realaddr = &pf->text;
	    break;

	  default:
	    q_status_message1(0,1,3,"Unknown custom header type %d",
							(void *)pf->type);
	    break;
	}

	he->name = pf->name;

	/* use first 8 characters for prompt */
	he->prompt = cpystr("        : ");
	strncpy(he->prompt, he->name, min(strlen(he->name), he->prlen - 2));

	he->rich_header = view_as_rich(he->name, he->rich_header);
    }

    /*
     * Make sure at least *one* field is displayable...
     */
    for(index = -1, i = 0, pf = header.local; pf; pf = pf->next, i++)
      if(!pf->he->rich_header)
	index = i;

    /*
     * None displayable!!! Now we've got a problem...
     */
    if(index == -1){
	q_status_message(0,2,3,
		     "No default-composer-hdrs matched, displaying defaults");
	for(i = 0, pf = header.local; pf; pf = pf->next, i++)
	  if(i == N_TO || i == N_CC || i == N_SUBJ || i == N_ATTCH)
	    pf->he->rich_header = 0;
    }

    /*----------------------------------------------------------------------
       Loop calling the editor until everything goes well
     ----*/
    while(1){
        /*
         * Convert the envelope and body to the string format that
         * pico can edit
         */
        outgoing2strings(&header, *body, &pbuf.msgtext, &pbuf.attachments);

	for(pf = header.local; pf; pf = pf->next){
	    /*
	     * If this isn't the first time through this loop, we may have
	     * freed some of the FreeText headers below so that they wouldn't
	     * show up as empty headers in the finished message.  Need to alloc
	     * them again here so they can be edited.
	     */
	    if (pf->type == FreeText && !*pf->he->realaddr)
	      *pf->he->realaddr = cpystr("");

	    if (pf->type != Attachment && *pf->he->realaddr)
	      pf->he->maxlen = strlen(*pf->he->realaddr);
	}

#if	defined(DOS) && !defined(_WINDOWS)
/*
 * dumb hack to help ensure we've got something left
 * to send with if the user runs out of precious memory
 * in the composer...	(FIX THIS!!!)
 */
	if((reserve = (char *)malloc(16384)) == NULL)
	  q_status_message(1, 2, 4,
			   "\007LOW MEMORY!  May be unable to complete send!");
#endif

        flush_status_messages();
        clear_cursor_pos();

	dprint(1, (debugfile, "\n  ---- COMPOSER ----\n"));
	editor_result = pico(&pbuf);
	Raw(1);	/* make shure we are back in raw mode - VMS ~~~~~*/
	dprint(4, (debugfile, "... composer returns (0x%x)\n", editor_result));

#if	defined(DOS) && !defined(_WINDOWS)
	free((char *)reserve);
#endif
	/*
	 * Only reinitialize signals if we didn't receive an interesting
	 * one while in pico, pico's return is part of processing that
	 * signal which should continue to be ignored...
	 */
	if(!(editor_result & COMP_GOTHUP))
	  init_signals();        /* Pico has it's own signal stuff */

        mark_status_dirty();
	orig_cols = ps_global->ttyo->screen_cols;
        get_windsize(ps_global->ttyo); /* Might have changed, we won't know */
	if(orig_cols != ps_global->ttyo->screen_cols)
	  clear_index_cache();

        clear_cursor_pos();

        /* Turn strings back into structures */
        strings2outgoing(&header, body, pbuf.attachments);
  
        /* Make newsgroups NULL if it is "" (so won't show up in headers) */
	if(outgoing->newsgroups){
	    sqzspaces(outgoing->newsgroups);
	    if(!outgoing->newsgroups[0])
	      fs_give((void **)&(outgoing->newsgroups));
	}

        /* Make subject NULL if it is "" (so won't show up in headers) */
        if(!outgoing->subject[0])
          fs_give((void **)&(outgoing->subject)); 
	
	/* remove custom fields that are empty */
	for(pf = header.custom; pf && pf->name; pf = pf->next){
	    if (pf->type == FreeText && pf->text && pf->text[0] == '\0')
	      fs_give((void **)&pf->text); 
	}

        removing_trailing_white_space(fcc);

	/*-------- Stamp it with a current date -------*/
	if(outgoing->date)			/* update old date */
	  fs_give((void **)&(outgoing->date));

	rfc822_date(tmp_20k_buf);		/* format and copy new date */
	outgoing->date = cpystr(tmp_20k_buf);

	/*------- If Reply-To same as From, get rid of it -------*/
	if(outgoing->reply_to
	   && address_is_same(outgoing->from, outgoing->reply_to))
	  mail_free_address(&outgoing->reply_to);

	/*
	 * Don't ever believe the sender that is there.
	 * If From doesn't look quite right, generate our own sender.
	 */
	if(outgoing->sender)
	    mail_free_address(&outgoing->sender);
	/*
	 * If the LHS of the address doesn't match, or the RHS
	 * doesn't match one of localdomain or hostname,
	 * then add a sender line (really X-Sender).
	 *
	 * Don't add a personal_name since the user can change that.
	 */
	if(!outgoing->from->mailbox ||
	    strucmp(outgoing->from->mailbox, ps_global->VAR_USER_ID) != 0 ||
	   !outgoing->from->host ||
	    !(strucmp(outgoing->from->host, ps_global->localdomain) == 0 ||
	      strucmp(outgoing->from->host, ps_global->hostname) == 0)){

	    outgoing->sender	      = mail_newaddr();
	    outgoing->sender->mailbox = cpystr(ps_global->VAR_USER_ID);
	    outgoing->sender->host    = cpystr(ps_global->hostname);
	}

        /*----- Message is edited, now decide what to do with it ----*/
        if(editor_result & COMP_CANCEL) {
            /*================ Cancel the message ===================*/
            q_status_message(0, 0, 3, "Message cancelled");
	    dprint(4, (debugfile, "pine_send: message cancelled.\n"));
            break;

        } else if(editor_result & (COMP_SUSPEND | COMP_GOTHUP)) {
            /*=========== Postpone or Interrupted message ============*/
	    CONTEXT_S *fcc_cntxt = NULL;
	    char       folder[MAXPATH+1];
	    int	       fcc_result = 1;
	    PINEFIELD *qf;

	    dprint(4, (debugfile, "pine_send:%s handling\n",
		       (editor_result & COMP_SUSPEND) ? "SUSPEND" : "HUP"));

	    /*
	     * The idea here is to use the Fcc: writing facility
	     * to append the special postponed message folder...
	     *
	     * NOTE: the strategy now is to write the message and
	     * all attachments as they exist at composition time.
	     * In other words, attachments are postponed by value
	     * and not reference.  This may change later, but we'll
	     * need a local "message/external-body" type that
	     * outgoing2strings knows how to properly set up for
	     * the composer.  Maybe later...
	     */

	    if(F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global) &&
	       (editor_result & (COMP_SUSPEND)) &&
	       check_addresses(outgoing)){
               /*--- Addresses didn't check out---*/
               q_status_message(1, 7, 7,
	       "Not allowed to postpone message until addresses are qualified");
               continue;
            }

	    fcc_written = 0;
	    fcc_so      = NULL;

	    if(editor_result & COMP_GOTHUP){
		build_path(folder, ps_global->home_dir, INTERRUPTED_MAIL);
	    }
	    else if(!ps_global->VAR_POSTPONED_FOLDER
		    || !ps_global->VAR_POSTPONED_FOLDER[0]){
                q_status_message(1, 3, 3, "No postponed file defined");
		continue;
	    }
	    else
	      strcpy(folder, ps_global->VAR_POSTPONED_FOLDER);

	    /*
	     * add the fcc to the pre-allocated pfields slot
	     */
	    pf = &header.custom[total_cnt-fixed_cnt];
	    pf->name = cpystr("Fcc");
	    pf->type = FreeText;
	    pf->text = cpystr(fcc ? fcc : "");
	    pf->next = NULL;

	    /* add to end of PINEFIELD list */
	    for(qf = header.local; qf->next; qf = qf->next)
	      ;					/* do nothing */

	    qf->next = pf;

	    /*
	     * We need to make sure any header values that got cleared
	     * get written to the postponed message (they won't if
	     * pf->text is NULL).  Otherwise, we can't tell previously
	     * non-existent custom headers or default values from 
	     * custom (or other) headers that got blanked in the
	     * composer...
	     */
	    for(pf = header.local; pf; pf = pf->next)
	      if (pf->type == FreeText && pf->he && !*(pf->he->realaddr))
		*(pf->he->realaddr) = cpystr("");

	    if(folder[0]
	       && open_fcc(folder, &fcc_cntxt, 1) > 0
	       && (fcc_so = so_get(FCC_SOURCE, NULL, WRITE_ACCESS))
	       && pine_rfc822_output(&header, *body, NULL, NULL) >= 0)
	      fcc_result = write_fcc(folder, fcc_cntxt, fcc_so,
				     (editor_result & COMP_SUSPEND)
					? "postponed message" : NULL);
	    else
	      fcc_result = 0;			/* Open of fcc failed! */

	    if(fcc_so)
	      so_give(&fcc_so);

	    if(editor_result & COMP_GOTHUP){
		dprint(1, (debugfile, "Save composition on HUP %sED\n",
			   fcc_result ? "SUCCEED" : "FAIL"));
		hup_signal();		/* Do what we normally do on SIGHUP */
	    }
	    else if(fcc_result) {
                q_status_message(1, 1, 3,
                          "Composition postponed. Select Compose to resume.");
                break; /* postpone went OK, get out of here */
            }
	    else {
		PINEFIELD *qf;
		/* clean up alloc'd fcc field */
		pf = &header.custom[total_cnt-fixed_cnt];
		fs_give((void **)&pf->name);
		fs_give((void **)&pf->text);
		/* delete from end of PINEFIELD list */
		for(qf = header.local; qf && qf->next != pf; qf = qf->next)
		    ;/* do nothing */

		if(qf)
		  qf->next = pf->next;

                q_status_message(1, 3, 3,
		  "\007Continuing composition; message not postponed or sent");
		second_time++;
                continue; /* postpone failed, jump back in to composer */
            }
        }
	else{
            /*------ Must be sending mail or posting ! -----*/
	    int news_result, mail_result, fcc_result;
	    CONTEXT_S *fcc_cntxt = NULL;

            mail_result = news_result = fcc_result = 0;
	    dprint(4, (debugfile, "=== sending: "));

            /* --- If posting, confirm with user ----*/
            if(outgoing->newsgroups && *outgoing->newsgroups
	       && want_to(POST_PMT, 'n', 'n', NO_HELP, 0, 0) == 'n') {
                  q_status_message(0, 3, 3, "Message not posted");
		  dprint(4, (debugfile, "no post, continuing\n"));
                  continue;
            }

	    if(!outgoing->to && !outgoing->cc && !outgoing->bcc &&
	       !outgoing->newsgroups && !(fcc && fcc[0] != '\0')){
               q_status_message(1, 3, 4, "No recipients specified!");
	       dprint(4, (debugfile, "no recip, continuing\n"));
               continue;
	    }
	       
            if(check_addresses(outgoing)) {
                /*--- Addresses didn't check out---*/
	       dprint(4, (debugfile, "addrs failed, continuing\n"));
               continue;
            }

            /*---- Check out fcc -----*/
            if(fcc && *fcc) {
	        if(open_fcc(fcc, &fcc_cntxt, 0) < 0){
		    /* ---- Open of fcc failed ----- */
		    dprint(4, (debugfile, "fcc open failed, continuing\n"));
		    continue;
		}

                if((fcc_so = so_get(FCC_SOURCE, NULL, WRITE_ACCESS)) == NULL){
                    q_status_message(1, 3, 3,
				   "\007Problem creating space for Fcc.");
		    dprint(4, (debugfile, "can't get fcc_so, continuing\n"));
		    continue;
		}

		fcc_written = 0;
            }
	    else
	      fcc_so = NULL;

            /*---- recompute message-id to encode body info stats ----*/
            update_message_id(outgoing, mime_stats(*body));

            /*------ Actually post  -------*/
            if(outgoing->newsgroups){
		PINEFIELD *qf;

		/*-- Set up References field in prealloc'd slot --*/
		if(ref_list){
		    pf = &header.custom[total_cnt-fixed_cnt];
		    pf->name = cpystr("References");
		    pf->type = FreeText;
		    pf->text = cpystr(ref_list);
		    pf->next = NULL;

		    /* add to end of PINEFIELD list */
		    for(qf = header.local; qf->next; qf = qf->next)
		      ;					/* do nothing */

		    qf->next = pf;
		}

		news_result = news_poster(&header, *body); /* try posting */

		/*--- NULL out References --*/
		if(ref_list){
		    pf = &header.custom[total_cnt-fixed_cnt];
		    fs_give((void **)&pf->name);
		    fs_give((void **)&pf->text);
		    /* delete from end of PINEFIELD list */
		    for(qf = header.local; qf && qf->next != pf; qf = qf->next)
		      ;				/* do nothing */

		    if(qf)
		      qf->next = pf->next;
		}

		if(news_result < 0){
		    dprint(1, (debugfile, "Post failed, continuing\n"));
		    continue;
		}
	    }

	    /*
	     * BUG: IF we've posted the message *and* an fcc was specified
	     * then we've already got a neatly formatted message in the
	     * fcc_so.  It'd be nice not to have to re-encode everything
	     * to insert it into the smtp slot...
	     */

            /*------- Actually mail ------*/
            if(outgoing->to || outgoing->cc || outgoing ->bcc)
	      mail_result = call_mailer(&header, *body);

            /*------ Write FCC if at least one thing worked ------*/
            if(fcc_so && (mail_result == 1 || news_result == 1
			  || (!outgoing->to && !outgoing->cc 
			      && !outgoing->bcc && !outgoing->newsgroups))){
		char label[500];

	        /* Either posted, sent or no recipients */
	        if(mail_result == 0 && news_result == 0) {
		    /* No recipients, so we must manually output */
	            if(pine_rfc822_output(&header, *body, NULL, NULL) < 1)
	              q_status_message(0,2,3,
				       "Fcc Failed!.  No message saved.");
		}

		if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC) != 0)
		    sprintf(label, "Fcc to %s", fcc);
		else
		    sprintf(label, "Fcc");

		/*-- Now actually copy to fcc folder and close --*/
                fcc_result = write_fcc(fcc, fcc_cntxt, fcc_so, label);
	    }

	    if(fcc_so)
	      so_give(&fcc_so);

            /*----- Both weren't OK, back to composer -----*/
            if(mail_result < 0 && news_result != 1){
		dprint(1, (debugfile, "Send and Post failed, continuing\n"));
		continue;
	    }

            /*----- Signed, sealed, delivered! ------*/
            q_status_message7(0, mail_result < 0 ? 2 : 1, 3,
                              "Message %s%s%s%s%s%s%s.",
                              (news_result == 1)
			        ? "posted" 
			        : (news_result == -1)
				    ? "NOT posted" : "",
			      (news_result && mail_result && fcc_result)
			        ? ", "
			        : (news_result && (mail_result || fcc_result))
			            ? " and " : "",
                              (mail_result == 1)
			        ? "sent"
			        :  (mail_result == -1)
			             ? "\007NOT SENT" : "",
			      (mail_result && fcc_result)
			        ? " and copied to " 
			        : (fcc_result) ? "copied to " : "",
                              (fcc_result) ? "\"" : "",
                              (fcc_result) ? fcc  : "",
                              (fcc_result) ? "\"" : "");
	    /*
	     * If message *completely* successfully sent, there's a
	     * reply_list AND we're allowed to write back state, do it.
	     * But also protect against shifted message numbers due 
	     * to new mail arrival.  Since the number passed is based
	     * on the real imap msg no, AND we're sure no expunge has 
	     * been done, just fix up the sorted number...
	     */
	    if(reply_list && reply_list[0] > -1 && !READONLY_FOLDER){
		char *seq, *p;
		long  i, j;

		for(i = 0L, p = tmp_20k_buf; reply_list[i] != -1L; i++){
		    if(i)
		      sstrcpy(&p, ",");

		    sstrcpy(&p, long2string(reply_list[i]));
		    if(j = mn_raw2m(ps_global->msgmap, reply_list[i]))
		      clear_index_cache_ent(j);
		}

		seq = cpystr(tmp_20k_buf);
		mail_setflag(ps_global->mail_stream, seq, "\\ANSWERED");
		fs_give((void **)&seq);
		check_point_change();
	    }

            if(mail_result < 0){
		/* Send wasn't OK, back to the composer */
		dprint(1, (debugfile, "Send failed, continuing\n"));
		continue;
	    }

            break; /* All's well, pop out of here */

            /*--- Send failed, loop on back ----*/
        }

    }

    if(fcc)
      fs_give((void **)&fcc);

    free_attachment_list(&pbuf.attachments);
    for(i=0; i < fixed_cnt; i++)
      fs_give((void **)&pfields[i].name);

    free_customs(header.custom);
    fs_give((void **)&pfields);
    fs_give((void **)&headents);
    display_message('\0');
    dprint(4, (debugfile, "=== send returning ===\n"));
}



/*----------------------------------------------------------------------
   Check for addresses the user is not permitted to send to, or probably
   doesn't want to send to
   
Returns: 0 if OK, and 1 if the message shouldn't be sent

Alls queues a message indicating what happened
  ---*/
check_addresses(e)
    ENVELOPE *e;
{
    ADDRESS *a;

    /*---- Restricted mode can only send mail back -----*/
    if(ps_global->restricted){
        if(!address_is_us(e->to, ps_global) ||
          (e->cc != NULL &&
           !address_is_us(e->cc,ps_global)) ||
          (e->bcc != NULL &&
           !address_is_us(e->bcc,ps_global))){
            q_status_message(1, 2, 4,
	 "Restricted demo version of Pine. You may only send mail to yourself");
            return(1);
        }
    }

    /*
     * compose-rejects-unqualified-addrs is set and an unqualified
     * address wasn't in addrbooks
     */
    if(F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)){
	for(a = e->to; a != NULL; a = a->next) 
	    if(a->host && a->host[0] == '@')
		goto not_in_addrbook;
	for(a = e->cc; a != NULL; a = a->next) 
	    if(a->host && a->host[0] == '@')
		goto not_in_addrbook;
	for(a = e->bcc; a != NULL; a = a->next) 
	    if(a->host && a->host[0] == '@')
		goto not_in_addrbook;
    }

    /*---- Is he/she trying to send mail to the mailer-daemon ----*/
    for(a = e->to; a != NULL; a = a->next) 
      if(strucmp(a->mailbox, "mailer-daemon") == 0)
        goto really_send;
    for(a = e->cc; a != NULL; a = a->next) 
      if(strucmp(a->mailbox, "mailer-daemon") == 0)
        goto really_send;
    for(a = e->bcc; a != NULL; a = a->next) 
      if(strucmp(a->mailbox, "mailer-daemon") == 0)
          goto really_send;

    return(0); /* All is OK */

  really_send:
    if(want_to("Really send this message to the MAILER-DAEMON", 'n', 'n',
						       NO_HELP, 0, 0) == 'n')
      return(1);
    else
      return(0);

  not_in_addrbook:
    q_status_message1(1, 4, 4,
	"Can't send to address %s, it's not in addressbook", a->mailbox);
    return(1);
}


/*----------------------------------------------------------------------
    Validate the given subject relative to any news groups.
     
Args: none

Returns: always returns 1, but also returns error if
----*/      
int
valid_subject(given, expanded, error, fcc)
    char  *given,
         **expanded,
         **error,
         **fcc;
{
    struct headerentry *hp;

    if(expanded)
      *expanded = cpystr(given);

    if(error){
	/*
	 * Now look for any header entry we passed to pico that has to do
	 * with news.  If there's no subject, gripe.
	 */
	for(hp = pbuf.headents; hp->prompt; hp++)
	  if(hp->help == h_composer_news){
	      if(hp->hd_text->text[0] && !*given)
		*error = cpystr(
			"News postings MUST have a subject!  Please add one!");

	      break;
	  }
    }

    return(0);
}


/*----------------------------------------------------------------------
     Generate and send a message back to the pine development team
     
Args: none

Returns: none
----*/      
void
phone_home()
{
    char	 tmp[MAX_ADDRESS], *from_addr;
    METAENV	 header;
    ENVELOPE	*outgoing;
    BODY	*body;

#ifdef	DOS
    if(!dos_valid_from(0))
      return;
#endif

    header.env	  = outgoing = mail_newenvelope();
    header.local  = NULL;
    header.custom = NULL;

    sprintf(tmp, "pine%s@%s", PHONE_HOME_VERSION, PHONE_HOME_HOST);
    rfc822_parse_adrlist(&outgoing->to, tmp, ps_global->maildomain);
    outgoing->from	  = generate_from();
    outgoing->return_path = rfc822_cpy_adr(outgoing->from);
    rfc822_date(tmp_20k_buf);
    outgoing->date	  = cpystr(tmp_20k_buf);
    outgoing->message_id  = generate_message_id(ps_global);
    outgoing->subject	  = cpystr("Document Request");

    body       = mail_newbody();
    body->type = TYPETEXT;

    if(body->contents.binary = (void *)so_get(PicoText,NULL,EDIT_ACCESS)){
	so_puts((STORE_S *)body->contents.binary, "Document request: Pine");
	so_puts((STORE_S *)body->contents.binary, PHONE_HOME_VERSION);
	if(ps_global->first_time_user)
	  so_puts((STORE_S *)body->contents.binary, " for New Users");

	if(ps_global->VAR_INBOX_PATH && ps_global->VAR_INBOX_PATH[0] == '{')
	  so_puts((STORE_S *)body->contents.binary, " and IMAP");

	if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
	      && ps_global->VAR_NNTP_SERVER[0][0])
	  so_puts((STORE_S *)body->contents.binary, " and NNTP");

	q_status_message2(0, 2, 3,"%sequest sent from \"%s\"",
			  (call_mailer(&header, body) == 1) ? "R" : "No r",
			  from_addr = addr_list_string(outgoing->from));
	fs_give((void **)&from_addr);
    }
    else
      q_status_message(0,2,4,"\007Problem creating space for message text.");

    mail_free_envelope(&outgoing);
    pine_free_body(&body);

}


/*----------------------------------------------------------------------
     Call the mailer, SMTP, sendmail or whatever
     
Args: fcc      -- The fcc to write a copy of the message to
      envelope -- The full envelope structure
      body     -- The full body of the message including text
      header   -- a text version of the header LF format
      text     -- Text of the message if text only, LF format

Returns: -1 if failed, 1 if succeeded
----*/      
int
call_mailer(header, body)
    METAENV *header;
    BODY    *body;
{
    char         error_buf[100], *error_mess = NULL;
    ADDRESS     *a;
    int          addr_error_count;

#define MAX_ADDR_ERROR 2  /* Only display 2 address errors */

    dprint(4, (debugfile, "Sending mail...\n"));
    q_status_message(0, 0, 3, "Sending mail.....");
    flush_status_messages();

    /*----- dump message off to the mailer ----*/
    if(header->env->to || header->env->cc || header->env->bcc){
 	if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
	   && ps_global->VAR_SMTP_SERVER[0][0]){
	    /*---------- SMTP ----------*/

	    ps_global->noshow_error = 1;
#ifdef DEBUG
	    sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, debug);
#else
	    sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, 0L);
#endif
	    ps_global->noshow_error = 0;
	    if(sending_stream) {
		dprint(1, (debugfile, "Got SMTP server %s open\n",
			   tcp_host(sending_stream->tcpstream)));
		if(pine_smtp_mail(sending_stream, "MAIL", header, body) != 1) {
		    sprintf(error_buf,
			   "\007Mail not sent. Transfer protocol error: %.40s",
			    sending_stream->reply == NULL ? "" :
			    sending_stream->reply);
		    dprint(1, (debugfile, error_buf));
		    addr_error_count = 0;
		    for(a = header->env->to; a != NULL; a = a->next){
			if(a->error != NULL) {
			    if(addr_error_count++ < MAX_ADDR_ERROR) {
				if(error_mess)
				  q_status_message(1, 4, 7, error_mess);

				error_mess = tidy_smtp_mess(a->error,
						    "\007Mail not sent: %.60s",
						    error_buf);
			    }

			    dprint(1,(debugfile,"Send Error: \"%s\"\n",
				      a->error));
			}
		    }

		    for(a = header->env->cc; a != NULL; a = a->next){
			if(a->error != NULL) {
			    if(addr_error_count++ < MAX_ADDR_ERROR) {
				if(error_mess)
				  q_status_message(1, 4, 7, error_mess);

				error_mess = tidy_smtp_mess(a->error,
						    "\007Mail not sent: %.60s",
						    error_buf);
			    }

			    dprint(1,(debugfile,"Send Error: \"%s\"\n",
				      a->error));
			}
		    }

		    for(a = header->env->bcc; a != NULL; a = a->next){
			if(a->error != NULL) {
			    if(addr_error_count++ < MAX_ADDR_ERROR) {
				if(error_mess)
				  q_status_message(1, 4, 7, error_mess);

				error_mess = tidy_smtp_mess(a->error,
						    "\007Mail not sent: %.60s",
						    error_buf);
			    }

			    dprint(1,(debugfile,"Send Error: \"%s\"\n",
				      a->error));
			}
		    }

		    if(!error_mess)
		      error_mess = error_buf;
		} 

		smtp_close(sending_stream);
		sending_stream = NULL;
	    }
	    else {
		sprintf(error_buf,"\007Error connecting to mail server: %.60s",
			ps_global->c_client_error);
		error_mess = error_buf;
	    }
	} 
	else {
	    /*----- Send via local mechanism ------*/
	    dprint(4, (debugfile, "call_mailer: handing off\n"));
	    error_mess = send_handoff(header, body, error_buf);
	}
    }
    else {				/* shouldn't happen */
	q_status_message(0,2,3,"Can't send message. No recipients specified!");
	return(0);
    }

    /*-------- Did message make it ? ----------*/
    if(error_mess) {
        /*---- Error sending mail -----*/
	if(fcc_so && !fcc_written)
	  so_give(&fcc_so);

        q_status_message(1, 4, 7, error_mess);
	dprint(1, (debugfile, "call_mailer ERROR: %s\n", error_mess));
	return(-1);
    } else {
	fcc_written = 1;
	return(1);
    }
}


/*----------------------------------------------------------------------
    Checks to make sure the fcc is available and can be opened

Args: fcc -- the name of the fcc to create.  It can't be NULL.
      fcc_cntxt -- Returns the context the fcc is in.
      force -- supress user option prompt

Returns -1 on failure, 1 on success
  ----*/
open_fcc(fcc, fcc_cntxt, force)
     char       *fcc;
     CONTEXT_S **fcc_cntxt;
     int 	 force;
{
    MAILSTREAM *create_stream;

    *fcc_cntxt = NULL;

    /* 
     * check for fcc's existance...
     */
    if(context_isambig(fcc)){
        int  ok = 1;

	/*
	 * 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 = default_save_context(ps_global->context_list)))
	  *fcc_cntxt = ps_global->context_list;

        find_folders_in_context(*fcc_cntxt, fcc);
        if(folder_index(fcc, (*fcc_cntxt)->folders) < 0){
	    if(ps_global->context_list->next){
		sprintf(tmp_20k_buf,
			"Folder \"%.20s\" in <%.30s> doesn't exist. Create",
			fcc, (*fcc_cntxt)->label[0]);
	    }
	    else
	      sprintf(tmp_20k_buf,"Folder \"%.40s\" doesn't exist.  Create",
		      fcc);

	    if(!force && want_to(tmp_20k_buf, 'y', 'n', NO_HELP, 0, 0) != 'y'){
		q_status_message(0, 1, 3, "\007Fcc of message rejected");
		ok = 0;
	    }
	    else if(!context_create((*fcc_cntxt)->context,
				    (*fcc_cntxt)->proto, fcc))
	      ok = 0;
        }

        free_folders_in_context(*fcc_cntxt);
        if(!ok)
          return(-1);
    }
    else if(!folder_exists(NULL, fcc)){
        sprintf(tmp_20k_buf,"Folder \"%.40s\" doesn't exist.  Create",fcc);

        if(!force && want_to(tmp_20k_buf, 'y', 'n', NO_HELP, 0, 0) != 'y'){
	    q_status_message(0, 1, 3, "\007Fcc of message rejected");
	    return(-1);
	}

	if((create_stream = default_driver(fcc)) == NULL){
	    /*
	     * See if an already open stream will service the create
	     */
	    create_stream = same_stream(fcc, ps_global->mail_stream);
	    if(!create_stream
	       && ps_global->mail_stream != ps_global->inbox_stream)
	      create_stream = same_stream(fcc, ps_global->inbox_stream);
	}

        if(!mail_create(create_stream, fcc))
          return(-1);
    }

    return(1);
}


/*----------------------------------------------------------------------
   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(fcc, fcc_cntxt, tmp_storage, label)
     char      *fcc;
     CONTEXT_S *fcc_cntxt;
     STORE_S   *tmp_storage;
     char      *label;
{
    STRING      msg;
    MAILSTREAM *fcc_stream;
    char       *cntxt_string;
#ifdef	DOS
    struct {			/* hack! stolen from dawz.c */
	int fd;
	unsigned long pos;
    } d;
    extern STRINGDRIVER dawz_string;
#endif

    if(!tmp_storage)
      return(0);

    dprint(4, (debugfile, "Writing %s\n", label ? label : ""));
    if(label){
	q_status_message1(0, 1, 3, "Writing %s...", label);
	flush_status_messages();
    }

    so_seek(tmp_storage, 0L, 0);
#ifdef	DOS
    d.fd  = fileno((FILE *)so_text(tmp_storage));
    d.pos = 0L;
    INIT(&msg, dawz_string, (void *)&d, filelength(d.fd));
#else
    INIT(&msg, mail_string, (void *)so_text(tmp_storage), 
	     strlen((char *)so_text(tmp_storage)));
#endif

    cntxt_string = fcc_cntxt ? fcc_cntxt->context : "[]";
    fcc_stream   = context_same_stream(cntxt_string, fcc,
				       ps_global->mail_stream);

    if(!fcc_stream && ps_global->mail_stream != ps_global->inbox_stream)
      fcc_stream = context_same_stream(cntxt_string, fcc,
				       ps_global->inbox_stream);

    if(!context_append(cntxt_string, fcc_stream, fcc, &msg)){
	q_status_message1(0, 3, 3, "\007Write to \"%s\" FAILED!!!", fcc);
	dprint(1, (debugfile, "ERROR appending %s in \"%s\"",
		   fcc, cntxt_string));
	return(0);
    }

    dprint(4, (debugfile, "done.\n"));
    return(1);
}

  




/*----------------------------------------------------------------------
    Remove the leading digits from SMTP error messages
 -----*/
char *
tidy_smtp_mess(error, printstring, outbuf)
    char *error, *printstring, *outbuf;
{
    while(isdigit(*error) || isspace(*error))
      error++;

    sprintf(outbuf, printstring, error);
    return(outbuf);
}

        
    
/*----------------------------------------------------------------------
    Set up fields for passing to pico.  Assumes first text part is
    intended to be passed along for editing, and is in the form of
    of a storage object brought into existence sometime before pico_send().
 -----*/
void
outgoing2strings(header, bod, text, pico_a)
    METAENV   *header;
    BODY      *bod;
    void     **text;
    PATMT    **pico_a;
{
    PART      *part;
    PATMT     *pa;
    char      *type;
    PINEFIELD *pf;
    PARAMETER *parms;

    /*
     * SIMPLIFYING ASSUMPTION #37: the first TEXT part's storage object
     * is guaranteed to be of type PicoText!
     */
    if(bod->type == TYPETEXT){
	*text = so_text(bod->contents.binary);
    } else if(bod->type == TYPEMULTIPART){
	/*
	 * We used to jump out the window if the first part wasn't text,
	 * but that may not be the case when bouncing a message with
	 * a leading non-text segment.  So, IT'S UNDERSTOOD that the 
	 * contents of the first part to send is still ALWAYS in a 
	 * PicoText storage object, *AND* if that object doesn't contain
	 * data of type text, then it must contain THE ENCODED NON-TEXT
	 * DATA of the piece being sent.
	 *
	 * It's up to the programmer to make sure that such a message is
	 * sent via pine_simple_send and never get to the composer via
	 * pine_send.
	 *
	 * Make sense?
	 */
	*text = so_text(bod->contents.part->body.contents.binary);

	/*
	 * If we already had a list, blast it now, so we can build a new
	 * attachment list that reflects what's really there...
	 */
	if(pico_a)
	  free_attachment_list(pico_a);


        /* Simplifyihg assumption #28e. (see cross reference) 
           All parts in the body passed in here that are not already
           in the attachments list are added to the end of the attachments
           list. Attachment items not in the body list will be taken care
           of in strings2outgoing, but they are unlikey to occur
         */

        for(part = bod->contents.part->next; part != NULL; part = part->next) {
            for(pa = *pico_a; pa != NULL; pa = pa->next) {
                if(strcmp(pa->id, part->body.id) == 0)
                  break;  /* Already in list */
            }
            if(pa != NULL) /* Was in the list, don't worry 'bout this part */
              continue;

            /* to end of list */
            for(pa = *pico_a;  pa!= NULL && pa->next != NULL;  pa = pa->next);
            /* empty list or no? */
            if(pa == NULL) {
                /* empty list */
                *pico_a = (PATMT *)fs_get(sizeof(PATMT));
                pa = *pico_a;
            } else {
                pa->next = (PATMT *)fs_get(sizeof(PATMT));
                pa = pa->next;
            }
            pa->description = part->body.description == NULL ? cpystr("") : 
                                              cpystr(part->body.description);
            
            type = type_desc(part->body.type,
			     part->body.subtype,part->body.parameter,0);

	    /*
	     * If we can find a "name" parm, display that too...
	     */
	    for(parms = part->body.parameter; parms; parms = parms->next)
	      if(!strucmp(parms->attribute, "name") && parms->value)
		break;

            pa->filename = fs_get(strlen(type)
				  + (parms ? strlen(parms->value) : 0) + 5);

            sprintf(pa->filename, "[%s%s%s]", type,
		    parms ? ": " : "", 
		    parms ? parms->value : "");
            pa->flags    = A_FLIT;
            pa->size     = cpystr(byte_string(part->body.size.bytes));
            if(part->body.id == NULL)
              part->body.id = generate_message_id(ps_global);
            pa->id       = cpystr(part->body.id);
            pa->next     = NULL;
        }
    }
        

    /*------------------------------------------------------------------
       Malloc strings to pass to composer editor because it expects
       such strings so it can realloc them
      -----------------------------------------------------------------*/
    /*
     * turn any address fields into text strings
     */
    for(pf = header->local; pf; pf = pf->next)
      if(pf->type == Address && pf->addr)
	pf->text = addr_list_string(*pf->addr);
}


/*----------------------------------------------------------------------
    Restore fields returned from pico to form useful to sending
    routines.
 -----*/
void
strings2outgoing(header, bod, attach)
    METAENV  *header;
    BODY    **bod;
    PATMT    *attach;
{
    PINEFIELD *pf;

    /*
     * turn any local address strings into address lists
     */
    for(pf = header->local; pf; pf = pf->next){
	if(pf->type == Address && pf->text){
	    char  fakedomain[2];
	    char *parse_domain;

	    mail_free_address(pf->addr);	/* free old addrs */
	    *pf->addr = NULL;			/* make sure it's dead */
	    removing_trailing_white_space(pf->text);
	    if(F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)){
		fakedomain[0] = '@';
		fakedomain[1] = '\0';
		parse_domain = fakedomain;
	    }
	    else
		parse_domain = ps_global->maildomain;
	    rfc822_parse_adrlist(pf->addr, pf->text, parse_domain);
	    fs_give((void **)&pf->text);	/* free now useless text */
	}
    }

    create_message_body(bod, attach);
    pine_encode_body(*bod);
}


/*----------------------------------------------------------------------

 The head of the body list here is always either TEXT or MULTIPART. It may be
changed from TEXT to MULTIPART if there are attachments to be added
and it is not already multipart. 
  ----*/
void
create_message_body(b, attach)
    BODY  **b;
    PATMT  *attach;
{
    PART         *p, *p_trail;
    PATMT        *pa;
    BODY         *b1;
    void         *file_contents;
    PARAMETER    *pm;

    if((*b)->type != TYPEMULTIPART && !attach){
	/* only override assigned encoding if it might need upgrading */
	if((*b)->type == TYPETEXT && (*b)->encoding == ENC7BIT)
	  (*b)->encoding = ENCOTHER;

	set_mime_types(*b);
        return;
    }

    if((*b)->type == TYPETEXT) {
        /*-- Current type is text, but there are attachments to add --*/
        /*-- Upgrade to a TYPEMULTIPART --*/
        b1                                  = (BODY *)mail_newbody();
        b1->type                            = TYPEMULTIPART;
        b1->contents.part                   = mail_newbody_part();
        b1->contents.part->body             = **b;

        (*b)->subtype = (*b)->id = (*b)->description = NULL;
	(*b)->parameter = NULL;
	(*b)->contents.binary               = NULL;
	pine_free_body(b);
        *b = b1;
    }

    /*-- Now type must be MULTIPART with first part text --*/
    (*b)->contents.part->body.encoding = ENCOTHER;
    set_mime_types(&((*b)->contents.part->body));

    /*------ Go through the parts list remove those to be deleted -----*/
    for(p = p_trail = (*b)->contents.part->next; p != NULL;) {
        for(pa = attach; pa != NULL; pa = pa->next) {
            if(pa->id == NULL)
              continue;
            if(p->body.id == NULL || strcmp(pa->id, p->body.id) == 0)
              break; /* Found it */
        }
        if(pa == NULL) {
            /* attachment wasn't in the list; zap it */
            if(p == (*b)->contents.part->next) {
                /* Beginning of list */
                (*b)->contents.part->next = p->next;
                p->next = NULL;  /* Don't free the whole chain */
                pine_free_body_part(&p);
                p = p_trail = (*b)->contents.part->next;
            } else {
                p_trail->next = p->next;
                p->next = NULL;  /* Don't free the whole chain */
                pine_free_body_part(&p);
                p = p_trail->next;
            }
        } else {
            p_trail = p;
            p       = p->next;
        }
    }

    /*---------- Now add any new attachments ---------*/
    for(p = (*b)->contents.part ; p->next != NULL; p = p->next);
    for(pa = attach; pa != NULL; pa = pa->next) {
        if(pa->id != NULL)
          continue; /* Has an ID, it's old */
	/*
	 * the idea is handle ALL attachments as open FILE *'s.  Actual
         * encoding and such is handled at the time the message
         * is shoved into the mail slot or written to disk...
	 *
         * Also, we never unlink a file, so it's up to whoever opens
         * it to deal with tmpfile issues.
	 */
	if((file_contents = (void *)so_get(FileStar, pa->filename,
					   READ_ACCESS)) == NULL){
            q_status_message2(1, 3, 4,
                              "\007Error \"%s\", couldn't attach file \"%s\"",
                              error_description(errno), pa->filename);
            display_message('x');
            continue;
        }
        
        p->next                      = mail_newbody_part();
        p                            = p->next;
        p->body.id                   = generate_message_id(ps_global);
        p->body.contents.binary      = file_contents;
	/*
	 * Set type to unknown and let set_mime_types figure it out.
	 * Always encode attachments we add as BINARY.
	 */
	p->body.type		     = TYPEOTHER;
	p->body.encoding	     = ENCBINARY;
	p->body.size.bytes           = name_file_size(pa->filename);
	set_mime_types(&(p->body));
	so_release((STORE_S *)p->body.contents.binary);
        p->body.description          = cpystr(pa->description);
	/* add name attribute */
	if (p->body.parameter == NULL) {
	    pm = p->body.parameter = mail_newbody_parameter();
	    pm->attribute = cpystr("name");
	}else {
            for (pm = p->body.parameter;
			strucmp(pm->attribute, "name") && pm->next != NULL;
								pm = pm->next);
	    if (strucmp(pm->attribute, "name") != 0) {
		pm->next = mail_newbody_parameter();
		pm = pm->next;
		pm->attribute = cpystr("name");
	    }
	}
	pm->value = cpystr(last_cmpnt(pa->filename));

        p->next = NULL;
        pa->id = cpystr(p->body.id);
    }
}


/*
 * Build and return the "From:" address for outbound messages from
 * global data...
 */
ADDRESS *
generate_from()
{
    ADDRESS *addr = mail_newaddr();
    if(ps_global->VAR_PERSONAL_NAME)
      addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);

    addr->mailbox = cpystr(ps_global->VAR_USER_ID);
    addr->host    = cpystr(ps_global->maildomain);
    return(addr);
}


/*
 * free_attachment_list - free attachments in given list
 */
void
free_attachment_list(alist)
    PATMT  **alist;
{
    PATMT  *leading;

    while(alist && *alist){		/* pointer pointing to something */
	leading = (*alist)->next;
	if((*alist)->description)
          fs_give((void **)&(*alist)->description);

	if((*alist)->filename)
          fs_give((void **)&(*alist)->filename);

	if((*alist)->size)
          fs_give((void **)&(*alist)->size);

	if((*alist)->id)
          fs_give((void **)&(*alist)->id);

	fs_give((void **)alist);

	*alist = leading;
    }
}



/*----------------------------------------------------------------------

Returns IsText:
  If lines are less than 500 characters and thereis nothing with the 
  8th bit on

Returns IsText8:
  If lines are less than 500 characters and less than 10% of characters
  have 8th bit on

Returns IsBinary
  All other cases;

 ---*/
FileTypes   
file_type(f, len)
    void *f;
    long  len;
{
    long      max_line, eight_bit_chars;
    char     *p, *line_start;
    FileTypes rv;

    max_line        = 0L;
    line_start      = (char *)f;
    eight_bit_chars = 0L;
    
    for(p = (char *)f; p < &(((char *)f)[len]); p++) {
        if(*p == '\n') {
            max_line = max(max_line, p - line_start);
            line_start = p;
        } else if(*p & 0x80){
            eight_bit_chars++;
        } else if(!*p) {
            /* A NULL, must be binary */
            return(IsBinary);
        }
    }
    if(max_line > 500L)
      rv = IsBinary;  /* Very long lines */
    else if(eight_bit_chars == 0L)
      rv = IsText;    /* short lines, no 8 bit */
    else if((eight_bit_chars * 100L)/len < 10L)
      rv = IsText8;   /* Short lines, < 10% 8 bit chars */
    else
      rv = IsBinary;   /* Short lines, > 10% 8 bit chars */
    
    dprint(4, (debugfile,
               "file_type -- len: %ld max_line: %ld  8 bit chars: %ld  %s\n",
               len, max_line, eight_bit_chars,
               rv==IsText ? "IsText" : rv==IsBinary ? "IsBinary" : "IsText8"));
    return(rv);
}



/*----------------------------------------------------------------------
  Insert the addition into the message id before first "@"
 
 This may be called twice on the same message-ID, but it won't do anything
 the second time. This will cause the status in the message-ID to be wrong
 if an attempt to send the message is made, an error occurs, and then the
 size or MIME parts changed and it is sent again.
  ----*/
void 
update_message_id(e, addition)
    ENVELOPE *e;
    char *addition;
{
    char *p, *q, *r, *new;

    new = fs_get(strlen(e->message_id) + strlen(addition) + 5);
    for(p = new, q = e->message_id; *q && *q != '@'; *p++ = *q++);
    if(isdigit(*p)) {
        /* Already been updated if it's a digit, not a letter */
        fs_give((void **)&new);
        return;
    }

    *p++ = '-';
    for(r = addition; *r ; *p++ = *r++);
    for(; *q; *p++ = *q++);
    *p = *q;
    fs_give((void **)&(e->message_id));
    e->message_id = new;
}




static struct mime_count {
    int text_parts;
    int image_parts;
    int message_parts;
    int application_parts;
    int audio_parts;
    int  video_parts;
} mc;

char *
mime_stats(body)
    BODY *body;
{
    static char id[10];
    mc.text_parts = 0;
    mc.image_parts = 0;
    mc.message_parts = 0;
    mc.application_parts = 0;
    mc.audio_parts = 0;
    mc.video_parts = 0;

    mime_recur(body);

    mc.text_parts        = min(8, mc.text_parts );
    mc.image_parts       = min(8, mc.image_parts );
    mc.message_parts     = min(8, mc.message_parts );
    mc.application_parts = min(8, mc.application_parts );
    mc.audio_parts       = min(8, mc.audio_parts );
    mc.video_parts       = min(8, mc.video_parts );


    id[0] = encode_bits(mc.text_parts);
    id[1] = encode_bits(mc.message_parts);
    id[2] = encode_bits(mc.application_parts);
    id[3] = encode_bits(mc.video_parts);
    id[4] = encode_bits(mc.audio_parts);
    id[5] = encode_bits(mc.image_parts);
    id[6] = '\0';
    return(id);
}
    


/*----------------------------------------------------------------------
   ----*/
void
mime_recur(body)
    BODY *body;
{
    PART *part;
    switch (body->type) {
      case TYPETEXT:
        mc.text_parts++;
        break;
      case TYPEIMAGE:
        mc.image_parts++;
        break;
      case TYPEMESSAGE:
        mc.message_parts++;
        break;
      case TYPEAUDIO:
        mc.audio_parts++;
        break;
      case TYPEAPPLICATION:
        mc.application_parts++;
        break;
      case TYPEVIDEO:
        mc.video_parts++;
        break;
      case TYPEMULTIPART:
        for(part = body->contents.part; part != NULL; part = part->next) 
          mime_recur(&(part->body));
        break;
    }
}
        
int        
encode_bits(bits)
    int bits;
{
    if(bits < 10)
      return(bits + '0');
    else if(bits < 36)
      return(bits - 10 + 'a');
    else if (bits < 62)
      return(bits - 36 + 'A');
    else
      return('.');
}

int
logbase2(x)
    long x;
{
    int base2;

    for(base2 = 0; x != 0; base2++)
      x /= 2;
    return(base2);
}


/*
 * set_mime_types - sniff the given storage object to determine its 
 *                  type, subtype and encoding
 *
 *		"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.
 *
 * 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_types(body)
    BODY *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,
                     can_be_ascii = 1L;
    STORE_S         *so = (STORE_S *)body->contents.binary;
    unsigned short   new_encoding = ENCOTHER;
#ifdef ENCODE_FROMS
    short            froms = 0,
                     bmap  = 0x1;
#endif

#ifndef DOS
    buf = (unsigned char *)fs_get(RBUFSZ);
#else
    buf = (unsigned char *)tmp_20k_buf;
#endif
    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 (!strncmp((char *)buf, "GIF", 3)) {
		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 (!strncmp((char *)buf, "MM", 2)
					|| !strncmp((char *)buf, "II", 2)) {
		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 (!strncmp((char *)buf, ".snd", 4)) {
		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) {
#ifdef DOS /* for binary file detection */
	int lastchar = '\0';
#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, bmap, *p);
#endif
	    if (*p == '\n') {
		max_line = max(max_line, line_so_far + p - bol);
		bol = p;
		line_so_far = 0L;
	    }else if (*p == ctrl('O') || *p == ctrl('N') || *p == ESCAPE) {
		can_be_ascii--;
	    }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;
	    }
#ifdef DOS /* for binary file detection */
#define BREAKOUT 300   /* a value that a character can't be */
	    /* LF with no preceding CR, so binary */
	    else if (*p == '\n' && lastchar != '\r') {
		    lastchar = BREAKOUT;
	    }
#endif

#ifdef DOS /* for binary file detection */
	    if (lastchar != BREAKOUT)
		lastchar = *p;
#endif

	    /* read another buffer in */
	    if (n == 0) {
		line_so_far += p - bol;
		for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
		  ;
		len += n;
		p = buf;
	    }else {
		p++;
	    }
#ifdef DOS /* 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
	}
    }

    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 ((eight_bit_chars * 100L)/len < 30L) {
	    /*
	     * The 30% threshold is based on qp encoded readability
	     * on non-MIME UA's.
	     */
	    can_be_ascii--;
	    if (body->type == TYPEOTHER)
		body->type = TYPETEXT;

	    if (new_encoding != ENCBINARY)
		new_encoding = ENC8BIT;  /* short lines, < 30% 8 bit chars */
	}else {
	    can_be_ascii--;
	    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 */
    if (froms && new_encoding != ENCBINARY)
	new_encoding = ENC8BIT;
#endif

    /* need to set the subtype, and possibly the charset */
    if(body->type == TYPETEXT && body->subtype == NULL) {
        PARAMETER *pm;

	body->subtype = cpystr("PLAIN");

	/* need to add charset */
	if(can_be_ascii > 0 || ps_global->VAR_CHAR_SET) {
	    if(body->parameter == NULL) {
	        pm = body->parameter = mail_newbody_parameter();
	        pm->attribute = cpystr("charset");
	    }else {
                for(pm = body->parameter;
			strucmp(pm->attribute, "charset") && pm->next != NULL;
								pm = pm->next);
	        if(strucmp(pm->attribute, "charset") != 0) {
		    pm->next = mail_newbody_parameter();
		    pm = pm->next;
		    pm->attribute = cpystr("charset");
	        }else if(pm->value) {
			fs_give((void **)&pm->value);
		}
	    }

	    if(can_be_ascii > 0)
	        pm->value = cpystr("US-ASCII");
	    else
	        pm->value = cpystr(ps_global->VAR_CHAR_SET);
	}
    }

    if(body->encoding == ENCOTHER)
      body->encoding = new_encoding;

#ifndef	DOS
    fs_give((void **)&buf);
#endif
}



/*
 * these functions provide for attachment handling in DOS.  They
 * may at a later date be exended to pine in general.  It pretty
 * much depends on how the piping performs.
 */


/*
 * pine_smtp_mail - pine version of c-client call to deal with 
 *                  somewhat novel way pine deals with text internally
 */
long 
pine_smtp_mail (stream,type,header,body)
    SMTPSTREAM *stream;
    char       *type;
    METAENV    *header;
    BODY       *body;
{
  char tmp[8*MAILTMPLEN];
  long error = NIL;
  if (!(header->env->to || header->env->cc || header->env->bcc)) {
  				/* no recipients in request */
    smtp_fake (stream,SMTPHARDERROR,"No recipients specified");
    return NIL;
  }
  				/* make sure stream is in good shape */
  smtp_send (stream,"RSET",NIL);
  strcpy (tmp,"FROM:<");	/* compose "MAIL FROM:<return-path>" */
  rfc822_address (tmp,header->env->return_path);
  strcat (tmp,">");
				/* send "MAIL FROM" command */
  if (!(smtp_send (stream,type,tmp) == SMTPOK)) return NIL;
				/* negotiate the recipients */
  if (header->env->to) smtp_rcpt (stream,header->env->to,&error);
  if (header->env->cc) smtp_rcpt (stream,header->env->cc,&error);
  if (header->env->bcc) smtp_rcpt (stream,header->env->bcc,&error);
  if (error) {			/* any recipients failed? */
      				/* reset the stream */
    smtp_send (stream,"RSET",NIL);
    smtp_fake (stream,SMTPHARDERROR,"One or more recipients failed");
    return NIL;
  }
				/* negotiate data command */
  if (!(smtp_send (stream,"DATA",NIL) == SMTPREADY)) return NIL;
				/* set up error in case failure */
  smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection went away!");
				/* output data, return success status */
  return(pine_rfc822_output(header, body, smtp_soutr, stream->tcpstream)
	 && (smtp_send (stream,".",NIL) == SMTPOK));
}


/*
 * pine_header_line - simple wrapper around c-client call to contain
 *                    repeated code, and to write fcc if required.
 */
int
pine_header_line(tmp, field, header, text, f, s)
     char      *tmp;
     char      *field;
     METAENV   *header;
     char      *text;
     soutr_t    f;
     TCPSTREAM *s;
{
    char *p;

    *(p = tmp) = '\0';
    rfc822_header_line(&p, field, header ? header->env : NULL, text);
    return((f ? (*f)(s, tmp) : 1)
	     && ((fcc_so && !fcc_written) ? so_puts(fcc_so, tmp) : 1));
}


/*
 * pine_address_line - write a header field containing addresses,
 *                     one by one (so there's no buffer limit), and
 *                     wrapping where necesary.
 * Note: we use c-client functions to properly build the text string,
 *       but have screw around with pointers to fool c-client functions
 *       into no blatting all the text into a single buffer.  Yeah, I know.
 */
int
pine_address_line(tmp, field, header, alist, f, s)
     char      *tmp;
     char      *field;
     METAENV   *header;
     ADDRESS   *alist;
     soutr_t    f;
     TCPSTREAM *s;
{
    char    *p;
    ADDRESS *atmp;
    int      i, count, write_field;

    if(!alist)				/* nothing in field! */
      return(1);

    *(p = tmp)  = '\0';
    atmp        = alist->next;
    alist->next = NULL;			/* digest only first address! */
    rfc822_address_line(&p, field, header ? header->env : NULL, alist);
    alist->next = atmp;			/* restore pointer to next addr */
    write_field   = !sending_stream || strucmp("Bcc", field);

    if((count = strlen(tmp)) > 2){	/* back over \n\r ? */
	tmp[count-2] = '\0';
	count -= 2;
    }

    if((write_field && f && !(*f)(s, tmp))
       || (fcc_so && !fcc_written && !so_puts(fcc_so, tmp)))
      return(0);

    for(alist = atmp; alist; alist = alist->next){
	/* check host to account for c-client's representation
	 * of group names.
	 */
	/* first, write delimiter */
	if(alist->host && ((write_field && f && !(*f)(s, ", "))
	   || (fcc_so && !fcc_written && !so_puts(fcc_so, ", "))))
	  return(0);

	tmp[0]      = '\0';
	atmp        = alist->next;
	alist->next = NULL;		/* tie off linked list */
	rfc822_write_address(tmp, alist);
	alist->next = atmp;		/* restore next pointer */

	if(count + 2 + (i = strlen(tmp)) > 78){ /* wrap long lines... */
	    count = i + 4;
	    if((write_field && f && !(*f)(s, "\015\012    "))
	       || (fcc_so && !fcc_written && !so_puts(fcc_so, "\015\012    ")))
	      return(0);
	}
	else
	  count += i + 2;

	if((write_field && f && !(*f)(s, tmp))
	   || (fcc_so && !fcc_written && !so_puts(fcc_so, tmp)))
	  return(0);
    }

    return((write_field && f ? (*f)(s, "\015\012") : 1)
	   && ((fcc_so && !fcc_written) ? so_puts(fcc_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.
 */
int
pine_rfc822_header(header, body, f, s)
    METAENV   *header;
    BODY      *body;
    soutr_t    f;
    TCPSTREAM *s;
{
    char       tmp[MAILTMPLEN], *p;
    PINEFIELD *extra;

    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)) 
	  || (fcc_so && !fcc_written && !so_puts(fcc_so, header->env->remail)))
	  return(0);				/* start with remail header */
    }

    if(!pine_header_line(tmp,"Newsgroups",header,header->env->newsgroups,f,s)
       || !pine_header_line(tmp,"Date",header,header->env->date,f,s)
       || !pine_address_line(tmp,"From",header,header->env->from,f,s)
#if	!defined(DOS) || defined(NOAUTH)	/* add it below */
       || !pine_address_line(tmp,"X-Sender",header,header->env->sender,f,s)
#endif
       || !pine_address_line(tmp,"Reply-To",header,header->env->reply_to,f,s)
       || !pine_address_line(tmp,"To",header,header->env->to,f,s)
       || !pine_address_line(tmp,"cc",header,header->env->cc,f,s)
       || !pine_address_line(tmp,"bcc",header,header->env->bcc,f,s)
       || !pine_header_line(tmp,"Subject",header,header->env->subject,f,s))
      return(0);

#if	defined(DOS) && !defined(NOAUTH)
    /*
     * Add comforting "X-" header line indicating what sort of 
     * authenticity the receiver can expect...
     */
    {
	 NETMBX netmbox;
	 char   sstring[MAILTMPLEN], *label;	/* place to write  */

	 if((ps_global->mail_stream
	     && mail_valid_net_parse(ps_global->mail_stream->mailbox, &netmbox)
	     && !netmbox.anoflag)
	    || (ps_global->inbox_stream
		&& ps_global->inbox_stream != ps_global->mail_stream
		&& mail_valid_net_parse(ps_global->inbox_stream->mailbox,
					&netmbox)
		&& !netmbox.anoflag)){
	     char   last_char = netmbox.host[strlen(netmbox.host) - 1];

	     sprintf(sstring, "%s@%s%s%s",
		     imap_user(netmbox.mailbox), isdigit(last_char) ? "[" : "",
		     netmbox.host, isdigit(last_char) ? "]" : "");
	     label =  "X-Sender";
	 }
	 else{
	     strcpy(sstring,"UNAuthenticated Sender");
	     label = "X-Warning";
	 }

	 if(!pine_header_line(tmp, label, header, sstring, f, s))
	   return(0);
     }
#endif

    if(!pine_header_line(tmp,"In-Reply-To",header,header->env->in_reply_to,
			 f,s))
      return(0);

    if(!pine_header_line(tmp,"Message-ID",header,header->env->message_id,f,s))
      return(0);

    /*
     * Write any custom fields...
     */
    for(extra = header->custom; extra && extra->name; extra = extra->next){
	switch(extra->type){
	  case Address :
	    if(!pine_address_line(tmp,extra->name,header,*extra->addr,f,s))
	      return(0);

	    break;
	  case FreeText :
	    if(!pine_header_line(tmp, extra->name, header, extra->text, f, s))
	      return(0);

	    break;
	  default:
	    q_status_message1(0,1,3,"Unknown header type: %s", extra->name);
	    break;
	}
    }

    if (body && !header->env->remail) {	/* not if remail or no body */
	if((f && !(*f)(s, MIME_VER))
	   || (fcc_so && !fcc_written && !so_puts(fcc_so, MIME_VER)))
	  return(0);

	*(p = tmp) = '\0';
	pine_write_body_header (&p, body);

	if((f && !(*f)(s, tmp)) 
	   || (fcc_so && !fcc_written && !so_puts(fcc_so, tmp)))
	  return(0);
    }
    else{					/* write terminating newline */
	if((f && !(*f)(s, "\015\012"))
	   || (fcc_so && !fcc_written && !so_puts(fcc_so, "\015\012")))
	  return(0);
    }

    return(1);
}


/*
 * since encoding happens on the way out the door, this is basically
 * just needed to handle TYPEMULTIPART
 */
void
pine_encode_body (body)
    BODY *body;
{
  void *f;
  PART *part;
  dprint(4, (debugfile, "-- pine_encode_body: %d\n", body ? body->type : 0));
  if (body) switch (body->type) {
  case TYPEMULTIPART:		/* multi-part */
    if (!body->parameter) {	/* cookie not set up yet? */
      char tmp[MAILTMPLEN];	/* make cookie not in BASE64 or QUOTEPRINT*/
      sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
	       getpid ());
      body->parameter = mail_newbody_parameter ();
      body->parameter->attribute = cpystr ("BOUNDARY");
      body->parameter->value = cpystr (tmp);
    }
    part = body->contents.part;	/* encode body parts */
    do pine_encode_body (&part->body);
    while (part = part->next);	/* until done */
    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_rfc822_output - pine's version of c-client call.  Necessary here
 *			since we're not using its structures as intended!
 */
int
pine_rfc822_output(header, body, f, s)
    METAENV   *header;
    BODY      *body;
    soutr_t    f;
    TCPSTREAM *s;
{
    dprint(4, (debugfile, "-- pine_rfc822_output\n"));
    pine_encode_body(body);		/* encode body as necessary */
    /*
     * build and output RFC822 header, output body
     */
    return(pine_rfc822_header(header, body, f, s)
	   && (body ? pine_rfc822_output_body(body, f, s) : 1));
}


static soutr_t    l_f;
static TCPSTREAM *l_stream;
static unsigned   c_in_buf = 0;

/*
 * l_flust_net - empties gf_io terminal function's buffer
 */
void
l_flush_net()
{
    if(c_in_buf){
	if(l_f)
	  (*l_f)(l_stream, tmp_20k_buf);

	if(fcc_so && !fcc_written)
	  so_puts(fcc_so, tmp_20k_buf);
    }

    c_in_buf = 0;
}


/*
 * l_putc - gf_io terminal function that calls smtp's soutr_t function.
 *
 */
int
l_putc(c)
    int c;
{
    tmp_20k_buf[c_in_buf++] = (char) c;
    tmp_20k_buf[c_in_buf]   = '\0';

    if(c_in_buf > 16384){
	if(fcc_so && !fcc_written)
	  so_puts(fcc_so, tmp_20k_buf);

        c_in_buf = 0;
        return(l_f ? (int)(*l_f)(l_stream, tmp_20k_buf) : 1);
    }
    else
	return(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(body, f, s)
    BODY *body;
    soutr_t f;
    TCPSTREAM *s;
{
    PART *part;
    PARAMETER *param;
    char *cookie = NIL, *t, *encode_error;
    char tmp[MAILTMPLEN];
    gf_io_t            gc, pc;
#ifdef	DOS
    extern unsigned char  *xlate_from_codepage;
#endif

    dprint(4, (debugfile, "-- pine_rfc822_output_body: %d\n",
	       body ? body->type : 0));
    if(body->type == TYPEMULTIPART) {   /* multipart gets special handling */
	part = body->contents.part;	/* first body part */
					/* find cookie */
	for (param = body->parameter; param && !cookie; param = param->next)
	  if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
	if (!cookie) cookie = "-";	/* yucky default */

	/*
	 * Output a bit of text before the first multipart delimiter
	 * to warn unsuspecting users of non-mime-aware ua's that
	 * they should expect weirdness...
	 */
	if(f && !(*f)(s, "  This message is in MIME format.  The first part \
should be readable text,\015\012  while the remaining parts are likely \
unreadable without MIME-aware tools.\015\012  Send mail to \
mime@docserver.cac.washington.edu for more info.\015\012\015\012"))
	  return(0);

	do {				/* for each part */
					/* build cookie */
	    sprintf (t = tmp,"--%s\015\012",cookie);
					/* append mini-header */
	    pine_write_body_header (&t,&part->body);
				/* output cookie, mini-header, and contents */
	    if(fcc_so && !fcc_written)
	      so_puts(fcc_so, tmp);

	    if (!((f ? (*f) (s,tmp) : 1)
		  && pine_rfc822_output_body (&part->body,f,s)))
	      return(0);
	} while (part = part->next);	/* until done */
					/* output trailing cookie */
	sprintf (t = tmp,"--%s--",cookie);
	if(fcc_so && !fcc_written){
	    so_puts(fcc_so, t);
	    so_puts(fcc_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...     */
    pc       = l_putc;			/* using our output function	*/
    gf_filter_init();
    dprint(4, (debugfile, "-- pine_rfc822_output_body: segment %ld bytes\n",
	       body->size.bytes));

    if(body->contents.binary)
      gf_set_so_readc(&gc, (STORE_S *)body->contents.binary);
    else
      return(1);

    so_seek((STORE_S *)body->contents.binary, 0L, 0);

    if(body->type != TYPEMESSAGE){ 	/* NOT encapsulated message */
	/*
	 * Convert text pieces to canonical form
	 * BEFORE applying any encoding (rfc1341: appendix G)...
	 */
	if(body->type == TYPETEXT){
	    gf_link_filter(gf_local_nvtnl);

#ifdef	DOS
	    if(xlate_from_codepage){
		gf_translate_opt(xlate_from_codepage, 256);
		gf_link_filter(gf_translate);
	    }
#endif
	}

	switch (body->encoding) {	/* all else needs filtering */
	  case ENC8BIT:			/* encode 8BIT into QUOTED-PRINTABLE */
	    gf_link_filter(gf_8bit_qp);
	    break;

	  case ENCBINARY:		/* encode binary into BASE64 */
	    gf_link_filter(gf_binary_b64);
	    break;

	  default:			/* otherwise text */
	    break;
	}
    }

    if(encode_error = gf_pipe(gc, pc)){ /* shove body part down pipe */
	q_status_message1(1, 3, 4,
			  "\007Encoding Error \"%s\"", encode_error);
	display_message('x');
	return(0);
    }
    else
      l_flush_net();

    so_release((STORE_S *)body->contents.binary);

    if(fcc_so && !fcc_written)
      so_puts(fcc_so, "\015\012");

    return(f ? (*f)(s, "\015\012") : 1);	/* output final stuff */
}


/*
 * pine_write_body_header - another c-client clone.  This time only
 *                          so the final encoding labels get set 
 *                          correctly since it hasn't happened yet.
 */
void
pine_write_body_header(dst, body)
    char **dst;
    BODY  *body;
{
    char *s;
    PARAMETER *param = body->parameter;
    extern const char *tspecials;

    sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
    s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
    sprintf (*dst += strlen (*dst),"/%s",s);
    if (param) do {
	sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
	rfc822_cat (*dst,param->value,tspecials);
    } while (param = param->next);
    else if (body->type == TYPETEXT) strcat (*dst,"; charset=US-ASCII");
    strcpy (*dst += strlen (*dst),"\015\012");
    if (body->encoding)		/* note: encoding 7BIT never output! */
      sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s\015\012",
	       body_encodings[body->encoding == ENCBINARY ? ENCBASE64 :
			      body->encoding == ENC8BIT ? ENCQUOTEDPRINTABLE :
			      body->encoding <= ENCMAX ? body->encoding :
							 ENCOTHER]);
    if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s\015\012",
			   body->id);
    if (body->description)
      sprintf (*dst += strlen (*dst),"Content-Description: %s\015\012",
	       body->description);
    strcat (*dst,"\015\012");	/* write terminating blank line */
}


/*
 * pine_free_body - yet another c-client clone just so the body gets
 *                  free'd appropriately
 */
void
pine_free_body(body)
    BODY **body;
{
    if (*body) {			/* only free if exists */
	pine_free_body_data (*body);	/* free its data */
	fs_give ((void **) body);	/* return body to free storage */
    }
}


/* 
 * pine_free_body_data - not just releasing strings anymore!
 */
void
pine_free_body_data(body)
    BODY *body;
{
    if (body->subtype) fs_give ((void **) &body->subtype);
    mail_free_body_parameter (&body->parameter);
    if (body->id) fs_give ((void **) &body->id);
    if (body->description) fs_give ((void **) &body->description);
    if(body->type == TYPEMULTIPART){
	pine_free_body_part (&body->contents.part);
    }
    else if(body->contents.binary){
	so_give((STORE_S **)&body->contents.binary);
	body->contents.binary = NULL;
    }
}


/*
 * pine_free_body_part - c-client clone to call the right routines
 *             for cleaning up.
 */
void
pine_free_body_part(part)
    PART **part;
{
    if (*part) {		/* only free if exists */
	pine_free_body_data (&(*part)->body);
				/* run down the list as necessary */
	pine_free_body_part (&(*part)->next);
	fs_give ((void **) part); /* return body part to free storage */
    }
}




/*----------------------------------------------------------------------
      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 fcc_so object.   
  ----*/
news_poster(header, body)
     METAENV *header;
     BODY    *body;
{
    char       *error_mess, error_buf[100];
    int         rv;

    q_status_message(0, 1, 3, "Posting news.....");
    display_message('x');

    dprint(4, (debugfile, "Posting: [%s]\n", header->env->newsgroups));

    if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
       && ps_global->VAR_NNTP_SERVER[0][0]){
       /*---------- NNTP server defined ----------*/
        error_mess              = NULL;

        ps_global->noshow_error = 1;
#ifdef DEBUG
        sending_stream = nntp_open(ps_global->VAR_NNTP_SERVER, debug);
#else
        sending_stream = nntp_open(ps_global->VAR_NNTP_SERVER, 0L);
#endif
        ps_global->noshow_error = 0;

        if(sending_stream != NULL) {
            if(pine_nntp_mail(sending_stream, header, body) == 0) {
                sprintf(error_buf, "\007Error posting message: %.60s",
                        sending_stream->reply);
                error_mess = error_buf;
            }

            smtp_close(sending_stream);
	    sending_stream = NULL;

        } else {
            /*---- Open of NNTP connection failed ------ */
            sprintf(error_buf,"\007Error connecting to news server: %.60s",
                    ps_global->c_client_error);
            dprint(1, (debugfile, error_buf));
            error_mess = error_buf;
        }

    } else {
        /*----- Post via local mechanism -------*/
        error_mess = post_handoff(header, body, error_buf);
    }

    if(error_mess != NULL) {
	if(fcc_so && !fcc_written)
	  so_give(&fcc_so);	/* clean up any fcc data */

        q_status_message(1, 2, 4, error_mess);
        return(-1);
    }

    fcc_written = 1;
    return(1);
}



/*----------------------------------------------------------------------
  Pine version of c-client routine to handle attachments from DOS

 * Network News Transfer Protocol deliver news
 * Accepts: stream
 *	    message envelope
 *	    message body
 * Returns: T on success, NIL on failure
 */

int
pine_nntp_mail (stream, header, body)
     SMTPSTREAM *stream;
     METAENV    *header;
     BODY       *body;
{
  long ret;
  char tmp[8*MAILTMPLEN],*s;
				/* negotiate post command */
  if (!(smtp_send (stream,"POST",NIL) == NNTPREADY)) return NIL;
				/* set up error in case failure */
  smtp_fake (stream,SMTPSOFTFATAL,"NNTP connection went away!");
				/* RFC-1036 requires this cretinism */
  sprintf (tmp,"Path: %s!%s\015\012",tcp_localhost (stream->tcpstream),
	   header->env->from ? header->env->from->mailbox : "foo");
				/* here's another cretinism */
  if (s = strstr (header->env->date," (")) *s = NIL;
				/* output data, return success status */
  ret = tcp_soutr (stream->tcpstream,tmp)
	 && pine_rfc822_output (header,body,smtp_soutr,stream->tcpstream)
	   && (smtp_send (stream,".",NIL) == NNTPOK);
  if (s) *s = ' ';		/* put the comment in the date back */
  return ret;
}


/*
 * view_as_rich - set the rich_header flag
 *
 *         name  - name of the header field
 *         deflt - default value to return if user didn't set it
 *
 *         Note: if the user tries to turn them all off with "", then
 *		 we take that to mean default, since otherwise there is no
 *		 way to get to the headers.
 */
int
view_as_rich(name, deflt)
char *name;
int deflt;
{
    char **p;
    char *q;

    p = ps_global->VAR_COMP_HDRS;

    if(p && *p && **p){
        for(; (q = *p) != NULL; p++){
	    if(!struncmp(q, name, strlen(name)))
		return 0; /* 0 means we do view it by default */
	}
        return 1; /* 1 means it starts out hidden */
    }
    return deflt;
}


/*
 * is_a_forbidden_hdr - is this name a "forbidden" header?
 *
 *          name - the header name to check
 *  We don't allow user to change these.
 */
int
is_a_forbidden_hdr(name)
char *name;
{
    char **p;
    static char *forbidden_headers[] = {
	"sender",
	"x-sender",
	"date",
	"received",
	"message-id",
	"in-reply-to",
	"path",
	"resent-message-id",
	"resent-message-date",
	"resent-message-from",
	"resent-message-sender",
	"resent-message-to",
	"resent-message-cc",
	"resent-message-reply-to",
	NULL
    };

    for(p = forbidden_headers; *p; p++){
	if(!strucmp(name, *p))
	    break;
    }

    return((*p) ? 1 : 0);
}


/*
 * count_custom_hdrs - returns number of custom headers defined by user
 */
int
count_custom_hdrs()
{
    char **p;
    char  *q     = NULL;
    char  *name;
    char  *t;
    char   save;
    int    ret   = 0;
    /* how many fixed fields are there? */
    int    fixed_cnt = (sizeof(pf_template)/sizeof(pf_template[0])) - 1;

    if(ps_global->VAR_CUSTOM_HDRS){
        for(p = ps_global->VAR_CUSTOM_HDRS; (q = *p) != NULL; p++){
	    if(q[0]){
		/* remove leading whitespace */
	        for(name = q; *name && isspace(*name); name++)
	    	    /* do nothing */;
		
		/* look for colon or space or end */
	        for(t = name; *t && !isspace(*t) && *t != ':'; t++)
	    	    /* do nothing */;

		save = *t;
		*t = '\0';
		if(!is_a_std_hdr(name) && !is_a_forbidden_hdr(name))
		    ret++;
		*t = save;
	    }
	}
    }

    return ret;
}


/*
 * set_default_hdrval - put the user's default value for this header
 *                      into pf->text.
 */
void
set_default_hdrval(pf)
PINEFIELD *pf;
{
    char       **p = 0;
    char        *q = 0;
    char        *t;
    char        *name;
    char        *value;
    char         save;

    if(!pf || !pf->name){
	q_status_message(0,1,3,"Internal error setting default header");
	return;
    }

    pf->text = NULL;

    if(ps_global->VAR_CUSTOM_HDRS){
        for(p = ps_global->VAR_CUSTOM_HDRS; (q = *p) != NULL; p++){

	    if(q[0]){

		/* remove leading whitespace */
	        for(name = q; *name && isspace(*name); name++)
	    	    /* do nothing */;
		
		if(!*name)
		    continue;

		/* look for colon or space or end */
	        for(t = name; *t && !isspace(*t) && *t != ':'; t++)
	    	    /* do nothing */;

		save = *t;
		*t = '\0';

		if(strucmp(name, pf->name) != 0){
		    *t = save;
		    continue;
		}

		*t = save;

		/* turn on editing, only used by from and reply-to */
		pf->noedit = 0;

		/* remove space between name and colon */
	        for(value = t; *value && isspace(*value); value++)
	    	    /* do nothing */;

		if(*value && *value == ':'){
		    /* remove leading whitespace from default value */
		    for(++value; *value && isspace(*value); value++)
			/* do nothing */;
		    pf->text = cpystr(value);
		}
		break;
	    }
	}
    }

    if(!pf->text)
	pf->text = cpystr("");
}


/*
 * is_a_std_hdr - is this name a "standard" header?
 *
 *          name - the header name to check
 */
int
is_a_std_hdr(name)
char *name;
{
    int    i;
    /* how many standard headers are there? */
    int fixed_cnt = (sizeof(pf_template)/sizeof(pf_template[0])) - 1;

    /* check to see if this is a standard header */
    for(i = 0; i < fixed_cnt; i++)
	if(!strucmp(name, pf_template[i].name))
	    break;

    return((i < fixed_cnt) ? 1 : 0);
}


/*
 * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
 *                    Allocates space for each name and addr ptr.
 *                    Allocates space for default in text, even if empty.
 *
 *              head - the first PINEFIELD to fill in
 */
void
customized_hdr_setup(head)
PINEFIELD *head;
{
    char **p, *q, *t, *name, *value, save;
    int forbid;
    PINEFIELD *pf;

    pf = head;

    if(ps_global->VAR_CUSTOM_HDRS){
        for(p = ps_global->VAR_CUSTOM_HDRS; (q = *p) != NULL; p++){

	    if(q[0]){

		/* remove leading whitespace */
	        for(name = q; *name && isspace(*name); name++)
	    	    /* do nothing */;
		
		if(!*name)
		    continue;

		/* look for colon or space or end */
	        for(t = name; *t && !isspace(*t) && *t != ':'; t++)
	    	    /* do nothing */;

		save = *t;
		*t = '\0';

		/*
		 * The same pinerc variable is used to customize standard
		 * headers, so skip them here.  Also, don't allow
		 * any of the forbidden headers.
		 */
		forbid = 0;
		if(is_a_std_hdr(name) || (forbid=is_a_forbidden_hdr(name))){
		    if(forbid)
			q_status_message1(0, 1, 2,
			    "Not allowed to change header \"%s\"", name);
		    *t = save;
		    continue;
		}

		pf->name = cpystr(name);
		pf->type = FreeText;
		pf->next = pf+1;
		/*
		 * 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;
		}
		*t = save;

		/* remove space between name and colon */
	        for(value = t; *value && isspace(*value); value++)
	    	    /* do nothing */;

		/* give them an alloc'd default, even if empty */
		if(!*value || (*value && *value != ':')){
		    pf->text = cpystr("");
		}else{
		    /* remove leading whitespace from default value */
		    for(++value; *value && isspace(*value); value++)
			/* do nothing */;
		    pf->text = cpystr(value);
		}
		pf++;
	    }
	}
    }
    /* fix last next pointer */
    if(pf != head)
	(pf-1)->next = NULL;
}


/*
 * get_dflt_custom_hdrs - allocate PINEFIELDS for custom headers
 *                        fill in the defaults
 */
PINEFIELD *
get_dflt_custom_hdrs()
{
    PINEFIELD          *pfields, *pf;
    int			i;

    /* add one for possible use by fcc */
    i       = (count_custom_hdrs() + 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);

    return(pfields);
}


/*
 * free_customs - free misc. resources associated with custom header fields
 *
 *           pf - pointer to first custom field
 */
void
free_customs(head)
PINEFIELD *head;
{
    PINEFIELD *pf;

    for(pf = head; pf && pf->name; pf = pf->next){

	fs_give((void **)&pf->name);

	/* only true for FreeText */
	if(pf->text)
	    fs_give((void **)&pf->text);

	/* only true for Address */
	if(pf->addr){
	    if(*pf->addr)
		mail_free_address(pf->addr);
	    fs_give((void **)&pf->addr);
	}

	if(pf->he && pf->he->prompt)
	    fs_give((void **)&pf->he->prompt);
    }
    fs_give((void **)&head);
}
