#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: reply.c,v 4.253 1999/01/27 22:31:57 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"


   Pine and Pico are registered 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, Pico, and Pilot software and its included text are Copyright
   1989-1999 by the University of Washington.

   The full text of our legal notices is contained in the file called
   CPYRIGHT, included with this distribution.


   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   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
    reply.c
   
   Code here for forward and reply to mail
   A few support routines as well

  This code will forward and reply to MIME messages. The Pine composer
at this time will only support non-text segments at the end of a
message so, things don't always come out as one would like. If you
always forward a message in MIME format, all will be correct.  Forwarding
of nested MULTIPART messages will work.  There's still a problem with
MULTIPART/ALTERNATIVE as the "first text part" rule doesn't allow modifying
the equivalent parts.  Ideally, we should probably such segments as a 
single attachment when forwarding/replying.  It would also be real nice to
flatten out the nesting in the composer so pieces inside can get snipped.

The evolution continues...

  =====*/


#include "headers.h"


/*
 * Internal Prototypes
 */
int	 reply_body_text PROTO((BODY *, BODY **));
void	 reply_forward_header PROTO((MAILSTREAM *, long, char *,
				     ENVELOPE *, gf_io_t, char *));
char    *reply_quote_initials PROTO((char *));
char	*reply_signature PROTO((char *, ENVELOPE *, REDRAFT_POS_S **, int *));
ADDRESS	*reply_cp_addr PROTO((struct pine *, long, char *, char *,
			      ADDRESS *, ADDRESS *, ADDRESS *, int));
void	 reply_append_addr PROTO((ADDRESS **, ADDRESS *));
ADDRESS *reply_resent PROTO((struct pine *, long, char *));
void	 reply_fish_personal PROTO((ENVELOPE *, ENVELOPE *));
char	*reply_build_refs PROTO((ENVELOPE *));
int	 addr_in_env PROTO((ADDRESS *, ENVELOPE *));
int	 addr_lists_same PROTO((ADDRESS *, ADDRESS *));
int	 reply_poster_followup PROTO((ENVELOPE *));
void	 forward_delimiter PROTO((gf_io_t));
void	 bounce_mask_header PROTO((char **, char *));
int	 sigdash_strip PROTO((long, char *, LT_INS_S **, void *));
int      sigdashes_are_present PROTO((char *));
char    *get_reply_data PROTO((ENVELOPE *, IndexColType, char *, int));
void     get_addr_data PROTO((ENVELOPE *, IndexColType, char *, int));
void     get_news_data PROTO((ENVELOPE *, IndexColType, char *, int));
char    *pico_sendexit_for_roles PROTO((struct headerentry *, void(*)()));
char    *pico_cancelexit_for_roles PROTO((void (*)()));
char    *detoken_src PROTO((char *, int, ENVELOPE *, REDRAFT_POS_S **, int *));
char    *detoken_guts PROTO((char *, int, ENVELOPE *, REDRAFT_POS_S **,
			     long *, int, int *));
void     a_little_addr_string PROTO((ADDRESS *, char *, int));
char    *handle_if_token PROTO((char *, char *, char **));
char    *get_token_arg PROTO((char *, char **));
char    *edit_role_sig PROTO((char *));
char    *edit_role_templ PROTO((char *));
char    *edit_a_file PROTO((int, char *));
char    *choose_sig_file PROTO((char **));
char    *choose_templ_file PROTO((char **));
char    *choose_a_file PROTO((char *, char **));



/*
 * Little defs to keep the code a bit neater...
 */
#define	FRM_PMT	"Use \"Reply-To:\" address instead of \"From:\" address"
#define	ALL_PMT		"Reply to all recipients"
#define	NEWS_PMT	"Follow-up to news group(s), Reply via email to author or Both? "
#define SIGDASHES	"-- "


/*
 * standard type of storage object used for body parts...
 */
#if	defined(DOS) && !defined(WIN32)
#define		  PART_SO_TYPE	TmpFileStar
#else
#define		  PART_SO_TYPE	CharStar
#endif



/*----------------------------------------------------------------------
        Fill in an outgoing message for reply and pass off to send

    Args: pine_state -- The usual pine structure

  Result: Reply is formatted and passed off to composer/mailer

Reply

   - put senders address in To field
   - search to and cc fields to see if we aren't the only recipients
   - if other than us, ask if we should reply to all.
   - if answer yes, fill out the To and Cc fields
   - fill in the fcc argument
   - fill in the subject field
   - fill out the body and the attachments
   - pass off to pine_send()
  ---*/
void
reply(pine_state)
     struct pine *pine_state;
{
    ADDRESS    *saved_from, *saved_to, *saved_cc, *saved_resent;
    ENVELOPE   *env, *outgoing;
    BODY       *body, *orig_body;
    REPLY_S     reply;
    void       *msgtext = NULL;
    char       *tmpfix = NULL, *prefix = NULL;
    long        msgno, totalm, *seq = NULL;
    int         i, include_text = 0, times = -1, warned = 0,
		flags = RSF_QUERY_REPLY_ALL;
    gf_io_t     pc;
    REDRAFT_POS_S *redraft_pos = NULL;
    PAT_HANDLE    *pattern_h;
    ROLE_ACTION_S *role = NULL;
    BUILDER_ARG fcc;
#if	defined(DOS) && !defined(_WINDOWS)
    char *reserve;
#endif

    outgoing = mail_newenvelope();
    memset((void *)&fcc, 0, sizeof(BUILDER_ARG));
    totalm   = mn_total_cur(pine_state->msgmap);
    seq	     = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));

    dprint(4, (debugfile,"\n - reply (%s msgs) -\n", comatose(totalm)));

    saved_from		  = (ADDRESS *) NULL;
    saved_to		  = (ADDRESS *) NULL;
    saved_cc		  = (ADDRESS *) NULL;
    saved_resent	  = (ADDRESS *) NULL;
    outgoing->subject	  = NULL;

    memset((void *)&reply, 0, sizeof(reply));

    /*
     * Loop thru the selected messages building the
     * outgoing envelope's destinations...
     */
    for(msgno = mn_first_cur(pine_state->msgmap);
	msgno > 0L;
	msgno = mn_next_cur(pine_state->msgmap)){

	/*--- Grab current envelope ---*/
	env = mail_fetchstructure(pine_state->mail_stream,
			    seq[++times] = mn_m2raw(pine_state->msgmap, msgno),
			    NULL);
	if(!env) {
	    q_status_message1(SM_ORDER,3,4,
			      "Error fetching message %s. Can't reply to it.",
			      long2string(msgno));
	    goto done_early;
	}

	if(!tmpfix){			/* look for prefix? */
	    tmpfix = reply_quote_str(env);
	    if(prefix){
		i = strcmp(tmpfix, prefix);
		fs_give((void **) &tmpfix);
		if(i){			/* don't check back if dissimilar*/
		    fs_give((void **) &prefix);
		    prefix = tmpfix = cpystr("> ");
		}
	    }
	    else{
		prefix = tmpfix;
		tmpfix = NULL;		/* check back later? */
	    }
	}

	/*
	 * For consistency, the first question is always "include text?"
	 */
	if(!times){		/* only first time */
	    char *p = cpystr(prefix);

	    if((include_text= reply_text_query(pine_state,totalm,&prefix)) < 0)
	      goto done_early;

	    /* edited prefix? */
	    if(strcmp(p, prefix))
	      tmpfix = NULL;	/* make sure we stop looking */

	    fs_give((void **) &p);
	}
	
	/*
	 * If we're agg-replying or there's a newsgroup and the user want's
	 * to post to news *and* via email, add relevant addresses to the
	 * outgoing envelope...
	 *
	 * The single message case get's us around the aggregate reply
	 * to messages in a mixed mail-news archive where some might
	 * have newsgroups and others not or whatever.
	 */
	if(totalm > 1L || ((i = reply_news_test(env, outgoing)) & 1)){
	    if(totalm > 1)
	      flags |= RSF_FORCE_REPLY_TO;

	    if(!reply_harvest(pine_state, seq[times], NULL, env,
			      &saved_from, &saved_to, &saved_cc,
			      &saved_resent, &flags))
	      goto done_early;
	}
	else if(i == 0)
	  goto done_early;

	/*------------ Format the subject line ---------------*/
	if(outgoing->subject){
	    /*
	     * if reply to more than one message, and all subjects
	     * match, so be it.  otherwise set it to something generic...
	     */
	    if(strucmp(outgoing->subject,
		       reply_subject(env->subject, tmp_20k_buf))){
		fs_give((void **)&outgoing->subject);
		outgoing->subject = cpystr("Re: several messages");
	    }
	}
	else
	  outgoing->subject = reply_subject(env->subject, NULL);
    }


    reply_seed(pine_state, outgoing, env, saved_from,
	       saved_to, saved_cc, saved_resent,
	       &fcc, flags & RSF_FORCE_REPLY_ALL);

    if(pine_state->expunge_count)	/* somebody expunged current msg */
      goto done_early;

    /*
     * Reply_seed may call c-client in get_fcc_based_on_to, so env may
     * no longer be valid. Get it again.
     */
    env = mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);

    /* Setup possible role */
    if((pattern_h = open_nonempty_patterns()) != NULL){
	if(!times)
	  /* setup default role */
	  role = set_role_from_msg(pine_state, ROLE_REPLY, pattern_h,
				   seq[times], NULL);

	if(confirm_role(ROLE_REPLY, pattern_h, &role))
	  role = combine_inherited_role(role, pattern_h);
	else{				/* cancel reply */
	    role = NULL;
	    close_patterns(&pattern_h);
	    cmd_cancelled("Reply");
	    goto done_early;
	}

	close_patterns(&pattern_h);
    }

    if(role){
	q_status_message1(SM_ORDER, 3, 4,
			  "Replying using role \"%s\"", role->nick);

	/* override fcc gotten in reply_seed */
	if(role->fcc){
	    if(fcc.tptr)
	      fs_give((void **)&fcc.tptr);

	    memset((void *)&fcc, 0, sizeof(BUILDER_ARG));
	}
    }

    seq[++times] = -1L;		/* mark end of sequence list */

    /*==========  Other miscelaneous fields ===================*/   
    outgoing->in_reply_to = reply_in_reply_to(env);
    outgoing->message_id = generate_message_id();

    if(!outgoing->to &&
       !outgoing->cc &&
       !outgoing->bcc &&
       !outgoing->newsgroups)
      q_status_message(SM_ORDER | SM_DING, 3, 6,
		       "Warning: no valid addresses to reply to!");

#if	defined(DOS) && !defined(_WINDOWS)
#if	defined(LWP) || defined(PCTCP) || defined(PCNFS)
#define	IN_RESERVE	8192
#else
#define	IN_RESERVE	16384
#endif
    if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
        q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Insufficient memory for message text");
	goto done_early;
    }
#endif

   /*==================== Now fix up the message body ====================*/

    /* 
     * create storage object to be used for message text
     */
    if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
        q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Error allocating message text");
        goto done_early;
    }

    gf_set_so_writec(&pc, (STORE_S *) msgtext);

    /*---- Include the original text if requested ----*/
    if(include_text && totalm > 1L){
	char *sig;
	int   impl, template_len = 0, leave_cursor_at_top = 0;


	env = NULL;
        if(role && role->template){
	    char *filtered;

	    impl = 0;
	    filtered = detoken_file(role->template, env, 0,
				    F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
				    0,
				    &redraft_pos, &impl);
	    if(filtered){
		if(*filtered){
		    so_puts((STORE_S *)msgtext, filtered);
		    if(impl == 1)
		      template_len = strlen(filtered);
		    else if(impl == 2)
		      leave_cursor_at_top++;
		}

		fs_give((void **)&filtered);
	    }
	    else
	      impl = 1;
	}
	else
	  impl = 1;

	if((sig = reply_signature((role && role->sig)
				    ? role->sig
				    : pine_state->VAR_SIGNATURE_FILE,
				  env, &redraft_pos, &impl)) &&
	   F_OFF(F_SIG_AT_BOTTOM, ps_global)){

	    /*
	     * Note that template_len is zero if CURSORPOS happened there.
	     * If impl was 2 from template, offset was already correct.
	     * If it was 1, we add the length of the template.
	     */
	    if(impl == 2)
	      redraft_pos->offset += template_len;
	    
	    if(*sig)
	      so_puts((STORE_S *)msgtext, sig);
	    
	    fs_give((void **)&sig);
	}

	/*
	 * Only put cursor in sig if there is a cursorpos there but not
	 * one in the template, and sig-at-bottom.
	 */
	 if(!(sig && impl == 2 && !leave_cursor_at_top))
	   leave_cursor_at_top++;

	body                  = mail_newbody();
	body->type            = TYPETEXT;
	body->contents.text.data = msgtext;

	for(msgno = mn_first_cur(pine_state->msgmap);
	    msgno > 0L;
	    msgno = mn_next_cur(pine_state->msgmap)){

	    if(env){			/* put 2 between messages */
		gf_puts(NEWLINE, pc);
		gf_puts(NEWLINE, pc);
	    }

	    /*--- Grab current envelope ---*/
	    env = mail_fetchstructure(pine_state->mail_stream,
				      mn_m2raw(pine_state->msgmap, msgno),
				      &orig_body);
	    if(!env || !orig_body){
		q_status_message1(SM_ORDER,3,4,
			      "Error fetching message %s. Can't reply to it.",
			      long2string(mn_get_cur(pine_state->msgmap)));
		goto done_early;
	    }

	    if(orig_body == NULL || orig_body->type == TYPETEXT) {
		reply_delimiter(env, pc);
		if(F_ON(F_INCLUDE_HEADER, pine_state))
		  reply_forward_header(pine_state->mail_stream,
				       mn_m2raw(pine_state->msgmap,msgno),
				       NULL, env, pc, prefix);

		get_body_part_text(pine_state->mail_stream, orig_body,
				   mn_m2raw(pine_state->msgmap, msgno),
				   "1", pc, prefix);
	    }
	    else if(orig_body->type == TYPEMULTIPART) {
		if(!warned++)
		  q_status_message(SM_ORDER,3,7,
		      "WARNING!  Attachments not included in multiple reply.");

		if(orig_body->nested.part
		   && orig_body->nested.part->body.type == TYPETEXT) {
		    /*---- First part of the message is text -----*/
		    reply_delimiter(env, pc);
		    if(F_ON(F_INCLUDE_HEADER, pine_state))
		      reply_forward_header(pine_state->mail_stream,
					   mn_m2raw(pine_state->msgmap,
						    msgno),
					   NULL, env, pc, prefix);

		    get_body_part_text(pine_state->mail_stream,
				       &orig_body->nested.part->body,
				       mn_m2raw(pine_state->msgmap, msgno),
				       "1", pc, prefix);
		}
		else{
		    q_status_message(SM_ORDER,0,3,
				     "Multipart with no leading text part.");
		}
	    }
	    else{
		/*---- Single non-text message of some sort ----*/
		q_status_message(SM_ORDER,3,3,
				 "Non-text message not included.");
	    }
	}

	if(!leave_cursor_at_top){
	    long  cnt = 0L;
	    unsigned char c;

	    /* rewind and count chars to start of sig file */
	    so_seek((STORE_S *)msgtext, 0L, 0);
	    while(so_readc(&c, (STORE_S *)msgtext))
	      cnt++;

	    if(!redraft_pos){
		redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(*redraft_pos));
		memset((void *)redraft_pos, 0,sizeof(*redraft_pos));
		redraft_pos->hdrname = cpystr(":");
	    }

	    /*
	     * If explicit cursor positioning in sig file,
	     * add offset to start of sig file plus offset into sig file.
	     * Else, just offset to start of sig file.
	     */
	    redraft_pos->offset += cnt;
	}

	if(sig){
	    if(*sig)
	      so_puts((STORE_S *)msgtext, sig);
	    
	    fs_give((void **)&sig);
	}
    }
    else{
	msgno = mn_m2raw(pine_state->msgmap,
			 mn_get_cur(pine_state->msgmap));

	/*--- Grab current envelope ---*/
	env = mail_fetchstructure(pine_state->mail_stream, msgno, &orig_body);
	if(env && orig_body) {
	    if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
				   msgno, NULL, msgtext, prefix,
				   include_text,
				   (role && role->template)
				      ? role->template
				      : NULL,
				   (role && role->sig)
				      ? role->sig
				      : pine_state->VAR_SIGNATURE_FILE,
				   &redraft_pos)))
	       goto done_early;
	}
	else{
	    q_status_message1(SM_ORDER,3,4,
			      "Error fetching message %s. Can't reply to it.",
			      long2string(mn_get_cur(pine_state->msgmap)));
	    goto done_early;
	}
    }

    /* fill in reply structure */
    reply.prefix	= prefix;
    reply.mailbox	= cpystr(pine_state->mail_stream->mailbox);
    reply.data.uid.msgs = (unsigned long *) fs_get((times + 1)
						      * sizeof(unsigned long));
    if(reply.data.uid.validity = pine_state->mail_stream->uid_validity){
	reply.flags = REPLY_UID;
	for(i = 0; i < times ; i++)
	  reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
    }
    else{
	reply.flags = REPLY_MSGNO;
	for(i = 0; i < times ; i++)
	  reply.data.uid.msgs[i] = (unsigned long) seq[i];
    }

    reply.data.uid.msgs[i] = 0;			/* tie off list */

