#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: reply.c,v 4.284 1999/11/08 18:24:05 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));
BODY	 *forward_multi_alt PROTO((MAILSTREAM *, ENVELOPE *, BODY *,
				   long, char *, void *, gf_io_t, int));
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 **));
int      reply_quote_str_contains_tokens PROTO((void));



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

    /*
     * We may have to loop through first to figure out what default
     * reply-indent-string to offer...
     */
    if(mn_total_cur(pine_state->msgmap) > 1 &&
       F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state) &&
       reply_quote_str_contains_tokens()){
	for(msgno = mn_first_cur(pine_state->msgmap);
	    msgno > 0L && !tmpfix;
	    msgno = mn_next_cur(pine_state->msgmap)){

	    env = mail_fetchstructure(pine_state->mail_stream,
				      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);
			/*
			 * We free prefix, not tmpfix. We set tmpfix to prefix
			 * so that we won't come check again.
			 */
			tmpfix = prefix = cpystr("> ");
		    }
		}
		else{
		    prefix = tmpfix;
		    tmpfix = NULL;		/* check back later? */
		}
	    }
	}

	tmpfix = prefix;
    }

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

	/*
	 * We check for the prefix here if we didn't do it in the first
	 * loop above. This is just to save having to go through the loop
	 * twice in the cases where we don't need to.
	 */
	if(!tmpfix){
	    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);
		    tmpfix = prefix = 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 = prefix;		/* 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(nonempty_patterns()){
	if(!times)
	  /* setup default role */
	  role = set_role_from_msg(pine_state, ROLE_REPLY, seq[times], NULL);

	if(confirm_role(ROLE_REPLY, &role))
	  role = combine_inherited_role(role);
	else{				/* cancel reply */
	    role = NULL;
	    cmd_cancelled("Reply");
	    goto done_early;
	}
    }

    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_action(&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);
}


ACTION_S *
set_role_from_msg(ps, type, msgno, section)
    struct pine *ps;
    int          type;
    long         msgno;
    char        *section;
{
    ACTION_S      *role = NULL;
    PAT_S         *pat = NULL;
    int            match = 0;
    SEARCHSET     *ss = NULL;
    PAT_STATE      pstate;

    if(!nonempty_patterns())
      return(role);

    if(msgno > 0L){
	ss = mail_newsearchset();
	ss->first = ss->last = (unsigned long)msgno;
    }

    /* Go through the possible roles one at a time until we get a match. */
    pat = first_pattern(type, &pstate);

    /* calculate this message's score if needed */
    if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
       get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
      calculate_some_scores(ps->mail_stream, ss);

    while(!match && pat){
	if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
			 get_msg_score)){
	    role = pat->action;
	    match++;
	}
	else
	  pat = next_pattern(type, &pstate);
    }

    if(ss)
      mail_free_searchset(&ss);

    return(role);
}