#if	defined(DOS) && !defined(_WINDOWS)
    free((void *)reserve);
#endif

    /* partially formatted outgoing message */
    pine_send(outgoing, &body, "COMPOSE MESSAGE REPLY",
	      role, fcc.tptr, &reply, redraft_pos, NULL, NULL, 0);
  done:
    pine_free_body(&body);
    if(reply.mailbox)
      fs_give((void **) &reply.mailbox);
    fs_give((void **) &reply.data.uid.msgs);
  done_early:
    if((STORE_S *) msgtext)
      gf_clear_so_writec((STORE_S *) msgtext);

    mail_free_envelope(&outgoing);
    mail_free_address(&saved_from);
    mail_free_address(&saved_to);
    mail_free_address(&saved_cc);
    mail_free_address(&saved_resent);

    fs_give((void **)&seq);

    if(prefix)
      fs_give((void **)&prefix);

    if(fcc.tptr)
      fs_give((void **)&fcc.tptr);

    free_redraft_pos(&redraft_pos);
    free_role(&role);
}



/*
 * reply_harvest - 
 *
 *  Returns: 1 if addresses successfully copied
 *	     0 on user cancel or error
 *
 *  Input flags: 
 *		 RSF_FORCE_REPLY_TO
 *		 RSF_QUERY_REPLY_ALL
 *		 RSF_FORCE_REPLY_ALL
 *
 *  Output flags:
 *		 RSF_FORCE_REPLY_ALL
 * 
 */
int
reply_harvest(ps, msgno, section, env, saved_from, saved_to,
	      saved_cc, saved_resent, flags)
    struct pine  *ps;
    long	  msgno;
    char	 *section;
    ENVELOPE	 *env;
    ADDRESS	**saved_from, **saved_to, **saved_cc, **saved_resent;
    int		 *flags;
{
    ADDRESS *ap, *ap2;
    int	     ret = 0, sniff_resent = 0;

      /*
       * If Reply-To is same as From just treat it like it was From.
       * Otherwise, always use the reply-to if we're replying to more
       * than one msg or say ok to using it, even if it's us.
       * If there's no reply-to or it's the same as the from, assume
       * that the user doesn't want to reply to himself, unless there's
       * nobody else.
       */
    if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
       && (F_ON(F_AUTO_REPLY_TO, ps_global)
	   || ((*flags) & RSF_FORCE_REPLY_TO)
	   || (ret = want_to(FRM_PMT,'y','x',NO_HELP,WT_SEQ_SENSITIVE)) == 'y'))
      ap = reply_cp_addr(ps, msgno, section, "reply-to", *saved_from,
			 (ADDRESS *) NULL, env->reply_to, 1);
    else
      ap = reply_cp_addr(ps, msgno, section, "From", *saved_from,
			 (ADDRESS *) NULL, env->from, 0);

    if(ret == 'x') {
	cmd_cancelled("Reply");
	return(0);
    }

    reply_append_addr(saved_from, ap);

    /*--------- check for other recipients ---------*/
    if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){

	if(ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
			      *saved_from, env->to, 0))
	  reply_append_addr(saved_to, ap);

	if(ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
			      *saved_from, env->cc, 0))
	  reply_append_addr(saved_cc, ap);

	/*
	 * In these cases, we either need to look at the resent headers
	 * to include in the reply-to-all, or to decide whether or not
	 * we need to ask the reply-to-all question.
	 */
	if(((*flags) & RSF_FORCE_REPLY_ALL)
	   || (((*flags) & RSF_QUERY_REPLY_ALL)
	       && ((!(*saved_from) && !(*saved_cc))
		   || (*saved_from && !(*saved_to) && !(*saved_cc))))){

	    sniff_resent++;
	    if(ap2 = reply_resent(ps, msgno, section)){
		/*
		 * look for bogus addr entries and replace
		 */
		if(ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
				      *saved_from, ap2, 0))

		  reply_append_addr(saved_resent, ap);

		mail_free_address(&ap2);
	    }
	}

	/*
	 * It makes sense to ask reply-to-all now.
	 */
	if(((*flags) & RSF_QUERY_REPLY_ALL)
	   && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
	       || (*saved_cc || *saved_resent))){
	    *flags &= ~RSF_QUERY_REPLY_ALL;
	    if((ret=want_to(ALL_PMT,'n','x',NO_HELP,WT_SEQ_SENSITIVE)) == 'x'){
		cmd_cancelled("Reply");
		return(0);
	    }
	    else if(ret == 'y')
	      *flags |= RSF_FORCE_REPLY_ALL;
	}

	/*
	 * If we just answered yes to the reply-to-all question and
	 * we still haven't collected the resent headers, do so now.
	 */
	if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
	   && (ap2 = reply_resent(ps, msgno, section))){
	    /*
	     * look for bogus addr entries and replace
	     */
	    if(ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
				  *saved_from, ap2, 0))
	      reply_append_addr(saved_resent, ap);

	    mail_free_address(&ap2);
	}
    }

    return(1);
}


ROLE_ACTION_S *
set_role_from_msg(ps, type, pattern_h, msgno, section)
    struct pine *ps;
    int          type;
    PAT_HANDLE  *pattern_h;
    long         msgno;
    char        *section;
{
    ROLE_ACTION_S *role = NULL;
    PAT_S         *pat = NULL;
    int            match = 0;

    if(!pattern_h)
      return(role);

    /* Go through the possible roles one at a time until we get a match. */
    pat = first_pattern(pattern_h, type);

    while(!match && pat){
	if(match_pattern(pat->patgrp, ps->mail_stream, msgno, section)){
	    role = pat->action->role;
	    match++;
	}
	else
	  pat = next_pattern(pattern_h, type);
    }

    return(role);
}


/*
 * Ask user to confirm role choice, or choose another role.
 *
 * Args  pattern_h -- The role handle
 *            role -- A pointer into the pattern_h space at the default
 *                    role to use. This can't be a copy, the comparison
 *                    relies on it pointing at the actual role.
 *                    This arg is also used to return a pointer to the
 *                    chosen role.
 *
 * Returns   1 -- Yes, use role which is now in *role. This may not be
 *                the same as the role passed in and it may even be NULL.
 *           0 -- Cancel reply.
 */
int
confirm_role(flags, pattern_h, role)
    int             flags;
    PAT_HANDLE     *pattern_h;
    ROLE_ACTION_S **role;
{
    char            prompt[200];
    char           *prompt_fodder;
    int             cmd, done, ret = 1;
    PAT_S          *curpat, *pat;
    static ESCKEY_S ekey[] = {
	{'y', 'y', "Y", "Yes"},
	{'n', 'n', "N", "No"},
	{ctrl('P'), 10, "^P", "Prev Role"},
	{ctrl('N'), 11, "^N", "Next Role"},
	{KEY_UP,    10, "", ""},
	{KEY_DOWN,  11, "", ""},
	{-1, 0, NULL, NULL}};

    if(!pattern_h || !role)
      return(ret);
    
    /*
     * If this is a reply or forward and the role doesn't require confirmation,
     * then we just return with what was passed in.
     */
    if(((flags & ROLE_REPLY) &&
	*role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
       ((flags & ROLE_FORWARD) &&
	*role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
       (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global) &&
	 !(flags & ROLE_DEFAULTOK)))
      return(ret);

    /* check that there is at least one role available */
    if(!first_pattern(pattern_h, flags))
      return(ret);
    
    /* Setup default */
    curpat = NULL;
    if(*role){
	for(pat = first_pattern(pattern_h, flags);
	    pat;
	    pat = next_pattern(pattern_h, flags)){
	    if(pat->action && pat->action->role == *role){
		curpat = pat;
		break;
	    }
	}
    }

    if(flags & ROLE_REPLY)
      prompt_fodder = "Reply";
    else if(flags & ROLE_FORWARD)
      prompt_fodder = "Forward";
    else if(flags & ROLE_COMPOSE)
      prompt_fodder = "Compose";
    
    /*
     * The list of roles is treated circularly with a NULL pattern
     * holding the place of the default role (no role).
     */

    done = 0;
    while(!done){
	sprintf(prompt, "%s using role \"%.40s\" ? ",
		prompt_fodder,
		(curpat && curpat->patgrp && curpat->patgrp->nick)
		 ? curpat->patgrp->nick
		 : curpat ? "No Nickname" : "Default Role");
	cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
			    'y', 'x', h_role_confirm, RB_NORM);
	switch(cmd){
	    case 'y':
	      done++;
	      *role = curpat ? curpat->action->role : NULL;
	      break;
	    
	    /* prev */
	    case 10:
	      if(curpat)
		curpat = prev_pattern(pattern_h, flags);
	      else
		curpat = last_pattern(pattern_h, flags);

	      break;
	    
	    /* next */
	    case 11:
	      if(curpat)
		curpat = next_pattern(pattern_h, flags);
	      else
		curpat = first_pattern(pattern_h, flags);

	      break;
	    
	    case 'n':
	    case 'x':
	      done++;
	      *role = NULL;
	      ret = 0;
	      break;
	}
    }

    return(ret);
}


/*
 * reply_seed - 
 * 
 */
void
reply_seed(ps, outgoing, env, saved_from, saved_to, saved_cc,
	   saved_resent, fcc, replytoall)
    struct pine *ps;
    ENVELOPE	*outgoing, *env;
    ADDRESS	*saved_from, *saved_to, *saved_cc, *saved_resent;
    BUILDER_ARG	*fcc;
    int		 replytoall;
{
    ADDRESS **to_tail, **cc_tail;
    
    to_tail = &outgoing->to;
    cc_tail = &outgoing->cc;

    if(saved_from){
	/* Put Reply-To or From in To. */
	*to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
				 (ADDRESS *) NULL, saved_from, 1);
	/* and the rest in cc */
	if(replytoall){
	    *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
				     outgoing->to, saved_to, 1);
	    while(*cc_tail)		/* stay on last address */
	      cc_tail = &(*cc_tail)->next;

	    *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
				     outgoing->to, saved_cc, 1);
	    while(*cc_tail)
	      cc_tail = &(*cc_tail)->next;

	    *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
				     outgoing->to, saved_resent, 1);
	}
    }
    else if(saved_to){
	/* No From (maybe from us), put To in To. */
	*to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
				 (ADDRESS *)NULL, saved_to, 1);
	/* and the rest in cc */
	if(replytoall){
	    *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
				     outgoing->to, saved_cc, 1);
	    while(*cc_tail)
	      cc_tail = &(*cc_tail)->next;

	    *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
				     outgoing->to, saved_resent, 1);
	}
    }
    else{
	/* No From or To, put everything else in To if replytoall, */
	if(replytoall){
	    *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
				     (ADDRESS *) NULL, saved_cc, 1);
	    while(*to_tail)
	      to_tail = &(*to_tail)->next;

	    *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
				     (ADDRESS *) NULL, saved_resent, 1);
	}
	/* else, reply to original From which must be us */
	else{
	    /*
	     * Put self in To if in original From.
	     */
	    if(!outgoing->newsgroups)
	      *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
				       (ADDRESS *) NULL, env->from, 1);
	}
    }

    /* add any missing personal data */
    reply_fish_personal(outgoing, env);

    /* get fcc */
    if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
	fcc->tptr = get_fcc_based_on_to(outgoing->to);
    }
    else if(fcc && outgoing->newsgroups){
	char *errmsg = NULL, *newsgroups_returned = NULL;
	int ret_val;

	ret_val = news_build(outgoing->newsgroups, &newsgroups_returned,
			     &errmsg, fcc, NULL);
	if(errmsg){
	    if(*errmsg){
		q_status_message1(SM_ORDER, 3, 3, "%s", errmsg);
		display_message(NO_OP_COMMAND);
	    }
	    fs_give((void **)&errmsg);
	}
	if(ret_val != -1 &&
	    strcmp(outgoing->newsgroups, newsgroups_returned)){
	    fs_give((void **)&outgoing->newsgroups);
	    outgoing->newsgroups = newsgroups_returned;
	}
	else
	  fs_give((void **) &newsgroups_returned);
    }
}



/*----------------------------------------------------------------------
    Return a pointer to a copy of the given address list
  filtering out those already in the "mask" lists and ourself.

Args:  mask1  -- Don't copy if in this list
       mask2  --  or if in this list
       source -- List to be copied
       us_too -- Don't filter out ourself.

  ---*/
ADDRESS *
reply_cp_addr(ps, msgno, section, field, mask1, mask2, source, us_too)
     struct pine *ps;
     long	  msgno;
     char	 *section;
     char	 *field;
     ADDRESS     *mask1, *mask2, *source;
     int	  us_too;
{
    ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;

    for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
      if(tmp1->host && tmp1->host[0] == '.'){
	  char *h, *fields[2];

	  fields[0] = field;
	  fields[1] = NULL;
	  if(h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
				        msgno, section, fields)){
	      char *p, fname[32];

	      sprintf(fname, "%s:", field);
	      for(p = h; p = strstr(p, fname); )
		rplstr(p, strlen(fname), "");	/* strip field strings */

	      sqznewlines(h);			/* blat out CR's & LF's */
	      for(p = h; p = strchr(p, TAB); )
		*p++ = ' ';			/* turn TABs to whitespace */

	      if(*h){
		  long l;

		  ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
		  memset(ret, 0, sizeof(ADDRESS));

		  /* base64 armor plate the gunk to protect against
		   * c-client quoting in output.
		   */
		  p = (char *) rfc822_binary(h, strlen(h),
					     (unsigned long *) &l);
		  fs_give((void **) &h);
		  ret->mailbox = (char *) fs_get(strlen(p) + 4);
		  sprintf(ret->mailbox, "&%s", p);
		  fs_give((void **) &p);
		  ret->host = cpystr(".RAW-FIELD.");
	      }
	  }

	  return(ret);
      }

    ret_tail = &ret;
    for(source = first_addr(source); source; source = source->next){
	for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
	  if(address_is_same(source, tmp1))
	    break;

	for(tmp2 = first_addr(mask2); tmp2; tmp2 = tmp2->next)
	  if(address_is_same(source, tmp2))
	    break;

	/*
	 * If there's no match in masks *and* this address isn't us, copy...
	 */
	if(!tmp1 && !tmp2 && (us_too || !ps || !address_is_us(source, ps))){
	    tmp1         = source->next;
	    source->next = NULL;	/* only copy one addr! */
	    *ret_tail    = rfc822_cpy_adr(source);
	    ret_tail     = &(*ret_tail)->next;
	    source->next = tmp1;	/* restore rest of list */
	}
    }

    return(ret);
}



void
reply_append_addr(dest, src)
    ADDRESS  **dest, *src;
{
    for( ; *dest; dest = &(*dest)->next)
      ;

    *dest = src;
}



/*----------------------------------------------------------------------
    Test the given address lists for equivalence

Args:  x -- First address list for comparison
       y -- Second address for comparison

  ---*/
int
addr_lists_same(x, y)
     ADDRESS *x, *y;
{
    for(x = first_addr(x), y = first_addr(y);
	x && y;
	x = first_addr(x->next), y = first_addr(y->next)){
	if(!address_is_same(x, y))
	  return(0);
    }

    return(!x && !y);			/* true if ran off both lists */
}



/*----------------------------------------------------------------------
    Test the given address against those in the given envelope's to, cc

Args:  addr -- address for comparison
       env  -- envelope to compare against

  ---*/
int
addr_in_env(addr, env)
    ADDRESS  *addr;
    ENVELOPE *env;
{
    ADDRESS *ap;

    for(ap = env ? env->to : NULL; ap; ap = ap->next)
      if(address_is_same(addr, ap))
	return(1);

    for(ap = env ? env->cc : NULL; ap; ap = ap->next)
      if(address_is_same(addr, ap))
	return(1);

    return(0);				/* not found! */
}



/*----------------------------------------------------------------------
    Add missing personal info dest from src envelope

Args:  dest -- envelope to add personal info to
       src  -- envelope to get personal info from

NOTE: This is just kind of a courtesy function.  It's really not adding
      anything needed to get the mail thru, but it is nice for the user
      under some odd circumstances.
  ---*/
void
reply_fish_personal(dest, src)
     ENVELOPE *dest, *src;
{
    ADDRESS *da, *sa;

    for(da = dest ? dest->to : NULL; da; da = da->next){
	for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
	  if(address_is_same(da, sa) && sa->personal)
	    da->personal = cpystr(sa->personal);

	for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
	  if(address_is_same(da, sa) && sa->personal)
	    da->personal = cpystr(sa->personal);
    }

    for(da = dest ? dest->cc : NULL; da; da = da->next){
	for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
	  if(address_is_same(da, sa) && sa->personal)
	    da->personal = cpystr(sa->personal);

	for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
	  if(address_is_same(da, sa) && sa->personal)
	    da->personal = cpystr(sa->personal);
    }
}


/*----------------------------------------------------------------------
   Given a header field and envelope, build "References: " header data

Args:  

Returns: 
  ---*/
char *
reply_build_refs(env)
    ENVELOPE *env;
{
    int   l, id_len;
    char *p, *refs = NULL, *h = env->references;

    if(!(env->message_id && (id_len = strlen(env->message_id))))
      return(NULL);

    if(h){
	while((l = strlen(h)) + id_len + 1 > 512 && (p = strstr(h, "> <")))
	  h = p + 2;

	refs = fs_get(id_len + l + 2);
	sprintf(refs, "%s %s", h, env->message_id);
    }

    if(!refs && id_len)
      refs = cpystr(env->message_id);

    return(refs);
}



/*----------------------------------------------------------------------
   Snoop for any Resent-* headers, and return an ADDRESS list

Args:  stream -- 
       msgno -- 

Returns: either NULL if no Resent-* or parsed ADDRESS struct list
  ---*/
ADDRESS *
reply_resent(pine_state, msgno, section)
    struct pine *pine_state;
    long	 msgno;
    char	*section;
{
#define RESENTFROM 0
#define RESENTTO   1
#define RESENTCC   2
    ADDRESS	*rlist = NULL, **a, **b;
    char	*hdrs, *values[RESENTCC+1];
    int		 i;
    static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
    static char *fakedomain = "@";

    if(hdrs = pine_fetchheader_lines(pine_state->mail_stream,
				     msgno, section, fields)){
	memset(values, 0, (RESENTCC+1) * sizeof(char *));
	simple_header_parse(hdrs, fields, values);
	for(i = RESENTFROM; i <= RESENTCC; i++)
	  rfc822_parse_adrlist(&rlist, values[i],
			       (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
				 ? fakedomain : pine_state->maildomain);

	/* pare dup's ... */
	for(a = &rlist; *a; a = &(*a)->next)	/* compare every address */
	  for(b = &(*a)->next; *b; )		/* to the others	 */
	    if(address_is_same(*a, *b)){
		ADDRESS *t = *b;

		if(!(*a)->personal){		/* preserve personal name */
		    (*a)->personal = (*b)->personal;
		    (*b)->personal = NULL;
		}

		*b	= t->next;
		t->next = NULL;
		mail_free_address(&t);
	    }
	    else
	      b = &(*b)->next;
    }

    if(hdrs)
      fs_give((void **) &hdrs);
    
    return(rlist);
}


/*----------------------------------------------------------------------
    Format and return subject suitable for the reply command

Args:  subject -- subject to build reply subject for
       buf -- buffer to use for writing.  If non supplied, alloc one.

Returns: with either "Re:" prepended or not, if already there.
         returned string is allocated.
  ---*/
char *
reply_subject(subject, buf)
     char *subject;
     char *buf;
{
    size_t  l   = (subject && *subject) ? strlen(subject) : 10;
    char   *tmp = fs_get(l + 1), *decoded;

    if(!buf)
      buf = fs_get(l + 5);

    /* decode any 8bit into tmp buffer */
    decoded = (char *) rfc1522_decode((unsigned char *)tmp, subject, NULL);

    if(decoded					/* already "re:" ? */
       && (decoded[0] == 'R' || decoded[0] == 'r')
       && (decoded[1] == 'E' || decoded[1] == 'e')
       &&  decoded[2] == ':')
      strcpy(buf, subject);
    else
      sprintf(buf, "Re: %s", (subject && *subject) ? subject : "your mail");

    fs_give((void **) &tmp);
    return(buf);
}


/*----------------------------------------------------------------------
    return initials for the given personal name

Args:  name -- Personal name to extract initials from

Returns: pointer to name overwritten with initials
  ---*/
char *
reply_quote_initials(name)
    char *name;
{
    char *s = name,
         *w = name;
    
    /* while there are still characters to look at */
    while(s && *s){
	/* skip to next initial */
	while(*s && !isalnum((unsigned char) *s))
	  s++;
	
	/* copy initial */
	if(*s)
	  *w++ = *s++;
	
	/* skip to end of this piece of name */
	while(*s && isalnum((unsigned char) *s))
	  s++;
    }

    if(w)
      *w = '\0';

    return(name);
}

/*
 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
 * tokens being substituted for (only in the size of buf below).
 */
#define MAX_SUBSTITUTION 6
#define MAX_PREFIX 63

/*----------------------------------------------------------------------
    return a quoting string, "> " by default, for replied text

Args:  env -- envelope of message being replied to

Returns: malloc'd array containing quoting string, freed by caller
  ---*/
char *
reply_quote_str(env)
    ENVELOPE *env;
{
    char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbuf[MAX_SUBSTITUTION+1];
    static char *from_token = "_FROM_";
    static char *nick_token = "_NICK_";
    static char *init_token = "_INIT_";

    strncpy(buf, ps_global->VAR_REPLY_STRING, MAX_PREFIX);
    buf[MAX_PREFIX] = '\0';

    /* set up the prefix to quote included text */
    if(p = strstr(buf, from_token)){
	repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
							: "";
	strncpy(pbuf, repl, MAX_SUBSTITUTION);
	pbuf[MAX_SUBSTITUTION] = '\0';;
	rplstr(p, strlen(from_token), pbuf);
    }
    
    if(p = strstr(buf, nick_token)){
	repl = (env &&
		env->from &&
		env->from &&
		get_nickname_from_addr(env->from, tmp_20k_buf)) ? tmp_20k_buf
								: "";
	strncpy(pbuf, repl, MAX_SUBSTITUTION);
	pbuf[MAX_SUBSTITUTION] = '\0';;
	rplstr(p, strlen(nick_token), pbuf);
    }

    if(p = strstr(buf, init_token)){
	char *q = NULL;

	repl = (env && env->from && env->from->personal)
		 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode(
						(unsigned char *)tmp_20k_buf, 
						env->from->personal, NULL)))
		 : "";

	strncpy(pbuf, repl, MAX_SUBSTITUTION);
	pbuf[MAX_SUBSTITUTION] = '\0';;
	rplstr(p, strlen(init_token), pbuf);
	if(q)
	  fs_give((void **)&q);
    }
    
    prefix = removing_quotes(cpystr(buf));

    return(prefix);
}

/*
 * reply_text_query - Ask user about replying with text...
 *
 * Returns:  1 if include the text
 *	     0 if we're NOT to include the text
 *	    -1 on cancel or error
 */
int
reply_text_query(ps, many, prefix)
    struct pine *ps;
    long	 many;
    char       **prefix;
{
    int ret, edited = 0;
    static ESCKEY_S rtq_opts[] = {
	{'y', 'y', "Y", "Yes"},
	{'n', 'n', "N", "No"},
	{-1, 0, NULL, NULL},	                  /* may be overridden below */
	{-1, 0, NULL, NULL}
    };

    if(F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
       && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps))
      return(1);

    while(1){
	sprintf(tmp_20k_buf, "Include %s%soriginal message%s in Reply%s%s%s? ",
		(many > 1L) ? comatose(many) : "",
		(many > 1L) ? " " : "",
		(many > 1L) ? "s" : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");

	if(F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
	    rtq_opts[2].ch    = ctrl('R');
	    rtq_opts[2].rval  = 'r';
	    rtq_opts[2].name  = "^R";
	    rtq_opts[2].label = "Edit Indent String";
	}
	else
	  rtq_opts[2].ch    = -1;

	switch(ret = radio_buttons(tmp_20k_buf, 
				   ps->ttyo->screen_rows > 4
				     ? -FOOTER_ROWS(ps_global) : -1,
				   rtq_opts, edited ? 'y' : 'n',
				   'x', NO_HELP, RB_SEQ_SENSITIVE)){
	  case 'x':
	    cmd_cancelled("Reply");
	    return(-1);

	  case 'r':
	    if(prefix && *prefix){
		int  done = 0;
		char buf[64];
		int  flags;

		while(!done){
		    strcpy(buf, *prefix);

		    flags = OE_APPEND_CURRENT |
			    OE_KEEP_TRAILING_SPACE |
			    OE_DISALLOW_HELP |
			    OE_SEQ_SENSITIVE;

		    switch(optionally_enter(buf, ps->ttyo->screen_rows > 4
					    ? -FOOTER_ROWS(ps_global) : -1,
					    0, 63, "Reply prefix : ", 
					    NULL, NO_HELP, &flags)){
		      case 0:		/* entry successful, continue */
			if(flags & OE_USER_MODIFIED){
			    fs_give((void **)prefix);
			    *prefix = removing_quotes(cpystr(buf));
			    edited = 1;
			}

			done++;
			break;

		      case 1:
			cmd_cancelled("Reply");

		      case -1:
			return(-1);

		      case 4:
			EndInverse();
			ClearScreen();
			redraw_titlebar();
			if(ps_global->redrawer != NULL)
			  (*ps_global->redrawer)();

			redraw_keymenu();
			break;

		      case 3:
			break;

		      case 2:
		      default:
			q_status_message(SM_ORDER, 3, 4,
				 "Programmer botch in reply_text_query()");
			return(-1);
		    }
		}
	    }

	    break;

	  case 'y':
	    return(1);

	  case 'n':
	    return(0);

	  default:
	    q_status_message1(SM_ORDER, 3, 4,
			      "Invalid rval \'%s\'", pretty_command(ret));
	    return(-1);
	}
    }
}



/*----------------------------------------------------------------------
  Build the body for the message number/part being replied to

    Args: 

  Result: BODY structure suitable for sending

  ----------------------------------------------------------------------*/
BODY *
reply_body(stream, env, orig_body, msgno, sect_prefix,
	   msgtext, prefix, plustext, template_file, sig_file, redraft_pos)
    MAILSTREAM *stream;
    ENVELOPE   *env;
    BODY       *orig_body;
    long	msgno;
    char       *sect_prefix;
    void       *msgtext;
    char       *prefix;
    int		plustext;
    char       *template_file;
    char       *sig_file;
    REDRAFT_POS_S **redraft_pos;
{
    char     *p, *sig, *section, sect_buf[256];
    BODY     *body = NULL, *tmp_body;
    PART     *part;
    gf_io_t   pc;
    int       impl, template_len = 0, leave_cursor_at_top = 0;

    if(sect_prefix)
      sprintf(section = sect_buf, "%s.1", sect_prefix);
    else
      section = "1";

    gf_set_so_writec(&pc, (STORE_S *) msgtext);

    if(template_file){
	char *filtered;

	impl = 0;
	filtered = detoken_file(template_file, env, 0,
				F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
				0, redraft_pos, &impl);
	if(filtered){
	    if(*filtered){
		so_puts((STORE_S *)msgtext, filtered);
		if(impl == 1)
		  template_len = strlen(filtered);
		else if(impl == 2)
		  leave_cursor_at_top++;
	    }
	    
	    fs_give((void **)&filtered);
	}
	else
	  impl = 1;
    }
    else
      impl = 1;

    if((sig = reply_signature(sig_file, env, redraft_pos, &impl)) &&
       F_OFF(F_SIG_AT_BOTTOM, ps_global)){

	/*
	 * Note that template_len is zero if CURSORPOS happened there.
	 * If impl was 2 from template, offset was already correct.
	 * If it was 1, we add the length of the template.
	 */
	if(impl == 2)
	  (*redraft_pos)->offset += template_len;
	
	if(*sig)
	  so_puts((STORE_S *)msgtext, sig);
	
	fs_give((void **)&sig);
    }

    /*
     * Only put cursor in sig if there is a cursorpos there but not
     * one in the template, and sig-at-bottom.
     */
    if(!(sig && impl == 2 && !leave_cursor_at_top))
      leave_cursor_at_top++;

    if(plustext){
	if(!orig_body
	   || orig_body->type == TYPETEXT
	   || F_OFF(F_ATTACHMENTS_IN_REPLY, ps_global)){
	    /*------ Simple text-only message ----*/
	    body		     = mail_newbody();
	    body->type		     = TYPETEXT;
	    body->contents.text.data = msgtext;
	    reply_delimiter(env, pc);
	    if(F_ON(F_INCLUDE_HEADER, ps_global))
	      reply_forward_header(stream, msgno, sect_prefix,
				   env, pc, prefix);

	    if(reply_body_text(orig_body, &tmp_body)){
		get_body_part_text(stream, tmp_body, msgno,
				   p = body_partno(stream, msgno, tmp_body),
				   pc, prefix);
		fs_give((void **) &p);
	    }
	    else{
		gf_puts(NEWLINE, pc);
		gf_puts("  [NON-Text Body part not included]", pc);
		gf_puts(NEWLINE, pc);
	    }
	}
	else if(orig_body->type == TYPEMULTIPART){
	    /*------ Message is Multipart ------*/
	    if(orig_body->subtype
	       && !strucmp(orig_body->subtype, "alternative")){
		/* Set up the simple text reply */
		body			 = mail_newbody();
		body->type		 = TYPETEXT;
		body->contents.text.data = msgtext;

		if(reply_body_text(orig_body, &tmp_body)){
		    reply_delimiter(env, pc);
		    if(F_ON(F_INCLUDE_HEADER, ps_global))
		      reply_forward_header(stream, msgno, sect_prefix,
					   env, pc, prefix);

		    get_body_part_text(stream, tmp_body, msgno,
				       p = body_partno(stream,msgno,tmp_body),
				       pc, prefix);
		    fs_give((void **) &p);
		}
		else
		  q_status_message(SM_ORDER | SM_DING, 3, 3,
			    "No suitable multipart text found for inclusion!");
	    }
	    else{
		body = copy_body(NULL, orig_body);
		/*
		 * whatever subtype it is, demote it
		 * to plain old MIXED.
		 */
		if(body->subtype)
		  fs_give((void **) &body->subtype);

		body->subtype = cpystr("Mixed");

		if(orig_body->nested.part &&
		   orig_body->nested.part->body.type == TYPETEXT) {
		    /*---- First part of the message is text -----*/
		    body->nested.part->body.contents.text.data = msgtext;
		    reply_delimiter(env, pc);
		    if(F_ON(F_INCLUDE_HEADER, ps_global))
		      reply_forward_header(stream, msgno, sect_prefix,
					   env, pc, prefix);

		    if(!(get_body_part_text(stream,
					    &orig_body->nested.part->body,
					    msgno, section, pc, prefix)
			 && fetch_contents(stream, msgno, sect_prefix, body)))
		      q_status_message(SM_ORDER | SM_DING, 3, 4,
				       "Error including all message parts");
		}
		else {
		    /*--- Fetch the original pieces ---*/
		    if(!fetch_contents(stream, msgno, sect_prefix, body))
		      q_status_message(SM_ORDER | SM_DING, 3, 4,
				       "Error including all message parts");

		    /*--- No text part, create a blank one ---*/
		    part			  = mail_newbody_part();
		    part->next			  = body->nested.part;
		    body->nested.part		  = part;
		    part->body.contents.text.data = msgtext;
		}
	    }
	}
	else{
	    /*---- Single non-text message of some sort ----*/
	    body              = mail_newbody();
	    body->type        = TYPEMULTIPART;
	    part              = mail_newbody_part();
	    body->nested.part = part;
    
	    /*--- The first part, a blank text part to be edited ---*/
	    part->body.type		  = TYPETEXT;
	    part->body.contents.text.data = msgtext;

	    /*--- The second part, what ever it is ---*/
	    part->next    = mail_newbody_part();
	    part          = part->next;
	    part->body.id = generate_message_id();
	    copy_body(&(part->body), orig_body);

	    /*
	     * the idea here is to fetch part into storage object
	     */
	    if(part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
							    NULL,EDIT_ACCESS)){
#if	defined(DOS) && !defined(WIN32)
		mail_parameters(ps_global->mail_stream, SET_GETS,
				(void *)dos_gets); /* fetched to disk */
		append_file = (FILE *)so_text(
				    (STORE_S *) part->body.contents.text.data);

		if(!mail_fetchbody(stream, msgno, section,
				   &part->body.size.bytes))
		  mail_free_body(&body);

		mail_parameters(stream, SET_GETS,(void *)NULL);
		append_file = NULL;
		mail_gc(stream, GC_TEXTS);
#else
		if(p = mail_fetchbody(stream, msgno, section,
				      &part->body.size.bytes)){
		    so_nputs((STORE_S *)part->body.contents.text.data,
			     p, part->body.size.bytes);
		}
		else
		  mail_free_body(&body);
#endif
	    }
	    else
	      mail_free_body(&body);
	}
    }
    else{
	/*--------- No text included --------*/
	body			 = mail_newbody();
	body->type		 = TYPETEXT;
	body->contents.text.data = msgtext;
    }

    if(!leave_cursor_at_top){
	long  cnt = 0L;
	unsigned char c;

	/* rewind and count chars to start of sig file */
	so_seek((STORE_S *)msgtext, 0L, 0);
	while(so_readc(&c, (STORE_S *)msgtext))
	  cnt++;

	if(!*redraft_pos){
	    *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
	    memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
	    (*redraft_pos)->hdrname = cpystr(":");
	}

	/*
	 * If explicit cursor positioning in sig file,
	 * add offset to start of sig file plus offset into sig file.
	 * Else, just offset to start of sig file.
	 */
	(*redraft_pos)->offset += cnt;
    }

    if(sig){
	if(*sig)
	  so_puts((STORE_S *)msgtext, sig);
	
	fs_give((void **)&sig);
    }

    gf_clear_so_writec((STORE_S *) msgtext);

    return(body);
}