/*
 * Ask user to confirm role choice, or choose another role.
 *
 * Args    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, role)
    int             flags;
    ACTION_S      **role;
{
    ACTION_S       *role_p = NULL;
    char            prompt[80], *prompt_fodder;
    int             cmd, done, ret = 1;
    void (*prev_screen)() = ps_global->prev_screen,
	 (*redraw)() = ps_global->redrawer;
    PAT_S          *curpat, *pat;
    PAT_STATE       pstate;
    HelpType        help;
    ESCKEY_S        ekey[4];

    if(!nonempty_patterns() || !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) ||
       ((flags & ROLE_COMPOSE) &&
	*role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
       (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global) &&
	 !(flags & ROLE_DEFAULTOK)))
      return(ret);

    /*
     * Check that there is at least one role available. This is among all
     * roles, not just the reply roles or just the forward roles. That's
     * because we have ^T take us to all the roles, not the category-specific
     * roles.
     */
    if(!(pat = last_pattern(ROLE_DO_ROLES, &pstate)))
      return(ret);
    
    ekey[0].ch    = 'y';
    ekey[0].rval  = 'y';

    ekey[1].ch    = 'n';
    ekey[1].rval  = 'n';

    ekey[2].ch    = ctrl('T');
    ekey[2].rval  = 2;
    ekey[2].name  = "^T";

    ekey[3].ch    = -1;

    /* check for more than one role available (or no role set) */
    if(pat == first_pattern(ROLE_DO_ROLES, &pstate) && *role)	/* no ^T */
      ekey[2].ch    = -1;
    
    /* Setup default */
    curpat = NULL;
    if(*role){
	for(pat = first_pattern(ROLE_DO_ROLES, &pstate);
	    pat;
	    pat = next_pattern(ROLE_DO_ROLES, &pstate)){
	    if(pat->action == *role){
		curpat = pat;
		break;
	    }
	}
    }

    if(flags & ROLE_REPLY)
      prompt_fodder = "Reply";
    else if(flags & ROLE_FORWARD)
      prompt_fodder = "Forward";
    else
      prompt_fodder = "Compose";

    done = 0;
    while(!done){
	if(curpat){
	    help = h_role_confirm;
	    ekey[0].name  = "Y";
	    ekey[0].label = "Yes";
	    ekey[1].name  = "N";
	    ekey[1].label = "No Role";
	    ekey[2].label = "To Select Alternate Role";
	    if(curpat->patgrp && curpat->patgrp->nick)
	      sprintf(prompt, "Use role \"%.40s\" for %s? ",
		      curpat->patgrp->nick, prompt_fodder);
	    else
	      sprintf(prompt,
		      "Use role \"<a role without a nickname>\" for %s? ",
		      prompt_fodder);
	}
	else{
	    help = h_norole_confirm;
	    ekey[0].name  = "Ret";
	    ekey[0].label = prompt_fodder;
	    ekey[1].name  = "";
	    ekey[1].label = "";
	    ekey[2].label = "To Select Role";
	    sprintf(prompt,
		    "Press Return to %s using no role, or ^T to select a role ",
		    prompt_fodder);
	}

	cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
			    'y', 'x', help, RB_NORM);

	switch(cmd){
	  case 'y':					/* Accept */
	    done++;
	    *role = curpat ? curpat->action : NULL;
	    break;

	  case 'x':					/* Cancel */
	    ret = 0;
	    /* fall through */

	  case 'n':					/* NoRole */
	    done++;
	    *role = NULL;
	    break;

	  case 2:					/* ^T */
	    if(role_select_screen(ps_global, &role_p, 0) >= 0){
		if(role_p){
		    for(pat = first_pattern(ROLE_DO_ROLES, &pstate);
			pat;
			pat = next_pattern(ROLE_DO_ROLES, &pstate)){
			if(pat->action == role_p){
			    curpat = pat;
			    break;
			}
		    }
		}
		else
		  curpat = NULL;
	    }

	    ClearBody();
	    ps_global->mangled_body = 1;
	    ps_global->prev_screen = prev_screen;
	    ps_global->redrawer = redraw;
	    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 && isspace((unsigned char) *s))
	  s++;
	
	/* copy initial */
	if(*s)
	  *w++ = *s++;
	
	/* skip to end of this piece of name */
	while(*s && !isspace((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
static char *from_token = "_FROM_";
static char *nick_token = "_NICK_";
static char *init_token = "_INIT_";

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

    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;
	char *dummy = NULL;

	repl = (env && env->from && env->from->personal)
		 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode(
						(unsigned char *)tmp_20k_buf, 
						env->from->personal, &dummy)))
		 : "";

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

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

int
reply_quote_str_contains_tokens()
{
    return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
	   (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
	    strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
	    strstr(ps_global->VAR_REPLY_STRING, init_token)));
}