/*
 * reply_part - first replyable multipart of a multipart.
 */
int
reply_body_text(body, new_body)
    BODY *body, **new_body;
{
    if(body){
	switch(body->type){
	  case TYPETEXT :
	    *new_body = body;
	    return(1);

	  case TYPEMULTIPART :
	    if(body->subtype && !strucmp(body->subtype, "alternative")){
		PART *part;

		/* Pick out the interesting text piece */
		for(part = body->nested.part; part; part = part->next)
		  if((!part->body.type || part->body.type == TYPETEXT)
		     && (!part->body.subtype
			 || !strucmp(part->body.subtype, "plain"))){
		      *new_body = &part->body;
		      return(1);
		  }
	    }
	    else if(body->nested.part)
	      /* NOTE: we're only interested in "first" part of mixed */
	      return(reply_body_text(&body->nested.part->body, new_body));

	    break;

	  default:
	    break;
	}
    }

    return(0);
}


char *
reply_signature(sig_file, env, redraft_pos, impl)
    char           *sig_file;
    ENVELOPE       *env;
    REDRAFT_POS_S **redraft_pos;
    int            *impl;
{
    char *sig;

    sig = detoken_file(sig_file, env,
		       2, F_ON(F_SIG_AT_BOTTOM, ps_global) ? 0 : 1, 1,
		       redraft_pos, impl);

    if(F_OFF(F_SIG_AT_BOTTOM, ps_global) && (!sig || !*sig)){
	if(sig)
	  fs_give((void **)&sig);

	sig = (char *)fs_get((strlen(NEWLINE) * 2 + 1) * sizeof(char));
	strcpy(sig, NEWLINE);
	strcat(sig, NEWLINE);
	return(sig);
    }

    return(sig);
}


void
get_addr_data(env, type, buf, maxlen)
    ENVELOPE    *env;
    IndexColType type;
    char         buf[];
    int          maxlen;
{
    ADDRESS *addr = NULL;
    ADDRESS *last_to = NULL;
    ADDRESS *first_addr, *second_addr, *third_addr, *fourth_addr;
    int      cntaddr, l, orig_maxlen;
    char    *p;

    switch(type){
      case iFrom:
	addr = env ? env->from : NULL;
	break;

      case iTo:
	addr = env ? env->to : NULL;
	break;

      case iCc:
	addr = env ? env->cc : NULL;
	break;

      case iSender:
	addr = env ? env->sender : NULL;
	break;

      /*
       * Recips is To and Cc togeter. We hook the two adrlists together
       * temporarily.
       */
      case iRecips:
	addr = env ? env->to : NULL;
	/* Find end of To list */
	for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
	  ;
	
	/* Make the end of To list point to cc list */
	if(last_to)
	  last_to->next = (env ? env->cc : NULL);
	  
	break;
    }

    orig_maxlen = maxlen;

    first_addr = addr;
    /* skip over rest of c-client group addr */
    if(first_addr && first_addr->mailbox && !first_addr->host){
	for(second_addr = first_addr->next;
	    second_addr && second_addr->host;
	    second_addr = second_addr->next)
	  ;
	
	if(second_addr && !second_addr->host)
	  second_addr = second_addr->next;
    }
    else
      second_addr = first_addr ? first_addr->next : NULL;

    if(second_addr && second_addr->mailbox && !second_addr->host){
	for(third_addr = second_addr->next;
	    third_addr && third_addr->host;
	    third_addr = third_addr->next)
	  ;
	
	if(third_addr && !third_addr->host)
	  third_addr = third_addr->next;
    }
    else
      third_addr = second_addr ? second_addr->next : NULL;

    if(third_addr && third_addr->mailbox && !third_addr->host){
	for(fourth_addr = third_addr->next;
	    fourth_addr && fourth_addr->host;
	    fourth_addr = fourth_addr->next)
	  ;
	
	if(fourth_addr && !fourth_addr->host)
	  fourth_addr = fourth_addr->next;
    }
    else
      fourth_addr = third_addr ? third_addr->next : NULL;

    /* Just attempting to make a nice display */
    if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
		      (first_addr->mailbox && first_addr->mailbox[0]))){
      if(second_addr){
	if((second_addr->personal && second_addr->personal[0]) ||
	   (second_addr->mailbox && second_addr->mailbox[0])){
	  if(third_addr){
	    if((third_addr->personal && third_addr->personal[0]) ||
	       (third_addr->mailbox && third_addr->mailbox[0])){
	      if(fourth_addr)
	        cntaddr = 4;
	      else
		cntaddr = 3;
	    }
	    else
	      cntaddr = -1;
	  }
	  else
	    cntaddr = 2;
	}
	else
	  cntaddr = -1;
      }
      else
	cntaddr = 1;
    }
    else
      cntaddr = -1;

    p = buf;
    if(cntaddr == 1)
      a_little_addr_string(first_addr, p, maxlen);
    else if(cntaddr == 2){
	a_little_addr_string(first_addr, p, maxlen);
	maxlen -= (l=strlen(p));
	p += l;
	if(maxlen > 7){
	    strcpy(p, " and ");
	    maxlen -= 5;
	    p += 5;
	    a_little_addr_string(second_addr, p, maxlen);
	}
    }
    else if(cntaddr == 3){
	a_little_addr_string(first_addr, p, maxlen);
	maxlen -= (l=strlen(p));
	p += l;
	if(maxlen > 7){
	    strcpy(p, ", ");
	    maxlen -= 2;
	    p += 2;
	    a_little_addr_string(second_addr, p, maxlen);
	    maxlen -= (l=strlen(p));
	    p += l;
	    if(maxlen > 7){
		strcpy(p, ", and ");
		maxlen -= 6;
		p += 6;
		a_little_addr_string(third_addr, p, maxlen);
	    }
	}
    }
    else if(cntaddr > 3){
	a_little_addr_string(first_addr, p, maxlen);
	maxlen -= (l=strlen(p));
	p += l;
	if(maxlen > 7){
	    strcpy(p, ", ");
	    maxlen -= 2;
	    p += 2;
	    a_little_addr_string(second_addr, p, maxlen);
	    maxlen -= (l=strlen(p));
	    p += l;
	    if(maxlen > 7){
		strcpy(p, ", ");
		maxlen -= 2;
		p += 2;
		a_little_addr_string(third_addr, p, maxlen);
		maxlen -= (l=strlen(p));
		p += l;
		if(maxlen >= 12)
		  strcpy(p, ", and others");
		else if(maxlen >= 3)
		  strcpy(p, "...");
	    }
	}
    }
    else if(addr){
	char *a_string;

	a_string = addr_list_string(addr, NULL, 0, 0);
	istrncpy(buf, a_string, maxlen);

	fs_give((void **)&a_string);
    }

    if(last_to)
      last_to->next = NULL;

    buf[orig_maxlen] = '\0';
}


void
get_news_data(env, type, buf, maxlen)
    ENVELOPE    *env;
    IndexColType type;
    char         buf[];
    int          maxlen;
{
    int      cntnews = 0, orig_maxlen;
    char    *news = NULL, *p, *q;

    switch(type){
      case iNews:
      case iNewsAndTo:
      case iToAndNews:
      case iNewsAndRecips:
      case iRecipsAndNews:
	news = env ? env->newsgroups : NULL;
	break;
    }

    orig_maxlen = maxlen;

    if(news){
	q = news;
	while(isspace(*q))
	  q++;

	if(*q)
	  cntnews++;
	
	while((q = strindex(q, ',')) != NULL){
	    q++;
	    while(isspace(*q))
	      q++;
	    
	    if(*q)
	      cntnews++;
	    else
	      break;
	}
    }

    if(cntnews == 1){
	istrncpy(buf, news, maxlen);
	buf[maxlen] = '\0';
	removing_leading_and_trailing_white_space(buf);
    }
    else if(cntnews == 2){
	p = buf;
	q = news;
	while(isspace(*q))
	  q++;
	
	while(maxlen > 0 && *q && !isspace(*q) && *q != ','){
	    *p++ = *q++;
	    maxlen--;
	}
	
	if(maxlen > 7){
	    strncpy(p, " and ", maxlen);
	    p += 5;
	    maxlen -= 5;
	}

	while(isspace(*q) || *q == ',')
	  q++;

	while(maxlen > 0 && *q && !isspace(*q) && *q != ','){
	    *p++ = *q++;
	    maxlen--;
	}

	*p = '\0';

	istrncpy(tmp_20k_buf, buf, 10000);
	strncpy(buf, tmp_20k_buf, orig_maxlen);
    }
    else if(cntnews > 2){
	char b[100];

	p = buf;
	q = news;
	while(isspace(*q))
	  q++;
	
	while(maxlen > 0 && *q && !isspace(*q) && *q != ','){
	    *p++ = *q++;
	    maxlen--;
	}
	
	*p = '\0';
	sprintf(b, " and %d other newsgroups", cntnews-1);
	if(maxlen >= strlen(b))
	  strcpy(p, b);
	else if(maxlen >= 3)
	  strcpy(p, "...");

	istrncpy(tmp_20k_buf, buf, 10000);
	strncpy(buf, tmp_20k_buf, orig_maxlen);
    }

    buf[orig_maxlen] = '\0';
}


void
a_little_addr_string(addr, buf, maxlen)
    ADDRESS *addr;
    char     buf[];
    int      maxlen;
{
    char *dummy = NULL;
    int   l;

    if(addr){
	if(addr->personal && addr->personal[0]){
	    istrncpy(buf, (char *)rfc1522_decode((unsigned char *)tmp_20k_buf,
						 addr->personal, &dummy),
		     maxlen);

	    if(dummy)
	      fs_give((void **)&dummy);
	}
	else if(addr->mailbox && addr->mailbox[0]){
	    strncpy(buf, addr->mailbox, maxlen);
	    buf[maxlen] = '\0';
	    if(addr->host && addr->host[0] && addr->host[0] != '.'){
		maxlen -= (l = (strlen(buf)+1));
		strncat(buf, "@", maxlen-l);
		strncat(buf, addr->host, maxlen-l);
	    }
	}
    }
}


char *
get_reply_data(env, type, buf, maxlen)
    ENVELOPE    *env;
    IndexColType type;
    char         buf[];
    int          maxlen;
{
    struct date  d;
    int          orig_maxlen;
    char        *space = NULL;
    IndexColType addrtype;

    buf[0] = '\0';

    switch(type){
      case iRDate:
      case iSDate:
      case iS1Date:
      case iS2Date:
      case iS3Date:
      case iS4Date:
      case iTime24:
      case iTime12:
      case iDay:
      case iMonAbb:
      case iMonLong:
      case iMon:
      case iYear:
      case iDate:
      case iLDate:
	if(env && env->date && env->date[0] && maxlen >= 20)
	  date_str(env->date, type, 1, buf);

	break;

      case iCurDate:
	if(maxlen >= 20)
	  date_str(NULL, type, 1, buf);

	break;

      case iCurTime24:
      case iCurTime12:
	if(maxlen >= 20)
	  date_str(NULL, type, 1, buf);

	break;

      case iFrom:
      case iTo:
      case iCc:
      case iSender:
      case iRecips:
	get_addr_data(env, type, buf, maxlen);
	break;

      case iAddress:
      case iMailbox:
	if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
	   strlen(env->from->mailbox) <= maxlen){
	    strcpy(buf, env->from->mailbox);
	    if(type == iAddress &&
	       env->from->host &&
	       env->from->host[0] &&
	       env->from->host[0] != '.' &&
	       strlen(env->from->mailbox) +
		   strlen(env->from->host) + 1 <= maxlen){
		strcat(buf, "@");
		strcat(buf, env->from->host);
	    }
	}

	break;

      case iNews:
	get_news_data(env, type, buf, maxlen);
	break;

      case iToAndNews:
      case iNewsAndTo:
      case iRecipsAndNews:
      case iNewsAndRecips:
	if(type == iToAndNews || type == iNewsAndTo)
	  addrtype = iTo;
	else
	  addrtype = iRecips;

	if(env && env->newsgroups){
	    space = (char *)fs_get((maxlen+1) * sizeof(char));
	    get_news_data(env, type, space, maxlen);
	}

	get_addr_data(env, addrtype, buf, maxlen);

	if(space && *space && *buf){
	    if(strlen(space) + strlen(buf) + 5 > maxlen){
		if(strlen(space) > maxlen/2)
		  get_news_data(env, type, space, maxlen - strlen(buf) - 5);
		else
		  get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
	    }

	    if(type == iToAndNews || type == iRecipsAndNews)
	      sprintf(tmp_20k_buf, "%s and %s", buf, space);
	    else
	      sprintf(tmp_20k_buf, "%s and %s", space, buf);

	    strncpy(buf, tmp_20k_buf, maxlen);
	}
	else if(space && *space)
	  strncpy(buf, space, maxlen);
	
	if(space)
	  fs_give((void **)&space);

	break;

      case iSubject:
	if(env && env->subject){
	    long n;
	    unsigned char *p, *tmp = NULL;
	    char  *dummy = NULL;

	    if((n = (long)strlen(env->subject)) >= 20000)
	      p = tmp = (unsigned char *)fs_get((size_t)(n + 1)*sizeof(char));
	    else
	      p = (unsigned char *)tmp_20k_buf;
	  
	    istrncpy(buf, (char *)rfc1522_decode(p, env->subject, &dummy),
		     maxlen);

	    buf[maxlen] = '\0';
	    if(dummy)
	      fs_give((void **)&dummy);
	    if(tmp)
	      fs_give((void **)&tmp);
	}

	break;
    
      case iMsgID:
	if(env && env->message_id)
	  strncpy(buf, env->message_id, maxlen);

	break;
    
      default:
	break;
    }

    buf[maxlen] = '\0';
    return(buf);
}


/*
 * reply_delimiter - output formatted reply delimiter for given envelope
 *		     with supplied character writing function.
 */
void
reply_delimiter(env, pc)
    ENVELOPE *env;
    gf_io_t   pc;
{
#define MAX_DELIM 2000
    char           buf[MAX_DELIM+1];
    char          *p;

    strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
    buf[MAX_DELIM] = '\0';
    /* preserve exact default behavior from before */
    if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
	struct date d;

	parse_date(env->date, &d);
	gf_puts("On ", pc);			/* All delims have... */
	if(d.wkday != -1){			/* "On day, date month year" */
	    gf_puts(week_abbrev(d.wkday), pc);	/* in common */
	    gf_puts(", ", pc);
	}

	gf_puts(int2string(d.day), pc);
	(*pc)(' ');
	gf_puts(month_abbrev(d.month), pc);
	(*pc)(' ');
	gf_puts(int2string(d.year), pc);

	/* but what follows, depends */
	if(!env->from || (env->from->host && env->from->host[0] == '.')){
	    gf_puts(", it was written:", pc);
	}
	else if (env->from->personal) {
	    gf_puts(", ", pc);
	    gf_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
					    env->from->personal, NULL), pc);
	    gf_puts(" wrote:", pc);
	}
	else {
	    (*pc)(' ');
	    gf_puts(env->from->mailbox, pc);
	    if(env->from->host){
		(*pc)('@');
		gf_puts(env->from->host, pc);
	    }

	    gf_puts(" wrote:", pc);
	}
    }
    else{
	char *filtered;
	int   len;

	filtered = detoken_src(buf, FOR_REPLY_INTRO, env, NULL, NULL);

	/* try to truncate if too long */
	if(filtered && (len = strlen(filtered)) > 80){
	    int ended_with_colon = 0;
	    int ended_with_quote = 0;
	    int ended_with_quote_colon = 0;

	    if(filtered[len-1] == ':'){
		int prevchar;

		ended_with_colon = ':';
		if(filtered[len-2] == QUOTE || filtered[len-2] == '\'')
		  ended_with_quote_colon = filtered[len-2];
	    }
	    else if(filtered[len-1] == QUOTE || filtered[len-1] == '\'')
	      ended_with_quote = filtered[len-1];

	    /* try to find space to break at */
	    for(p = &filtered[75]; p > &filtered[60] && !isspace(*p); p--)
	      ;
	    
	    if(!isspace(*p))
	      p = &filtered[75];

	    *p++ = '.';
	    *p++ = '.';
	    *p++ = '.';
	    if(ended_with_quote_colon){
		*p++ = ended_with_quote_colon;
		*p++ = ':';
	    }
	    else if(ended_with_colon)
	      *p++ = ended_with_colon;
	    else if(ended_with_quote)
	      *p++ = ended_with_quote;

	    *p = '\0';
	}

	if(filtered){
	    if(*filtered)
	      gf_puts(filtered, pc);
	    
	    fs_give((void **)&filtered);
	}
    }

    gf_puts(NEWLINE, pc);			/* and end with two newlines */
    gf_puts(NEWLINE, pc);
}


/*
 * reply_poster_followup - return TRUE if "followup-to" set to "poster"
 *
 * NOTE: queues status message indicating such
 */
int
reply_poster_followup(e)
    ENVELOPE *e;
{
    if(e && e->followup_to && !strucmp(e->followup_to, "poster")){
	q_status_message(SM_ORDER, 2, 3,
			 "Replying to Poster as specified in \"Followup-To\"");
	return(1);
    }

    return(0);
}



/*
 * reply_news_test - Test given envelope for newsgroup data and copy
 *		     it at the users request
 *	RETURNS:
 *	     0  if error or cancel
 *	     1     reply via email
 *           2     follow-up via news
 *	     3     do both
 */
int
reply_news_test(env, outgoing)
    ENVELOPE *env, *outgoing;
{
    int    ret = 1;
    static ESCKEY_S news_opt[] = { {'f', 'f', "F", "Follow-up"},
				   {'r', 'r', "R", "Reply"},
				   {'b', 'b', "B", "Both"},
				   {-1, 0, NULL, NULL} };

    if(env->newsgroups && *env->newsgroups && !reply_poster_followup(env))
      /*
       * Now that we know a newsgroups field is present, 
       * ask if the user is posting a follow-up article...
       */
      switch(radio_buttons(NEWS_PMT, -FOOTER_ROWS(ps_global),
			   news_opt, 'r', 'x', NO_HELP, RB_NORM)){
	case 'r' :		/* Reply */
	  ret = 1;
	  break;

	case 'f' :		/* Follow-Up via news ONLY! */
	  ret = 2;
	  break;

	case 'b' :		/* BOTH */
	  ret = 3;
	  break;

	case 'x' :		/* cancel or unknown response */
	default  :
	  cmd_cancelled("Reply");
	  ret = 0;
	  break;
      }

    if(ret > 1){
	if(env->followup_to){
	    q_status_message(SM_ORDER, 2, 3,
			     "Posting to specified Followup-To groups");
	    outgoing->newsgroups = cpystr(env->followup_to);
	}
	else if(!outgoing->newsgroups)
	  outgoing->newsgroups = cpystr(env->newsgroups);

	outgoing->references = reply_build_refs(env);
    }

    return(ret);
}


/*
 * Get the contents of file, detokenize them, and return a string.
 *
 * Args    file -- The file to get the contents from. Usually this is
 *		   relative to the pinerc dir.
 *          env -- The envelope to use for detokenizing. May be NULL.
 *  prenewlines -- How many blank lines should be included at start.
 * postnewlines -- How many blank lines should be included after.
 *    sigdashes -- Should sigdashes be included (only if the option is
 *		   turned on, too).
 *  redraft_pos -- This is a return value. If it is non-NULL coming in,
 *		   then the cursor position is returned here.
 *         impl -- This is a combination argument which is both an input
 *		   argument and a return value. If it is non-NULL and = 0,
 *		   that means that we want the cursor position returned here,
 *		   even if that position is set implicitly to the end of
 *		   the output string. If it is = 1 coming in, that means
 *		   we only want the cursor position to be set if it is set
 *		   explicitly. If it is 2, or if redraft_pos is NULL,
 *                 we don't set it at all.
 *		   If the cursor position gets set explicitly by a
 *		   _CURSORPOS_ token in the file then this is set to 2
 *		   on return. If the cursor position is set implicitly to
 *		   the end of the included file, then this is set to 1
 *                 on return.
 *
 * Returns -- An allocated string is returned.
 */
char *
detoken_file(file, env, prenewlines, postnewlines, sigdashes, redraft_pos, impl)
    char           *file;
    ENVELOPE       *env;
    int             prenewlines, postnewlines, sigdashes;
    REDRAFT_POS_S **redraft_pos;
    int            *impl;
{
    char *ret = NULL, *p;

    if(file){
	if((p = get_signature(file, prenewlines, postnewlines, sigdashes)) !=
									NULL){
	    if(*p)
	      ret = detoken_src(p, FOR_TEMPLATE, env, redraft_pos, impl);

	    fs_give((void **)&p);
	}
    }

    return(ret);
}


/*
 * Filter the source string from the template file and return an allocated
 * copy of the result with text replacements for the tokens.
 * Fill in offset in redraft_pos.
 *
 * This is really inefficient but who cares? It's just cpu time.
 */
char *
detoken_src(src, for_what, env, redraft_pos, impl)
    char           *src;
    int             for_what;
    ENVELOPE       *env;
    REDRAFT_POS_S **redraft_pos;
    int            *impl;
{
    long  figure_size;
    int   loopcnt = 25;		/* just in case, avoid infinite loop */
    char *ret, *str1, *str2;
    int   done = 0;

    if(!src)
      return(src);
    
    /*
     * We keep running it through until it stops changing so user can
     * nest calls to token stuff.
     */
    str1 = src;
    do {
	/* short-circuit if no chance it will change */
	if(strpbrk(str1, "_\\")){
	    figure_size = -1L;	/* tell detokenize to figure out size needed */
	    (void)detoken_guts(str1, for_what, env, NULL, &figure_size, 1,NULL);
	    figure_size = max(0L, figure_size);
	    str2 = detoken_guts(str1, for_what, env, NULL, &figure_size,1,NULL);
	}
	else
	  str2 = str1;

	if(str1 && str2 && (str1 == str2 || !strcmp(str1, str2))){
	    done++;				/* It stopped changing */
	    if(str1 && str1 != src && str1 != str2)
	      fs_give((void **)&str1);
	}
	else{					/* Still changing */
	    if(str1 && str1 != src && str1 != str2)
	      fs_give((void **)&str1);
	    
	    str1 = str2;
	}

    } while(str2 && !done && loopcnt-- > 0);

    /*
     * Have to run it through once more to get the redraft_pos.
     */
    if((str2 && strpbrk(str2, "_\\")) ||
       (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
	figure_size = -1L;
	(void)detoken_guts(str2, for_what, env, redraft_pos, &figure_size,
			   0, impl);
	figure_size = max(0L, figure_size);
	ret = detoken_guts(str2, for_what, env, redraft_pos, &figure_size,
			   0, impl);
	if(str2 != src)
	  fs_give((void **)&str2);
    }
    else if(str2){
	if(str2 == src)
	  ret = cpystr(str2);
	else
	  ret = str2;
    }

    return(ret);
}


/*
 * The guts of the detokenizing routines. Filter the src string looking for
 * tokens and replace them with the appropriate text. In the case of the
 * cursor_pos token we set redraft_pos instead.
 *
 * Args        src --  The source string
 *        for_what --  
 *             env --  Envelope to look in for token replacements.
 *     redraft_pos --  Return the redraft offset here, if non-zero.
 *     figure_size --  This is both an input value and a return value
 *                     If set to -1, this function will just figure out
 *                     how large a buffer it needs and return that in
 *                     figure_size. If > 0, that space will be allocated
 *                     here and used to put the answer in.
 * leave_cursorpos --  This is a flag to tell detoken_guts not to do
 *                     the replacement for _CURSORPOS_, just leave it as is.
 *                     We need this because we want to defer the cursor
 *                     placement until the very last call to detoken, since
 *                     otherwise we'd have to keep track of the cursor
 *                     position as subsequent text replacements (nested)
 *                     take place.
 *         impl -- This is a combination argument which is both an input
 *		   argument and a return value. If it is non-NULL and 0
 *		   coming in, that means that we should set redraft_pos,
 *		   even if that position is set implicitly to the end of
 *		   the output string. If it is 1 coming in, that means
 *		   we only want the cursor position to be set if it is set
 *		   explicitly. If it is 2 coming in (or if
 *                 either redraft_pos is NULL or *figure_size = -1) then
 *                 we don't set it at all.
 *		   If the cursor position gets set explicitly by a
 *		   _CURSORPOS_ token in the file then this is set to 2
 *		   on return. If the cursor position is set implicitly to
 *		   the end of the included file, then this is set to 1
 *                 on return.
 *
 * Returns   NULL if *figure_size = -1. Real return is in figure_size.
 *          else, pointer to alloced result
 */
char *
detoken_guts(src, for_what, env, redraft_pos, figure_size, leave_cursorpos,impl)
    char           *src;
    int             for_what;
    ENVELOPE       *env;
    REDRAFT_POS_S **redraft_pos;
    long           *figure_size;
    int             leave_cursorpos;
    int            *impl;
{
#define MAXSUB 500
    char *p, *q, *dst = NULL;
    char no_us[31], subbuf[MAXSUB+1], *v, *w, *repl;
    INDEX_PARSE_T *pt;
    extern INDEX_PARSE_T itokens[];
    long l, cnt = 0;
    struct date now;
    int countit = 0;

    if(*figure_size == -1L)
      countit++;

    if(!src){
	if(countit)
	  *figure_size = cnt;

	return(NULL);
    }

    if(!countit)
      q = dst = (char *)fs_get((*figure_size + 1) * sizeof(char));

    /*
     * The tokens we look for begin with _. _ may be escaped with \ and
     * \ may be escaped, too.
     * Tokens like _word_ are replaced with the appropriate text if
     * word is recognized. If _word_ is followed immediately by a left paren
     * it is an if-else thingie. _word_(if_text,else_text) means to
     * replace that with either the if_text or else_text depending on whether
     * _word_ (without the paren) would cause any text to be produced.
     */
    p = src;
    while(*p){
	switch(*p){
	  case '_':		/* possible start of token */
	    /*
	     * itokens doesn't include the underscores, so we have to
	     * strip those off before comparing.
	     */
	    for(v = p+1, w = no_us; *v && *v != '_' && w < no_us+30;)
	      *w++ = *v++;
	    
	    *w = '\0';

	    for(pt = itokens; pt->name; pt++)
	      if(pt->what_for & FOR_TEMPLATE && !strucmp(pt->name, no_us))
		break;
	    
	    if(pt->name){	/* recognized the token */
		char *free_this = NULL;

		p += (strlen(pt->name) + 2);	/* skip over token */

		repl = subbuf;
		subbuf[0] = '\0';

		if(pt->ctype == iCursorPos){
		    if(leave_cursorpos){
			subbuf[0] = '_';
			strcpy(subbuf+1, pt->name);
			strcat(subbuf, "_");
		    }

		    if(!countit){
			*q = '\0';
			l = strlen(dst);
			if(redraft_pos && l && impl && *impl != 2){
			    if(!*redraft_pos){
				*redraft_pos =
				 (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
				memset((void *)*redraft_pos, 0,
				       sizeof(**redraft_pos));
				(*redraft_pos)->hdrname = cpystr(":");
			    }

			    (*redraft_pos)->offset  = l;
			    *impl = 2;	/* set explicitly */
			}
		    }
		}
		/* these are all from the envelope */
		else if(pt->what_for & FOR_REPLY_INTRO)
		  repl = get_reply_data(env, pt->ctype, subbuf, MAXSUB);

		if(*p == LPAREN){		/* if-else construct */
		    char *skip_ahead;

		    repl = free_this = handle_if_token(repl, p, &skip_ahead);
		    p = skip_ahead;
		}

		if(repl && repl[0]){
		    if(countit)
		      cnt += (long)strlen(repl);
		    else{
			strcpy(q, repl);
			q += strlen(repl);
		    }
		}

		if(free_this)
		  fs_give((void **)&free_this);
	    }
	    else{	/* unrecognized token, treat it just like text */
		if(countit)
		  cnt++;
		else
		  *q++ = *p;

		p++;
	    }

	    break;

	  case BSLASH:		/* escape next char if special */
	    if(*(p+1) && (*(p+1) == BSLASH || *(p+1) == '_')){
		p++;
		if(*p){
		    if(countit)
		      cnt++;
		    else
		      *q++ = *p;
		    
		    p++;
		}

		break;
	    }
	    /* else, fall through */

	  default:
	    if(countit)
	      cnt++;
	    else
	      *q++ = *p;	/* copy the character */

	    p++;
	    break;
	}
    }

    if(!countit)
      *q = '\0';

    if(countit)
      *figure_size = cnt;

    /*
     * Set redraft_pos to character following the template, unless
     * it has already been set.
     */
    if(!countit && dst && impl && *impl == 0 && redraft_pos && !*redraft_pos){
	*redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
	memset((void *)*redraft_pos, 0, sizeof(**redraft_pos));
	(*redraft_pos)->offset  = strlen(dst);
	(*redraft_pos)->hdrname = cpystr(":");
	*impl = 1;
    }

    return(dst);
}


/*
 * Do the if-else part of the detokenization for one case of if-else.
 * The input src should like (match_this, if_matched, else)...
 * 
 * Args use_if -- Use the "if" part for the answer, else use the "else" part.
 *         src -- The source string beginning with the left paren.
 *  skip_ahead -- Tells caller how long the (...) part was so caller can
 *                  skip over that part of the source.
 *
 * Returns -- an allocated string which is the answer, or NULL if nothing.
 */
char *
handle_if_token(expands_to, src, skip_ahead)
    char  *expands_to;
    char  *src;
    char **skip_ahead;
{
    char *ret = NULL;
    char *skip_to;
    char *match_this, *if_matched, *else_part;

    if(skip_ahead)
      *skip_ahead = src;

    if(!src || *src != LPAREN){
	dprint(1, (debugfile,"botch calling handle_if_token, missing paren\n"));
	return(ret);
    }

    if(!*++src){
	q_status_message(SM_ORDER, 3, 3,
	    "Unexpected end of token string in Reply-LeadIn, Sig, or template");
	return(ret);
    }

    match_this = get_token_arg(src, &skip_to);
    if(!match_this)
      match_this = cpystr("");
    if(!expands_to)
      expands_to = "";

    src = skip_to;
    while(src && *src && (isspace(*src) || *src == ','))
      src++;

    if_matched = get_token_arg(src, &skip_to);
    src = skip_to;
    while(src && *src && (isspace(*src) || *src == ','))
      src++;

    else_part = get_token_arg(src, &skip_to);
    src = skip_to;
    while(src && *src && *src != RPAREN)
      src++;

    if(src && *src == RPAREN)
      src++;

    if(skip_ahead)
      *skip_ahead = src;

    if(!strcmp(match_this, expands_to)){
	ret = if_matched;
	if(else_part)
	  fs_give((void **)&else_part);
    }
    else{
	ret = else_part;
	if(if_matched)
	  fs_give((void **)&if_matched);
    }

    fs_give((void **)&match_this);

    return(ret);
}


char *
get_token_arg(src, skip_to)
    char  *src;
    char **skip_to;
{
    int   quotes = 0, done = 0;
    char *ret = NULL, *p;

    while(*src && isspace(*src))	/* skip space before string */
      src++;

    if(*src == RPAREN){
	if(skip_to)
	  *skip_to = src;

	return(ret);
    }

    p = ret = (char *)fs_get((strlen(src) + 1) * sizeof(char));
    while(!done){
	switch(*src){
	  case QUOTE:
	    if(++quotes == 2)
	      done++;

	    src++;
	    break;

	  case BSLASH:	/* don't count \" as a quote, just copy */
	    if(*(src+1) == BSLASH || *(src+1) == QUOTE){
		src++;  /* skip backslash */
		*p++ = *src++;
	    }
	    else
	      src++;

	    break;
	
	  case SPACE:
	  case TAB:
	  case RPAREN:
	  case COMMA:
	    if(quotes)
	      *p++ = *src++;
	    else
	      done++;
	    
	    break;

	  case '\0':
	    done++;
	    break;

	  default:
	    *p++ = *src++;
	    break;
	}
    }

    *p = '\0';
    if(skip_to)
      *skip_to = src;
    
    return(ret);
}


void
free_redraft_pos(redraft_pos)
    REDRAFT_POS_S **redraft_pos;
{
    if(redraft_pos && *redraft_pos){
	if((*redraft_pos)->hdrname)
	  fs_give((void **)&(*redraft_pos)->hdrname);
	
	fs_give((void **)redraft_pos);
    }
}


/*
 * forward_delimiter - return delimiter for forwarded text
 */
void
forward_delimiter(pc)
    gf_io_t pc;
{
    gf_puts(NEWLINE, pc);
    gf_puts("---------- Forwarded message ----------", pc);
    gf_puts(NEWLINE, pc);
}



/*----------------------------------------------------------------------
    Wrapper for header formatting tool

    Args: stream --
	  msgno --
	  env --
	  pc --
	  prefix --

  Result: header suitable for reply/forward text written using "pc"

  ----------------------------------------------------------------------*/
void
reply_forward_header(stream, msgno, part, env, pc, prefix)
    MAILSTREAM *stream;
    long	msgno;
    char       *part;
    ENVELOPE   *env;
    gf_io_t	pc;
    char       *prefix;
{
    int rv;
    HEADER_S h;

    HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except,
	    FE_DEFAULT & ~FE_BCC);
    if(rv = format_header(stream, msgno, part, env, &h, prefix, 0, pc)){
	if(rv == 1)
	  gf_puts("  [Error fetching message header data]", pc);
    }
    else{
	if(prefix)			/* prefix to write? */
	  gf_puts(prefix, pc);

	gf_puts(NEWLINE, pc);		/* write header delimiter */
    }
}
    


/*----------------------------------------------------------------------
       Partially set up message to forward and pass off to composer/mailer

    Args: pine_state -- The usual pine structure

  Result: outgoing envelope and body created and passed off to composer/mailer

   Create the outgoing envelope for the mail being forwarded, which is 
not much more than filling in the subject, and create the message body
of the outgoing message which requires formatting the header from the
envelope of the original messasge.
  ----------------------------------------------------------------------*/
void
forward(ps)
    struct pine *ps;
{
    char	  *sig;
    int		   ret;
    long	   msgno, totalmsgs;
    ENVELOPE	  *env, *outgoing;
    BODY	  *orig_body, *body = NULL;
    void	  *msgtext = NULL;
    gf_io_t	   pc;
    int            impl, template_len = 0;
    REDRAFT_POS_S *redraft_pos = NULL;
    PAT_HANDLE    *pattern_h;
    ROLE_ACTION_S *role = NULL;
#if	defined(DOS) && !defined(_WINDOWS)
    char	  *reserve;
#endif

    dprint(4, (debugfile, "\n - forward -\n"));

    outgoing              = mail_newenvelope();
    outgoing->message_id  = generate_message_id();

    if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
	sprintf(tmp_20k_buf, "%s forwarded messages...", comatose(totalmsgs));
	outgoing->subject = cpystr(tmp_20k_buf);
    }
    else{
	/*---------- Get the envelope of message we're forwarding ------*/
	msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
	if(!((env = mail_fetchstructure(ps->mail_stream, msgno, NULL))
	     && (outgoing->subject = forward_subject(env, 0)))){
	    q_status_message1(SM_ORDER,3,4,
			      "Error fetching message %s. Can't forward it.",
			      long2string(msgno));
	    goto clean;
	}
    }

    /*
     * as with all text bound for the composer, build it in 
     * a storage object of the type it understands...
     */
    if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Error allocating message text");
	goto clean;
    }

    ret = (totalmsgs > 1L)
	   ? want_to("Forward messages as a MIME digest", 'y', 'x',
		     NO_HELP, WT_SEQ_SENSITIVE)
	   : (ps->full_header)
	     ? want_to("Forward message as an attachment", 'n', 'x',
		       NO_HELP, WT_SEQ_SENSITIVE)
	     : 0;

    if(ret == 'x'){
	cmd_cancelled("Forward");
	so_give((STORE_S **)&msgtext);
	goto clean;
    }

    /* Setup possible role */
    if(!ps->anonymous && (pattern_h = open_nonempty_patterns()) != NULL){
	role = (totalmsgs == 1) ? set_role_from_msg(ps, ROLE_FORWARD,
						    pattern_h, msgno, NULL)
				: NULL;
	if(confirm_role(ROLE_FORWARD, pattern_h, &role))
	  role = combine_inherited_role(role, pattern_h);
	else{				/* cancel reply */
	    role = NULL;
	    close_patterns(&pattern_h);
	    cmd_cancelled("Forward");
	    so_give((STORE_S **)&msgtext);
	    goto clean;
	}

	close_patterns(&pattern_h);
    }

    if(role)
      q_status_message1(SM_ORDER, 3, 4,
			"Forwarding using role \"%s\"", role->nick);

    if(role && role->template){
	char *filtered;

	impl = 1;
	filtered = detoken_file(role->template,
				(totalmsgs == 1L) ? env : NULL,
				0, 0, 0, &redraft_pos, &impl);
	if(filtered){
	    if(*filtered){
		so_puts((STORE_S *)msgtext, filtered);
		if(impl == 1)
		  template_len = strlen(filtered);
	    }
	    
	    fs_give((void **)&filtered);
	}
    }
    else
      impl = 1;
     
    if(sig = detoken_file((role && role->sig)
			     ? role->sig
			     : ps_global->VAR_SIGNATURE_FILE,
			  NULL, 2, 0, 1, &redraft_pos, &impl)){
	if(impl == 2)
	  redraft_pos->offset += template_len;

	so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);

	fs_give((void **)&sig);
    }
    else
      so_puts((STORE_S *)msgtext, NEWLINE);

    gf_set_so_writec(&pc, (STORE_S *)msgtext);