/*
 * 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, "signed")
	       && orig_body->nested.part){
		/* operate only on the signed part */
		return(reply_body(stream, env,
				  &orig_body->nested.part->body,
				  msgno, section, msgtext, prefix,
				  plustext, template_file,
				  sig_file, redraft_pos));
	    }
	    else 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 if(orig_body->nested.part->body.type == TYPEMULTIPART
			&& orig_body->nested.part->body.subtype
			&& !strucmp(orig_body->nested.part->body.subtype,
				    "alternative")
			&& reply_body_text(&orig_body->nested.part->body,
					   &tmp_body)){
		    int partnum;

		    reply_delimiter(env, pc);
		    if(F_ON(F_INCLUDE_HEADER, ps_global))
		      reply_forward_header(stream, msgno, sect_prefix,
					   env, pc, prefix);

		    sprintf(sect_buf, "%s%s%s",
			    sect_prefix ? sect_prefix : "",
			    sect_prefix ? "." : "",
			    p = partno(orig_body, tmp_body));
		    fs_give((void **) &p);
		    get_body_part_text(stream, tmp_body, msgno,
				       sect_buf, pc, prefix);

		    part = body->nested.part->next;
		    body->nested.part->next = NULL;
		    mail_free_body_part(&body->nested.part);
		    body->nested.part = mail_newbody_part();
		    body->nested.part->body.type = TYPETEXT;
		    body->nested.part->body.subtype = cpystr("Plain");
		    body->nested.part->body.contents.text.data = msgtext;
		    body->nested.part->next = part;

		    partnum = 2;
		    do{
			sprintf(sect_buf, "%s%s%d",
				sect_prefix ? sect_prefix : "",
				sect_prefix ? "." : "", partnum++);

			if(!fetch_contents(stream, msgno,
					   sect_buf, &part->body)){
			    break;
			}
		    }
		    while(part = part->next);
		}
		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 iDateIso:
      case iDateIsoS:
      case iTime24:
      case iTime12:
      case iDay:
      case iDay2Digit:
      case iMonAbb:
      case iMonLong:
      case iMon:
      case iMon2Digit:
      case iYear:
      case iYear2Digit:
      case iDate:
      case iLDate:
      case iTimezone:
	if(env && env->date && env->date[0] && maxlen >= 20)
	  date_str(env->date, type, 1, buf);

	break;

      case iCurDate:
      case iCurDateIso:
      case iCurDateIsoS:
      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;
    char          *filtered = NULL;
    int            len;


    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{
	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 && *filtered)
	  gf_puts(filtered, pc);
    }

    /* and end with two newlines unless no leadin at all */
    if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
	gf_puts(NEWLINE, pc);
	gf_puts(NEWLINE, pc);
    }

    if(filtered)
      fs_give((void **)&filtered);
}