#if	defined(DOS) && !defined(_WINDOWS)
#if	defined(LWP) || defined(PCTCP) || defined(PCNFS)
#define	IN_RESERVE	8192
#else
#define	IN_RESERVE	16384
#endif
    if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
	gf_clear_so_writec((STORE_S *) msgtext);
	so_give((STORE_S **)&msgtext);
        q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Insufficient memory for message text");
	goto clean;
    }
#endif

    /*
     * If we're forwarding multiple messages *or* the forward-as-mime
     * is turned on and the users wants it done that way, package things
     * up...
     */
    if(ret == 'y'){			/* attach message[s]!!! */
	PART **pp;
	long   totalsize = 0L;

	/*---- New Body to start with ----*/
        body	   = mail_newbody();
        body->type = TYPEMULTIPART;

        /*---- The TEXT part/body ----*/
        body->nested.part                       = mail_newbody_part();
        body->nested.part->body.type            = TYPETEXT;
        body->nested.part->body.contents.text.data = msgtext;

	if(totalmsgs > 1L){
	    /*---- The MULTIPART/DIGEST part ----*/
	    body->nested.part->next		  = mail_newbody_part();
	    body->nested.part->next->body.type	  = TYPEMULTIPART;
	    body->nested.part->next->body.subtype = cpystr("Digest");
	    sprintf(tmp_20k_buf, "Digest of %s messages", comatose(totalmsgs));
	    body->nested.part->next->body.description = cpystr(tmp_20k_buf);
	    pp = &(body->nested.part->next->body.nested.part);
	}
	else
	  pp = &(body->nested.part->next);

	/*---- The Message body subparts ----*/
	for(msgno = mn_first_cur(ps->msgmap);
	    msgno > 0L;
	    msgno = mn_next_cur(ps->msgmap)){

	    msgno = mn_m2raw(ps->msgmap, msgno);
	    env   = mail_fetchstructure(ps->mail_stream, msgno, NULL);

	    if(forward_mime_msg(ps->mail_stream,msgno,NULL,env,pp,msgtext)){
		totalsize += (*pp)->body.size.bytes;
		pp = &((*pp)->next);
	    }
	    else
	    goto bomb;
	}

	if(totalmsgs > 1L)
	  body->nested.part->next->body.size.bytes = totalsize;
    }
    else if(totalmsgs > 1L){
	int		        warned = 0;
	body                  = mail_newbody();
	body->type            = TYPETEXT;
	body->contents.text.data = msgtext;
	env		      = NULL;

	for(msgno = mn_first_cur(ps->msgmap);
	    msgno > 0L;
	    msgno = mn_next_cur(ps->msgmap)){

	    if(env){			/* put 2 between messages */
		gf_puts(NEWLINE, pc);
		gf_puts(NEWLINE, pc);
	    }

	    /*--- Grab current envelope ---*/
	    env = mail_fetchstructure(ps->mail_stream,
				      mn_m2raw(ps->msgmap, msgno),
				      &orig_body);
	    if(!env || !orig_body){
		q_status_message1(SM_ORDER,3,4,
			       "Error fetching message %s. Can't forward it.",
			       long2string(msgno));
		goto bomb;
	    }

	    if(orig_body == NULL || orig_body->type == TYPETEXT) {
		if(!ps->anonymous){
		    forward_delimiter(pc);
		    reply_forward_header(ps->mail_stream,
					 mn_m2raw(ps->msgmap, msgno),
					 NULL, env, pc, "");
		}

		if(!get_body_part_text(ps->mail_stream, orig_body,
				       mn_m2raw(ps->msgmap, msgno),
				       "1", pc, NULL))
		  goto bomb;
	    } else if(orig_body->type == TYPEMULTIPART) {
		if(!warned++)
		  q_status_message(SM_ORDER,3,7,
		    "WARNING!  Attachments not included in multiple forward.");

		if(orig_body->nested.part &&
		   orig_body->nested.part->body.type == TYPETEXT) {
		    /*---- First part of the message is text -----*/
		    forward_delimiter(pc);
		    reply_forward_header(ps->mail_stream,
					 mn_m2raw(ps->msgmap,msgno),
					 NULL, env, pc, "");

		    if(!get_body_part_text(ps->mail_stream,
					   &orig_body->nested.part->body,
					   mn_m2raw(ps->msgmap, msgno),
					   "1", pc, NULL))
		      goto bomb;
		} else {
		    q_status_message(SM_ORDER,0,3,
				     "Multipart with no leading text part!");
		}
	    } else {
		/*---- Single non-text message of some sort ----*/
		q_status_message(SM_ORDER,0,3,
				 "Non-text message not included!");
	    }
	}
    }
    else if(!((env = mail_fetchstructure(ps->mail_stream, msgno, &orig_body))
	      && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
				      NULL, msgtext,
				      (ps->anonymous) ? FWD_ANON:FWD_NONE)))){
	q_status_message1(SM_ORDER,3,4,
			  "Error fetching message %s. Can't forward it.",
			  long2string(msgno));
	goto clean;
    }

#if	defined(DOS) && !defined(_WINDOWS)
    free((void *)reserve);
#endif
    if(ps->anonymous)
      pine_simple_send(outgoing, &body, NULL, NULL, NULL, 1);
    else			/* partially formatted outgoing message */
      pine_send(outgoing, &body,
		ps->nr_mode ? "SEND MESSAGE" : "FORWARD MESSAGE",
		role, NULL, NULL, redraft_pos, NULL, NULL, FALSE);

  clean:
    if(body)
      pine_free_body(&body);

    if((STORE_S *) msgtext)
      gf_clear_so_writec((STORE_S *) msgtext);

    mail_free_envelope(&outgoing);
    free_redraft_pos(&redraft_pos);
    free_role(&role);

    return;

  bomb:
#if	defined(DOS) && !defined(WIN32)
    mail_parameters(ps->mail_stream, SET_GETS, (void *) NULL);
    append_file = NULL;
    mail_gc(ps->mail_stream, GC_TEXTS);
#endif
    q_status_message(SM_ORDER | SM_DING, 4, 5,
		   "Error fetching message contents.  Can't forward message.");
    goto clean;
}



/*----------------------------------------------------------------------
  Build the subject for the message number being forwarded

    Args: pine_state -- The usual pine structure
          msgno      -- The message number to build subject for

  Result: malloc'd string containing new subject or NULL on error

  ----------------------------------------------------------------------*/
char *
forward_subject(env, flags)
    ENVELOPE   *env;
    int         flags;
{
    size_t l;
    char  *p;
    
    if(!env)
      return(NULL);

    dprint(9, (debugfile, "checking subject: \"%s\"\n",
	       env->subject ? env->subject : "NULL"));

    if(env->subject && env->subject[0]){		/* add (fwd)? */
	/* decode any 8bit (copy to the temp buffer if decoding doesn't) */
	if(rfc1522_decode((unsigned char *) tmp_20k_buf,
			 env->subject, NULL) == (unsigned char *) env->subject)
	  strcpy(tmp_20k_buf, env->subject);

	removing_trailing_white_space(tmp_20k_buf);
	if((l=strlen(tmp_20k_buf)) < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))
#ifdef	OLDWAY
	  strcat(tmp_20k_buf, " (fwd)");
#else
	  sprintf(tmp_20k_buf, "%s (fwd)", env->subject);
#endif

	/*
	 * HACK:  composer can't handle embedded double quotes in attachment
	 * comments so we substitute two single quotes.
	 */
	if(flags & FS_CONVERT_QUOTES)
	  while(p = strchr(tmp_20k_buf, QUOTE))
	    (void)rplstr(p, 1, "''");
	  
	return(cpystr(tmp_20k_buf));

    }

    return(cpystr("Forwarded mail...."));
}


/*----------------------------------------------------------------------
  Build the body for the message number/part being forwarded

    Args: 

  Result: BODY structure suitable for sending

  ----------------------------------------------------------------------*/
BODY *
forward_body(stream, env, orig_body, msgno, sect_prefix, msgtext, flags)
    MAILSTREAM *stream;
    ENVELOPE   *env;
    BODY       *orig_body;
    long	msgno;
    char       *sect_prefix;
    void       *msgtext;
    int		flags;
{
    BODY    *body = NULL, *text_body;
    PART    *part;
    gf_io_t  pc;
    char    *tmp_text, *section, sect_buf[256];

    /*
     * Check to see if messages got expunged out from underneath us. This
     * could have happened during the prompt to the user asking whether to
     * include the message as an attachment. Either the message is gone or
     * it might be at a different sequence number. We'd better bail.
     */
    if(ps_global->expunge_count)
      return(NULL);

    if(sect_prefix)
      sprintf(section = sect_buf, "%s.1", sect_prefix);
    else
      section = "1";

    gf_set_so_writec(&pc, (STORE_S *) msgtext);
    if(!orig_body || orig_body->type == TYPETEXT) {
	/*---- Message has a single text part -----*/
	body			 = mail_newbody();
	body->type		 = TYPETEXT;
	body->contents.text.data = msgtext;
	if(!(flags & FWD_ANON)){
	    forward_delimiter(pc);
	    reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
	}

	if(!get_body_part_text(stream, orig_body, msgno, section, pc, NULL)){
	    mail_free_body(&body);
	    return(NULL);
	}
    }
    else if(orig_body->type == TYPEMULTIPART) {
	/*---- Message is multipart ----*/
	if(orig_body->subtype && !strucmp(orig_body->subtype, "alternative")){
	    body		     = mail_newbody();
	    body->type		     = TYPETEXT;
	    body->contents.text.data = msgtext;

	    /* Pick out the interesting text piece */
	    for(part = orig_body->nested.part;
		part;
		part = part->next)
	      if((!part->body.type || part->body.type == TYPETEXT)
		 && (!part->body.subtype
		     || !strucmp(part->body.subtype, "plain")))
		break;

	    /*
	     * IF something's interesting insert it
	     * AND forget the rest of the multipart
	     */
	    if(part){
		if(!(flags & FWD_ANON)){
		    forward_delimiter(pc);
		    reply_forward_header(stream, msgno, sect_prefix,
					 env, pc, "");
		}

		get_body_part_text(stream, &part->body, msgno,
				   tmp_text = body_partno(stream, msgno,
							  &part->body),
				   pc, NULL);
		fs_give((void **) &tmp_text);
	    }
	    else{
		q_status_message(SM_ORDER | SM_DING, 3, 3,
			  "No suitable part found.  Forwarding as attachment");

		/*---- New Body to start with ----*/
		body       = mail_newbody();
		body->type = TYPEMULTIPART;

		/*---- The TEXT part/body ----*/
		body->nested.part            = mail_newbody_part();
		body->nested.part->body.type = TYPETEXT;
		body->nested.part->body.contents.text.data = msgtext;

		/*--- Copy the body and entire structure  ---*/
		/*--- The second part, what ever it is ---*/
		body->nested.part->next  = mail_newbody_part();
		body->nested.part->next->body.id = generate_message_id();
		copy_body(&body->nested.part->next->body, orig_body);

		if(!fetch_contents(stream, msgno, sect_prefix,
				   &body->nested.part->next->body))
		  mail_free_body(&body);
	    }
	}
	else{
	    /*--- Copy the body and entire structure  ---*/
	    body = copy_body(NULL, orig_body);

	    /*
	     * whatever subtype it is, demote it
	     * to plain old MIXED.
	     */
	    if(body->subtype)
	      fs_give((void **) &body->subtype);

	    body->subtype = cpystr("Mixed");

	    /*--- The text part of the message ---*/
	    if(!orig_body->nested.part){
		q_status_message(SM_ORDER | SM_DING, 3, 6,
				 "Error referencing body part 1");
		mail_free_body(&body);
	    }
	    else if(orig_body->nested.part->body.type == TYPETEXT) {
		/*--- The first part is text ----*/
		text_body		      = &body->nested.part->body;
		text_body->contents.text.data = msgtext;
		if(!(flags & FWD_ANON)){
		    forward_delimiter(pc);
		    reply_forward_header(stream, msgno, sect_prefix,
					 env, pc, "");
		}

		if(!(get_body_part_text(stream, &orig_body->nested.part->body,
					msgno, section, pc, NULL)
		     && fetch_contents(stream, msgno, sect_prefix, body)))
		  mail_free_body(&body);
/* BUG: ? matter that we're not setting body.size.bytes */
	    }
	    else {
		if(fetch_contents(stream, msgno, sect_prefix, body)){
		    /*--- Create a new blank text part ---*/
		    part			  = mail_newbody_part();
		    part->next			  = body->nested.part;
		    body->nested.part		  = part;
		    part->body.contents.text.data = msgtext;
		}
		else
		  mail_free_body(&body);
	    }
	}
    }
    else {
	/*---- A single part message, not of type text ----*/
	body                     = mail_newbody();
	body->type               = TYPEMULTIPART;
	part                     = mail_newbody_part();
	body->nested.part      = part;

	/*--- The first part, a blank text part to be edited ---*/
	part->body.type            = TYPETEXT;
	part->body.contents.text.data = msgtext;

	/*--- The second part, what ever it is ---*/
	part->next               = mail_newbody_part();
	part                     = part->next;
	part->body.id            = generate_message_id();
	copy_body(&(part->body), orig_body);

	/*
	 * the idea here is to fetch part into storage object
	 */
	if(part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
							   EDIT_ACCESS)){
#if	defined(DOS) && !defined(WIN32)
	    /* fetched text to disk */
	    mail_parameters(stream, SET_GETS, (void *)dos_gets);
	    append_file = (FILE *)so_text(
				   (STORE_S *) part->body.contents.text.data);

	    if(mail_fetchbody(stream, msgno, part, &part->body.size.bytes)){
		/* next time body may stay in core */
		mail_parameters(stream, SET_GETS, (void *)NULL);
		append_file = NULL;
		mail_gc(stream, GC_TEXTS);
	    }
	    else
	      mail_free_body(&body);
#else
	    if(tmp_text = mail_fetchbody(stream, msgno, section,
					 &part->body.size.bytes))
	      so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
		       part->body.size.bytes);
	    else
	      mail_free_body(&body);
#endif
	}
	else
	  mail_free_body(&body);
    }

    gf_clear_so_writec((STORE_S *) msgtext);

    return(body);
}