/*
 * 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.
 *       is_sig -- This is a signature file (not a template file).
 *  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, is_sig, redraft_pos, impl)
    char           *file;
    ENVELOPE       *env;
    int             prenewlines, postnewlines, is_sig;
    REDRAFT_POS_S **redraft_pos;
    int            *impl;
{
    char *ret = NULL, *p;

    if(file){
	if((p = get_signature(file,prenewlines,postnewlines,is_sig)) != 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(strindex(str1, '_')){
	    figure_size = -1L;	/* tell detokenize to figure out size needed */
	    (void)detoken_guts(str1, for_what, env, NULL, &figure_size, 0,NULL);
	    figure_size = max(0L, figure_size);
	    str2 = detoken_guts(str1, for_what, env, NULL, &figure_size,0,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 and
     * to remove any backslash escape for a token.
     */
    if((str2 && strindex(str2, '_')) ||
       (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
	figure_size = -1L;
	(void)detoken_guts(str2, for_what, env, redraft_pos, &figure_size,
			   1, impl);
	figure_size = max(0L, figure_size);
	ret = detoken_guts(str2, for_what, env, redraft_pos, &figure_size,
			   1, 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.
 *       last_pass --  This is a flag to tell detoken_guts whether or not to do
 *                     the replacement for _CURSORPOS_. Leave it as is until
 *                     the last pass. We need this because we want to defer
 *                     cursor placement until the very last call to detoken,
 *                     otherwise we'd have to keep track of the cursor
 *                     position as subsequent text replacements (nested)
 *                     take place.
 *                     This same flag is also used to decide when to eliminate
 *                     backslash escapes from in front of tokens. The only
 *                     use of backslash escapes is to escape an entire token.
 *                     That is, \_DATE_ is a literal _DATE_, but any other
 *                     backslash is a literal backslash. That way, nobody
 *                     but wackos will have to worry about backslashes.
 *         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, last_pass, impl)
    char           *src;
    int             for_what;
    ENVELOPE       *env;
    REDRAFT_POS_S **redraft_pos;
    long           *figure_size;
    int             last_pass;
    int            *impl;
{
#define MAXSUB 500
    char          *p, *q, *dst = NULL;
    char           subbuf[MAXSUB+1], *repl;
    INDEX_PARSE_T *pt;
    long           l, cnt = 0;
    struct date    now;
    int            countit = 0, suppress_tokens = 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 _. The only escaping mechanism
     * is a backslash in front of a token. This will give you the literal
     * token. So \_DATE_ is a literal _DATE_.
     * 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_(match_this,if_text,else_text) means to
     * replace that with either the if_text or else_text depending on whether
     * what _word_ (without the paren) would produce matches match_this or not.
     */
    p = src;
    while(*p){
	switch(*p){
	  case '_':		/* possible start of token */
	    if(!suppress_tokens &&
	       (pt = itoktype(p+1, for_what | DELIM_USCORE)) != NULL){
		char *free_this = NULL;

		p += (strlen(pt->name) + 2);	/* skip over token */

		repl = subbuf;
		subbuf[0] = '\0';

		if(pt->ctype == iCursorPos){
		    if(!last_pass){
			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 */
		suppress_tokens = 0;
		if(countit)
		  cnt++;
		else
		  *q++ = *p;

		p++;
	    }

	    break;

	  case BSLASH:
	    /*
	     * If a real token follows the backslash, then the backslash
	     * is here to escape the token. Otherwise, it's just a
	     * regular character.
	     */
	    if(*(p+1) == '_' &&
	       ((pt = itoktype(p+2, for_what | DELIM_USCORE)) != NULL)){
		/*
		 * Backslash is escape for literal token.
		 * If we're on the last pass we want to eliminate the
		 * backslash, otherwise we keep it.
		 * In either case, suppress_tokens will cause the token
		 * lookup to be skipped above so that the token will
		 * be treated as literal text.
		 */
		suppress_tokens++;
		if(last_pass){
		    p++;
		    break;
		}
		/* else, fall through and keep backslash */
	    }
	    /* this is a literal backslash, 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
      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;
    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 && nonempty_patterns()){
	role = (totalmsgs == 1) ? set_role_from_msg(ps, ROLE_FORWARD,
						    msgno, NULL)
				: NULL;
	if(confirm_role(ROLE_FORWARD, &role))
	  role = combine_inherited_role(role);
	else{				/* cancel reply */
	    role = NULL;
	    cmd_cancelled("Forward");
	    so_give((STORE_S **)&msgtext);
	    goto clean;
	}
    }

    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_action(&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)) < 1000 &&
	   (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
	    sprintf(tmp_20k_buf+2000, "%s (fwd)", tmp_20k_buf);
	    strcpy(tmp_20k_buf, tmp_20k_buf+2000);
	}

	/*
	 * 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, *tmp_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) {
	if(orig_body->subtype && !strucmp(orig_body->subtype, "signed")
	   && orig_body->nested.part){
	    /* only operate on the signed data (not the signature) */
	    return(forward_body(stream, env, &orig_body->nested.part->body,
				msgno, section, msgtext, flags));
	}
	/*---- Message is multipart ----*/
	else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
						 "alternative")
		  && (body = forward_multi_alt(stream, env, orig_body, msgno,
					       sect_prefix, msgtext,
					       pc, flags)))){
	    /*--- 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(body->nested.part->body.type == TYPEMULTIPART
		    && body->nested.part->body.subtype
		    && !strucmp(body->nested.part->body.subtype, "alternative")
		    && (tmp_body = forward_multi_alt(stream, env,
						     &body->nested.part->body,
						     msgno, section,
						     msgtext, pc, flags))){
		int partnum;

		part = body->nested.part->next;
		body->nested.part->next = NULL;
		mail_free_body_part(&body->nested.part);
		body->nested.part = mail_newbody_part();
		body->nested.part->body = *tmp_body;
		body->nested.part->next = part;

		partnum = 2;
		do{
		    sprintf(sect_buf, "%s%s%d",
			    sect_prefix ? sect_prefix : "",
			    sect_prefix ? "." : "", partnum++);

		    if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
			mail_free_body(&body);
			break;
		    }
		}
		while(part = part->next);
	    }
	    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 multipart/alternative part

    Args: 

  Result: 

  ----------------------------------------------------------------------*/
BODY *
forward_multi_alt(stream, env, orig_body, msgno,
		  sect_prefix, msgtext, pc, flags)
    MAILSTREAM *stream;
    ENVELOPE   *env;
    BODY       *orig_body;
    long	msgno;
    char       *sect_prefix;
    void       *msgtext;
    gf_io_t	pc;
    int		flags;
{
    BODY *body = NULL;
    PART *part;
    char  tmp_buf[256];
    int   partnum;

    /* Pick out the interesting text piece */
    for(part = orig_body->nested.part, partnum = 1;
	part;
	part = part->next, partnum++)
      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){
	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, "");
	}

	sprintf(tmp_buf, "%s%s%d", sect_prefix ? sect_prefix : "",
		sect_prefix ? "." : "", partnum);
	get_body_part_text(stream, body, msgno, tmp_buf, pc, NULL);
    }
    else
      q_status_message(SM_ORDER | SM_DING, 3, 3,
		       "No suitable part found.  Forwarding as attachment");

    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;
    ACTION_S *role = NULL;

    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){
	    if(nonempty_patterns()){
		/*
		 * This is really more like Compose, even though it
		 * is called Forward.
		 */
		if(confirm_role(ROLE_COMPOSE, &role))
		  role = combine_inherited_role(role);
		else{			/* cancel */
		    cmd_cancelled("Composition");
		    display_message('x');
		    mail_free_envelope(&env);
		    pine_free_body(&body);
		    return;
		}
	    }

	    if(role)
	      q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
				role->nick);

	    sig = detoken_file((role && role->sig)
				    ? role->sig
				    : ps_global->VAR_SIGNATURE_FILE,
				NULL, 2, 0, 1, NULL, NULL);
	    so_puts(msgtext, (sig && *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", role, 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');
    }

    free_action(&role);
}



/*----------------------------------------------------------------------
       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) % 100, 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
        is_sig -- this is a signature (not a template)
  ----*/
char *
get_signature(file, prenewlines, postnewlines, is_sig)
    char *file;
    int   prenewlines,
	  postnewlines,
	  is_sig;
{
    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] == '|'){
	if(is_sig && F_ON(F_DISABLE_PIPES_IN_SIGS, ps_global)){
	    q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Pipes for signatures are administratively disabled");
	    return(sig);
	}
	else if(!is_sig && F_ON(F_DISABLE_PIPES_IN_TEMPLATES, ps_global)){
	    q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Pipes for templates are administratively disabled");
	    return(sig);
	}
	    
	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(is_sig && 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);
}