/*----------------------------------------------------------------------
  Build the body for the message number/part being forwarded as ATTACHMENT

    Args: 

  Result: PARTS suitably attached to body

  ----------------------------------------------------------------------*/
int
forward_mime_msg(stream, msgno, section, env, partp, msgtext)
    MAILSTREAM	*stream;
    long	 msgno;
    char	*section;
    ENVELOPE	*env;
    PART       **partp;
    void	*msgtext;
{
    char	   *tmp_text;
    unsigned long   len;
    BODY	   *b;

    *partp	   = mail_newbody_part();
    b		   = &(*partp)->body;
    b->type	   = TYPEMESSAGE;
    b->id	   = generate_message_id();
    b->description = forward_subject(env, FS_CONVERT_QUOTES);
    b->nested.msg  = mail_newmsg();

    /*---- Package each message in a storage object ----*/
    if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
       && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
       && *tmp_text){
	so_puts((STORE_S *) b->contents.text.data, tmp_text);

#if	defined(DOS) && !defined(WIN32)
	/* write fetched text to disk */
	mail_parameters(stream, SET_GETS, (void *) dos_gets);
	append_file = (FILE *) so_text((STORE_S *)b->contents.text.data);

	/* HACK!  See mailview.c:format_message for details... */
	stream->text = NULL;

	/* write the body */
	if(mail_fetch_text (stream,msgno,section,&len,NIL)){
	    b->size.bytes = ftell(append_file);
	    /* next time body may stay in core */
	    mail_parameters(stream, SET_GETS, (void *)NULL);
	    append_file   = NULL;
	    mail_gc(ps->mail_stream, GC_TEXTS);
	    so_release((STORE_S *)b->contents.text.data);
	    return(1);
	}
#else
	b->size.bytes = strlen(tmp_text);
	so_puts((STORE_S *) b->contents.text.data, "\015\012");
	if(tmp_text = mail_fetch_text (stream,msgno,section,&len,NIL)){
	    so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
	    b->size.bytes += len;
	    return(1);
	}
#endif
    }

    return(0);
}


/*----------------------------------------------------------------------
       Partially set up message to forward and pass off to composer/mailer

    Args: pine_state -- The usual pine structure
          message    -- The MESSAGECACHE of entry to reply to 

  Result: outgoing envelope and body created and passed off to composer/mailer

   Create the outgoing envelope for the mail being forwarded, which is 
not much more than filling in the subject, and create the message body
of the outgoing message which requires formatting the header from the
envelope of the original messasge.
  ----------------------------------------------------------------------*/
void
forward_text(pine_state, text, source)
     struct pine *pine_state;
     void        *text;
     SourceType   source;
{
    ENVELOPE *env;
    BODY     *body;
    gf_io_t   pc, gc;
    STORE_S  *msgtext;
    char     *enc_error, *sig;

    if(msgtext = so_get(PicoText, NULL, EDIT_ACCESS)){
	env                   = mail_newenvelope();
	env->message_id       = generate_message_id();
	body                  = mail_newbody();
	body->type            = TYPETEXT;
	body->contents.text.data = (void *) msgtext;

	if(!pine_state->anonymous){
	    so_puts(msgtext,
		    ((sig = detoken_file(ps_global->VAR_SIGNATURE_FILE,
					 NULL, 2, 0, 1, NULL, NULL))
		     && *sig)
		      ? sig : NEWLINE);
	    so_puts(msgtext, NEWLINE);
	    so_puts(msgtext, "----- Included text -----");
	    so_puts(msgtext, NEWLINE);
	    if(sig)
	      fs_give((void **)&sig);
	}

	gf_filter_init();
	gf_set_so_writec(&pc, msgtext);
	gf_set_readc(&gc,text,(source == CharStar) ? strlen((char *)text) : 0L,
		     source);

	if((enc_error = gf_pipe(gc, pc)) == NULL){
	    if(pine_state->anonymous){
		pine_simple_send(env, &body, NULL, NULL, NULL, 1);
		pine_state->mangled_footer = 1;
	    }
	    else{
		pine_send(env, &body, "SEND MESSAGE", NULL, NULL, NULL, NULL,
			  NULL, NULL, FALSE);
		pine_state->mangled_screen = 1;
	    }
	}
	else{
	    q_status_message1(SM_ORDER | SM_DING, 3, 5,
			      "Error reading text \"%s\"",enc_error);
	    display_message('x');
	}

	gf_clear_so_writec(msgtext);
	mail_free_envelope(&env);
	pine_free_body(&body);
    }
    else {
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Error allocating message text");
	display_message('x');
    }
}



/*----------------------------------------------------------------------
       Partially set up message to resend and pass off to mailer

    Args: pine_state -- The usual pine structure

  Result: outgoing envelope and body created and passed off to mailer

   Create the outgoing envelope for the mail being resent, which is 
not much more than filling in the subject, and create the message body
of the outgoing message which requires formatting the header from the
envelope of the original messasge.
  ----------------------------------------------------------------------*/
void
bounce(pine_state)
    struct pine   *pine_state;
{
    ENVELOPE	  *env;
    long           msgno, rawno;
    char          *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
		  *prmpt_who = NULL, *prmpt_cnf = NULL;

    dprint(4, (debugfile, "\n - bounce -\n"));

    if(mn_total_cur(pine_state->msgmap) > 1L){
	save_toptr = &save_to;
	sprintf(tmp_20k_buf, "BOUNCE (redirect) %d messages to : ",
		mn_total_cur(pine_state->msgmap));
	prmpt_who = cpystr(tmp_20k_buf);
	sprintf(tmp_20k_buf, "Send %d messages ",
		mn_total_cur(pine_state->msgmap));
	prmpt_cnf = cpystr(tmp_20k_buf);
    }

    for(msgno = mn_first_cur(pine_state->msgmap);
	msgno > 0L;
	msgno = mn_next_cur(pine_state->msgmap)){

	rawno = mn_m2raw(pine_state->msgmap, msgno);
	if(env = mail_fetchstructure(pine_state->mail_stream, rawno, NULL))
	  errstr = bounce_msg(pine_state->mail_stream, rawno, NULL,
			      save_toptr, env->subject, prmpt_who, prmpt_cnf);
	else
	  errstr = "Can't fetch Subject for Bounce";


	if(errstr){
	    if(*errstr)
	      q_status_message(SM_ORDER | SM_DING, 4, 7, errstr);

	    break;
	}
    }

    if(save_to)
      fs_give((void **)&save_to);

    if(prmpt_who)
      fs_give((void **) &prmpt_who);

    if(prmpt_cnf)
      fs_give((void **) &prmpt_cnf);
}


char *
bounce_msg(stream, rawno, part, to, subject, pmt_who, pmt_cnf)
    MAILSTREAM	*stream;
    long	 rawno;
    char	*part;
    char       **to;
    char	*subject;
    char	*pmt_who, *pmt_cnf;
{
    char     *h, *p, *errstr = NULL;
    int	      i;
    void     *msgtext;
    gf_io_t   pc;
    ENVELOPE *outgoing;
    BODY     *body = NULL;

    outgoing		 = mail_newenvelope();
    outgoing->message_id = generate_message_id();
    outgoing->subject    = cpystr(subject ? subject : "Resent mail....");

    /*
     * Fill in destination if we were given one.  If so, note that we
     * call p_s_s() below such that it won't prompt...
     */
    if(to && *to){
	static char *fakedomain = "@";
	char	    *tmp_a_string;

	/* rfc822_parse_adrlist feels free to destroy input so copy */
	tmp_a_string = cpystr(*to);
	rfc822_parse_adrlist(&outgoing->to, tmp_a_string,
			     (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
			     ? fakedomain : ps_global->maildomain);
	fs_give((void **) &tmp_a_string);
    }

    /* build remail'd header */
    if(h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)){
	for(p = h, i = 0; p = strchr(p, ':'); p++)
	  i++;

	/* allocate it */
	outgoing->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);

	/*
	 * copy it, "X-"ing out transport headers bothersome to
	 * software but potentially useful to the human recipient...
	 */
	p = outgoing->remail;
	bounce_mask_header(&p, h);
	do
	  if(*h == '\015' && *(h+1) == '\012'){
	      *p++ = *h++;		/* copy CR LF */
	      *p++ = *h++;
	      bounce_mask_header(&p, h);
	  }
	while(*p++ = *h++);
    }
    /* BUG: else complain? */
	     
    /*
     * as with all text bound for the composer, build it in 
     * a storage object of the type it understands...
     */
    if(!(msgtext = (void *) so_get(PicoText, NULL, EDIT_ACCESS))){
	mail_free_envelope(&outgoing);
	return("Error allocating message text");
    }

    /*
     * Build a fake body description.  It's ignored by pine_rfc822_header,
     * but we need to set it to something that makes set_mime_types
     * not sniff it and pine_rfc822_output_body not re-encode it.
     * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
     * problems unless something tries to access body_encodings[] using
     * it without proper precautions.  We don't want to use ENCOTHER
     * cause that tells set_mime_types to sniff it, and we don't want to
     * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
     * it.  When there's time, it'd be nice to clean this interaction
     * up...
     */
    body		     = mail_newbody();
    body->type		     = TYPETEXT;
    body->encoding	     = ENCMAX + 1;
    body->subtype	     = cpystr("Plain");
    body->contents.text.data = msgtext;
    gf_set_so_writec(&pc, (STORE_S *) msgtext);

    /* pass NULL body to force mail_fetchtext */
    if(!get_body_part_text(stream, NULL, rawno, part, pc, NULL))
      errstr = "Error fetching message contents. Can't Bounce message";

    gf_clear_so_writec((STORE_S *) msgtext);

    if(pine_simple_send(outgoing,&body,pmt_who,pmt_cnf,to,!(to && *to)) < 0)
      errstr = "";		/* p_s_s() better have explained! */

    /* Just for good measure... */
    mail_free_envelope(&outgoing);
    pine_free_body(&body);

    return(errstr);		/* no problem-o */
}




/*----------------------------------------------------------------------
    Mask off any header entries we don't want xport software to see

Args:  d -- destination string pointer pointer
       s -- source string pointer pointer

  ----*/
void
bounce_mask_header(d, s)
    char **d, *s;
{
    if((*s == 'R' || *s == 'r')
       && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8))){
	*(*d)++ = 'X';				/* write mask */
	*(*d)++ = '-';
    }
}


        
/*----------------------------------------------------------------------
    Fetch and format text for forwarding

Args:  stream      -- Mail stream to fetch text for
       message_no  -- Message number of text for foward
       part_number -- Part number of text to forward
       env         -- Envelope of message being forwarded
       body        -- Body structure of message being forwarded

Returns:  true if OK, false if problem occured while filtering

If the text is richtext, it will be converted to plain text, since there's
no rich text editing capabilities in Pine (yet). The character sets aren't
really handled correctly here. Theoretically editing should not be allowed
if text character set doesn't match what term-character-set is set to.

It's up to calling routines to plug in signature appropriately

As with all internal text, NVT end-of-line conventions are observed.
DOESN'T sanity check the prefix given!!!
  ----*/
int
get_body_part_text(stream, body, msg_no, part_no, pc, prefix)
    MAILSTREAM *stream;
    BODY       *body;
    long        msg_no;
    char       *part_no;
    gf_io_t     pc;
    char       *prefix;
{
    int		i, we_cancel = 0, dashdata;
    FILTLIST_S  filters[5];
    long	len;
    char       *err;
#if	defined(DOS) && !defined(WIN32)
    char     *tmpfile_name = NULL;
#endif

    memset(filters, 0, 5 * sizeof(FILTLIST_S));
    we_cancel = busy_alarm(1, NULL, NULL, 0);

    /* if null body, we must be talking to a non-IMAP2bis server.
     * No MIME parsing provided, so we just grab the message text...
     */
    if(body == NULL){
	char         *text, *decode_error;
	MESSAGECACHE *mc;
	gf_io_t       gc;
	SourceType    src = CharStar;
	int           rv = 0;

	(void)mail_fetchstructure(stream, msg_no, NULL);
	mc = mail_elt(stream,  msg_no);

#if	defined(DOS) && !defined(WIN32)
	if(mc->rfc822_size > MAX_MSG_INCORE
	  || (ps_global->context_current->type & FTYPE_BBOARD)){
	    src = FileStar;		/* write fetched text to disk */
	    if(!(tmpfile_name = temp_nam(NULL, "pt"))
	       || !(append_file = fopen(tmpfile_name, "w+b"))){
		if(tmpfile_name)
		  fs_give((void **)&tmpfile_name);

		q_status_message1(SM_ORDER,3,4,"Can't build tmpfile: %s",
				  error_description(errno));
		if(we_cancel)
		  cancel_busy_alarm(-1);

		return(rv);
	    }

	    mail_parameters(stream, SET_GETS, (void *)dos_gets);
	}
	else				/* message stays in core */
	  mail_parameters(stream, SET_GETS, (void *)NULL);
#endif

	if(text = mail_fetch_text(stream, msg_no, part_no, NULL, 0)){
#if	defined(DOS) && !defined(WIN32)
	    if(src == FileStar)
	      gf_set_readc(&gc, append_file, 0L, src);
	    else
#endif
	    gf_set_readc(&gc, text, (unsigned long)strlen(text), src);

	    gf_filter_init();		/* no filters needed */
	    if(decode_error = gf_pipe(gc, pc)){
		sprintf(tmp_20k_buf, "%s%s    [Formatting error: %s]%s",
			NEWLINE, NEWLINE,
			decode_error, NEWLINE);
		gf_puts(tmp_20k_buf, pc);
		rv++;
	    }
	}
	else{
	    gf_puts(NEWLINE, pc);
	    gf_puts("    [ERROR fetching text of message]", pc);
	    gf_puts(NEWLINE, pc);
	    gf_puts(NEWLINE, pc);
	    rv++;
	}

#if	defined(DOS) && !defined(WIN32)
	/* clean up tmp file created for dos_gets ?  If so, trash
	 * cached knowledge, and make sure next fetch stays in core
	 */
	if(src == FileStar){
	    fclose(append_file);
	    append_file = NULL;
	    unlink(tmpfile_name);
	    fs_give((void **)&tmpfile_name);
	    mail_parameters(stream, SET_GETS, (void *) NULL);
	    mail_gc(stream, GC_TEXTS);
	}
#endif
	if(we_cancel)
	  cancel_busy_alarm(-1);

	return(rv == 0);
    }

    i = 0;			/* for start of filter list */

    /*
     * just use detach, but add an auxiliary filter to insert prefix,
     * and, perhaps, digest richtext
     */
    if(body->subtype){
	if(strucmp(body->subtype,"richtext") == 0){
	    filters[i].filter = gf_rich2plain;
	    filters[i++].data = gf_rich2plain_opt(1);
	}
	else if(strucmp(body->subtype,"html") == 0){
	    filters[i].filter = gf_html2plain;
	    filters[i++].data = gf_html2plain_opt(NULL,
						  ps_global->ttyo->screen_cols,
						  GFHP_STRIPPED);
	}
    }

    if(prefix){
	if(F_ON(F_ENABLE_SIGDASHES, ps_global) ||
	   F_ON(F_ENABLE_STRIP_SIGDASHES, ps_global)){
	    dashdata = 0;
	    filters[i].filter = gf_line_test;
	    filters[i++].data = gf_line_test_opt(sigdash_strip, &dashdata);
	}

	filters[i].filter = gf_prefix;
	filters[i++].data = gf_prefix_opt(prefix);
    }

    err = detach(stream, msg_no, part_no, &len, pc,
		 filters[0].filter ? filters : NULL);
    if (err != (char *) NULL)
       q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
			 err, (void *) msg_no);

    if(we_cancel)
      cancel_busy_alarm(-1);

    return((int)len);
}



int
sigdash_strip(linenum, line, ins, local)
    long       linenum;
    char      *line;
    LT_INS_S **ins;
    void      *local;
{
    if(*((int *)local)
       || (*line == '-' && *(line+1) == '-'
	   && *(line+2) == ' ' && !*(line+3))){
	*((int *) local) = 1;
	return(2);		/* skip this line! */
    }

    return(0);
}



/*----------------------------------------------------------------------
  return the c-client reference name for the given end_body part
  ----*/
char *
body_partno(stream, msgno, end_body)
    MAILSTREAM *stream;
    long	msgno;
    BODY       *end_body;
{
    BODY *body;

    (void) mail_fetchstructure(stream, msgno, &body);
    return(partno(body, end_body));
}



/*----------------------------------------------------------------------
  return the c-client reference name for the given end_body part
  ----*/
char *
partno(body, end_body)
     BODY *body, *end_body;
{
    PART *part;
    int   num = 0;
    char  tmp[64], *p = NULL;

    if(body && body->type == TYPEMULTIPART) {
	part = body->nested.part;	/* first body part */

	do {				/* for each part */
	    num++;
	    if(&part->body == end_body || (p = partno(&part->body, end_body))){
		sprintf(tmp, "%d%s%s", num, (p) ? "." : "", (p) ? p : "");
		if(p)
		  fs_give((void **)&p);

		return(cpystr(tmp));
	    }
	} while (part = part->next);	/* until done */

	return(NULL);
    }
    else if(body && body->type == TYPEMESSAGE && body->subtype 
	    && !strucmp(body->subtype, "rfc822")){
	return(partno(body->nested.msg->body, end_body));
    }

    return((body == end_body) ? cpystr("1") : NULL);
}



/*----------------------------------------------------------------------
   Fill in the contents of each body part

Args: stream      -- Stream the message is on
      msgno       -- Message number the body structure is for
      section	  -- body section associated with body pointer
      body        -- Body pointer to fill in

Result: 1 if all went OK, 0 if there was a problem

This function copies the contents from an original message/body to
a new message/body.  It recurses down all multipart levels.

If one or more part (but not all) can't be fetched, a status message
will be queued.
 ----*/
int
fetch_contents(stream, msgno, section, body)
     MAILSTREAM *stream;
     long        msgno;
     char	*section;
     BODY       *body;
{
    char *tp;
    int   got_one = 0;

    if(!body->id)
      body->id = generate_message_id();
          
    if(body->type == TYPEMULTIPART){
	char  subsection[256], *subp;
	int   n, last_one = 10;		/* remember worst case */
	PART *part     = body->nested.part;

	if(!(part = body->nested.part))
	  return(0);

	subp = subsection;
	if(section && *section){
	    for(n = 0; *subp = section[n]; n++, subp++)
	      ;

	    *subp++ = '.';
	}

	n = 1;
	do {
	    sprintf(subp, "%d", n++);
	    got_one  = fetch_contents(stream, msgno, subsection, &part->body);
	    last_one = min(last_one, got_one);
	}
	while(part = part->next);

	return(last_one);
    }

    if(body->contents.text.data)
      return(1);			/* already taken care of... */

    if(body->type == TYPEMESSAGE){
	if(body->subtype && strucmp(body->subtype,"external-body")){
	    /*
	     * the idea here is to fetch everything into storage objects
	     */
	    body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
						    EDIT_ACCESS);
#if	defined(DOS) && !defined(WIN32)
	    if(body->contents.text.data){
		/* fetch text to disk */
		mail_parameters(stream, SET_GETS, (void *)dos_gets);
		append_file =(FILE *)so_text((STORE_S *)body->contents.text.data);

		if(mail_fetchbody(stream, msgno, section, &body->size.bytes)){
		    so_release((STORE_S *)body->contents.text.data);
		    got_one = 1;
		}
		else
		  q_status_message1(SM_ORDER | SM_DING, 3, 3,
				    "Error fetching part %s", section);

		/* next time body may stay in core */
		mail_parameters(stream, SET_GETS, (void *)NULL);
		append_file = NULL;
		mail_gc(stream, GC_TEXTS);
	    }
#else
	    if(body->contents.text.data
	       && (tp = mail_fetchbody(stream, msgno, section,
				       &body->size.bytes))){
		so_nputs((STORE_S *)body->contents.text.data, tp,
			 body->size.bytes);
		got_one = 1;
	    }
#endif
	    else
	      q_status_message1(SM_ORDER | SM_DING, 3, 3,
				"Error fetching part %s", section);
	} else {
	    got_one = 1;
	}
    } else {
	/*
	 * the idea here is to fetch everything into storage objects
	 * so, grab one, then fetch the body part
	 */
	body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
#if	defined(DOS) && !defined(WIN32)
	if(body->contents.text.data){
	    /* write fetched text to disk */
	    mail_parameters(stream, SET_GETS, (void *)dos_gets);
	    append_file = (FILE *)so_text((STORE_S *)body->contents.text.data);
	    if(mail_fetchbody(stream, msgno, section, &body->size.bytes)){
		so_release((STORE_S *)body->contents.text.data);
		got_one = 1;
	    }
	    else
	      q_status_message1(SM_ORDER | SM_DING, 3, 3,
				"Error fetching part %s", section);

	    /* next time body may stay in core */
	    mail_parameters(stream, SET_GETS, (void *)NULL);
	    append_file = NULL;
	    mail_gc(stream, GC_TEXTS);
	}
#else
	if(body->contents.text.data
	   && (tp=mail_fetchbody(stream, msgno, section, &body->size.bytes))){
	    so_nputs((STORE_S *)body->contents.text.data, tp,
		     body->size.bytes);
	    got_one = 1;
	}
#endif
	else
	  q_status_message1(SM_ORDER | SM_DING, 3, 3,
			    "Error fetching part %s", section);
    }

    return(got_one);
}



/*----------------------------------------------------------------------
    Copy the body structure

Args: new_body -- Pointer to already allocated body, or NULL, if none
      old_body -- The Body to copy


 This is traverses the body structure recursively copying all elements.
The new_body parameter can be NULL in which case a new body is
allocated. Alternatively it can point to an already allocated body
structure. This is used when copying body parts since a PART includes a 
BODY. The contents fields are *not* filled in.
  ----*/

BODY *
copy_body(new_body, old_body)
     BODY *old_body, *new_body;
{
    if(old_body == NULL)
      return(NULL);

    if(new_body == NULL)
      new_body = mail_newbody();

    new_body->type = old_body->type;
    new_body->encoding = old_body->encoding;

    if(old_body->subtype)
      new_body->subtype = cpystr(old_body->subtype);

    new_body->parameter = copy_parameters(old_body->parameter);

    if(old_body->id)
      new_body->id = cpystr(old_body->id);

    if(old_body->description)
      new_body->description = cpystr(old_body->description);

    if(old_body->disposition.type)
      new_body->disposition.type = cpystr(old_body->disposition.type);

    new_body->disposition.parameter
			    = copy_parameters(old_body->disposition.parameter);

    if(new_body->type == TYPEMESSAGE
       && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
	new_body->nested.msg = mail_newmsg();
	new_body->nested.msg->body
				 = copy_body(NULL, old_body->nested.msg->body);
    }
    else if(new_body->type == TYPEMULTIPART) {
	PART **new_partp, *old_part;

	new_partp = &new_body->nested.part;
	for(old_part = old_body->nested.part;
	    old_part != NULL;
	    old_part = old_part->next){
	    *new_partp = mail_newbody_part();
            copy_body(&(*new_partp)->body, &old_part->body);
	    new_partp = &(*new_partp)->next;
        }
    }

    return(new_body);
}



/*----------------------------------------------------------------------
    Copy the MIME parameter list
 
 Allocates storage for new part, and returns pointer to new paramter
list. If old_p is NULL, NULL is returned.
 ----*/

PARAMETER *
copy_parameters(old_p)
     PARAMETER *old_p;
{
    PARAMETER *new_p, *p1, *p2;

    if(old_p == NULL)
      return((PARAMETER *)NULL);

    new_p = p2 = NULL;
    for(p1 = old_p; p1 != NULL; p1 = p1->next) {
        if(new_p == NULL) {
            p2 = mail_newbody_parameter();
            new_p = p2;
        } else {
            p2->next = mail_newbody_parameter();
            p2 = p2->next;
        }
        p2->attribute = cpystr(p1->attribute);
        p2->value     = cpystr(p1->value);
    }
    return(new_p);
}
    
    

/*----------------------------------------------------------------------
    Make a complete copy of an envelope and all it's fields

Args:    e -- the envelope to copy

Result:  returns the new envelope, or NULL, if the given envelope was NULL

  ----*/

ENVELOPE *    
copy_envelope(e)
     register ENVELOPE *e;
{
    register ENVELOPE *e2;

    if(!e)
      return(NULL);

    e2		    = mail_newenvelope();
    e2->remail      = e->remail	     ? cpystr(e->remail)	      : NULL;
    e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
    e2->date        = e->date	     ? cpystr(e->date)		      : NULL;
    e2->from        = e->from	     ? rfc822_cpy_adr(e->from)	      : NULL;
    e2->sender      = e->sender	     ? rfc822_cpy_adr(e->sender)      : NULL;
    e2->reply_to    = e->reply_to    ? rfc822_cpy_adr(e->reply_to)    : NULL;
    e2->subject     = e->subject     ? cpystr(e->subject)	      : NULL;
    e2->to          = e->to          ? rfc822_cpy_adr(e->to)	      : NULL;
    e2->cc          = e->cc          ? rfc822_cpy_adr(e->cc)	      : NULL;
    e2->bcc         = e->bcc         ? rfc822_cpy_adr(e->bcc)	      : NULL;
    e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to)	      : NULL;
    e2->newsgroups  = e->newsgroups  ? cpystr(e->newsgroups)	      : NULL;
    e2->message_id  = e->message_id  ? cpystr(e->message_id)	      : NULL;
    e2->references  = e->references  ? cpystr(e->references)          : NULL;
    e2->followup_to = e->followup_to ? cpystr(e->references)          : NULL;
    return(e2);
}


/*----------------------------------------------------------------------
     Generate the "In-reply-to" text from message header

  Args: message -- Envelope of original message

  Result: returns an alloc'd string or NULL if there is a problem
 ----*/
char *
reply_in_reply_to(env)
    ENVELOPE *env;
{
    return((env && env->message_id) ? cpystr(env->message_id) : NULL);
}


/*----------------------------------------------------------------------
        Generate a unique message id string.

   Args: ps -- The usual pine structure

  Result: Alloc'd unique string is returned

Uniqueness is gaurenteed by using the host name, process id, date to the
second and a single unique character
*----------------------------------------------------------------------*/
char *
generate_message_id()
{
    static short osec = 0, cnt = 0;
    char        *id;
    time_t       now;
    struct tm   *now_x;

    now   = time((time_t *)0);
    now_x = localtime(&now);
    id    = (char *)fs_get(128 * sizeof(char));

    if(now_x->tm_sec == osec){
	cnt++;
    }else{
	cnt = 0;
	osec = now_x->tm_sec;
    }
    sprintf(id,"<Pine.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
	    SYSTYPE, pine_version, now_x->tm_year, now_x->tm_mon + 1,
	    now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec, 
	    cnt, getpid(), ps_global->hostname);

    return(id);
}



/*----------------------------------------------------------------------
  Return the first true address pointer (modulo group syntax allowance)

  Args: addr  -- Address list

 Result: First real address pointer, or NULL
  ----------------------------------------------------------------------*/
ADDRESS *
first_addr(addr)
    ADDRESS *addr;
{
    while(addr && !addr->host)
      addr = addr->next;

    return(addr);
}


/*----------------------------------------------------------------------
  Acquire the pinerc defined signature file
  It is allocated here and freed by the caller.

          file -- use this file
   prenewlines -- prefix the file contents with this many newlines
  postnewlines -- postfix the file contents with this many newlines
     sigdashes -- include sigdashes if appropriate
  ----*/
char *
get_signature(file, prenewlines, postnewlines, sigdashes)
    char *file;
    int   prenewlines,
	  postnewlines,
	  sigdashes;
{
    char     *sig = NULL, *tmp_sig = NULL, sig_path[MAXPATH+1];
    int       len, do_the_pipe_thang = 0;
    long      sigsize = 0L, cntdown;

    if(!signature_path(file, sig_path, MAXPATH))
      return(sig);

    dprint(5, (debugfile, "get_signature(%s)\n", sig_path));

    if(sig_path[(len=strlen(sig_path))-1] == '|'){
	sig_path[len-1] = '\0';
	do_the_pipe_thang++;
    }

    if(ps_global->VAR_OPER_DIR &&
       !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
			  "Can't read file outside %s: %s",
			  ps_global->VAR_OPER_DIR, file);
	
	return(sig);
    }

    if(can_access(sig_path, ACCESS_EXISTS) == 0){
	if(do_the_pipe_thang){
	    if(can_access(sig_path, EXECUTE_ACCESS) == 0){
		STORE_S  *store;
		int       flags;
		PIPE_S   *syspipe;
		gf_io_t   pc, gc;
		long      start;

		if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){

		    flags = PIPE_READ | PIPE_STDERR | PIPE_NOSHELL;

		    start = time(0);

		    if(syspipe = open_system_pipe(sig_path,NULL,NULL,flags,5)){
			unsigned char c;
			char         *error, *q;

			gf_set_so_writec(&pc, store);
			gf_set_readc(&gc, (void *)syspipe->in.f, 0, FileStar);
			gf_filter_init();

			if((error = gf_pipe(gc, pc)) != NULL){
			    (void)close_system_pipe(&syspipe);
			    gf_clear_so_writec(store);
			    so_give(&store);
			    q_status_message1(SM_ORDER | SM_DING, 3, 4,
					      "Can't get file: %s", error);
			    return(sig);
			}

			if(close_system_pipe(&syspipe)){
			    long now;

			    now = time(0);
			    q_status_message2(SM_ORDER, 3, 4,
				    "Error running program \"%s\"%s",
				    file,
				    (now - start > 4) ? ": timed out" : "");
			}

			gf_clear_so_writec(store);

			/* rewind and count chars */
			so_seek(store, 0L, 0);
			while(so_readc(&c, store) && sigsize < 100000L)
			  sigsize++;

			/* allocate space */
			tmp_sig = fs_get((sigsize + 1) * sizeof(char));
			tmp_sig[0] = '\0';
			q = tmp_sig;

			/* rewind and copy chars, no prenewlines... */
			so_seek(store, 0L, 0);
			cntdown = sigsize;
			while(so_readc(&c, store) && cntdown-- > 0L)
			  *q++ = c;
			
			*q = '\0';
			so_give(&store);
		    }
		    else{
			so_give(&store);
			q_status_message1(SM_ORDER | SM_DING, 3, 4,
				     "Error running program \"%s\"",
				     file);
		    }
		}
		else
		  q_status_message(SM_ORDER | SM_DING, 3, 4,
			  "Error allocating space for sig or template program");
	    }
	    else
	      q_status_message1(SM_ORDER | SM_DING, 3, 4,
				"Can't execute \"%s\": Permission denied",
				sig_path);
	}
	else if(tmp_sig = read_file(sig_path))
	  sigsize = strlen(tmp_sig);
	else
	  q_status_message2(SM_ORDER | SM_DING, 3, 4,
			    "Error \"%s\" reading file \"%s\"",
			    error_description(errno), sig_path);
    }

    if(tmp_sig){
	sig = fs_get((sigsize + 5 +
		       (prenewlines+postnewlines) *
			strlen(NEWLINE) + 1) * sizeof(char));
	sig[0] = '\0';
	while(prenewlines--)
	  strcat(sig, NEWLINE);

	if(sigdashes && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
	   !sigdashes_are_present(tmp_sig)){
	    strcat(sig, SIGDASHES);
	    strcat(sig, NEWLINE);
	}

	strcat(sig, tmp_sig);
	fs_give((void **)&tmp_sig);

	while(postnewlines--)
	  strcat(sig, NEWLINE);
    }

    return(sig);
}


int
sigdashes_are_present(sig)
    char *sig;
{
    char *p;

    p = srchstr(sig, SIGDASHES);
    while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
	         (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
      p = srchstr(p+1, SIGDASHES);
    
    return(p ? 1 : 0);
}


/*----------------------------------------------------------------------
  Acquire the pinerc defined signature file pathname

  ----*/
char *
signature_path(sname, sbuf, len)
    char   *sname, *sbuf;
    size_t  len;
{
    *sbuf = '\0';
    if(sname && *sname){
	size_t spl = strlen(sname);
	if(is_absolute_path(sname)){
	    if(spl < len - 1)
	      strcpy(sbuf, sname);
	}
#ifndef	DOS
	else if(sname[0] == '~'){
	    strcpy(sbuf, sname);
	    fnexpand(sbuf, len);
	}
#endif
	else if(ps_global->VAR_OPER_DIR){
	    if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
	      build_path(sbuf, ps_global->VAR_OPER_DIR, sname);
	}
	else{
	    char *lc = last_cmpnt(ps_global->pinerc);

	    sbuf[0] = '\0';
	    if(lc != NULL){
		strncpy(sbuf,ps_global->pinerc,min(len-1,lc-ps_global->pinerc));
		sbuf[min(len-1,lc-ps_global->pinerc)] = '\0';
	    }

	    sbuf[spl+ strlen(sbuf)] = '\0';
	    strncat(sbuf, sname, spl + strlen(sbuf));
	}
    }

    return(*sbuf ? sbuf : NULL);
}
