/*
 * ========================================================================
 * Copyright 2006-2008 University of Washington
 * Copyright 2013-2022 Eduardo Chappa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

/*======================================================================
   Code here for forward and reply to mail
   A few support routines as well

  This code will forward and reply to MIME messages. The Alpine 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"
#include "reply.h"
#include "status.h"
#include "radio.h"
#include "send.h"
#include "titlebar.h"
#include "mailindx.h"
#include "help.h"
#include "signal.h"
#include "mailcmd.h"
#include "alpine.h"
#include "roleconf.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/init.h"
#include "../pith/filter.h"
#include "../pith/pattern.h"
#include "../pith/charset.h"
#include "../pith/mimedesc.h"
#include "../pith/remote.h"
#include "../pith/news.h"
#include "../pith/util.h"
#include "../pith/detoken.h"
#include "../pith/newmail.h"
#include "../pith/readfile.h"
#include "../pith/tempfile.h"
#include "../pith/busy.h"
#include "../pith/ablookup.h"
#include "../pith/copyaddr.h"
#include "../pith/rules.h"

/*
 * Internal Prototypes
 */
int	 reply_poster_followup(ENVELOPE *);
int	 sigedit_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
long	 new_mail_for_pico(int, int);
void	 cmd_input_for_pico(void);
int	 display_message_for_pico(int);
char	*checkpoint_dir_for_pico(char *, size_t);
void	 resize_for_pico(void);
PCOLORS *colors_for_pico(void);
void	 free_pcolors(PCOLORS **);


/*----------------------------------------------------------------------
        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()
  ---*/
int
reply(struct pine *pine_state, ACTION_S *role_arg)
{
    ADDRESS    *saved_from, *saved_to, *saved_cc, *saved_resent;
    ADDRESS    *us_in_to_and_cc, *ap;
    ENVELOPE   *env = NULL, *outgoing;
    BODY       *body, *orig_body = NULL;
    REPLY_S     reply;
    void       *msgtext = NULL;
    char       *tmpfix = NULL, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
    long        msgno, j, totalm, rflags, *seq = NULL;
    int         i, include_text = 0, times = -1, warned = 0, rv = 0,
		flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0;
    int         rolemsg = 0, copytomsg = 0, do_role_early = 0;
    gf_io_t     pc;
    PAT_STATE   dummy;
    REDRAFT_POS_S *redraft_pos = NULL;
    ACTION_S   *role = NULL, *nrole;
    RULE_RESULT *rule;
#if	defined(DOS) && !defined(_WINDOWS)
    char *reserve;
#endif

    outgoing = mail_newenvelope();
    totalm   = mn_total_cur(pine_state->msgmap);
    seq	     = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));

    dprint((4,"\n - reply (%s msgs) -\n", comatose(totalm)));

    saved_from		  = (ADDRESS *) NULL;
    saved_to		  = (ADDRESS *) NULL;
    saved_cc		  = (ADDRESS *) NULL;
    saved_resent	  = (ADDRESS *) NULL;

    us_in_to_and_cc	  = (ADDRESS *) NULL;

    outgoing->subject	  = NULL;

    memset((void *)&reply, 0, sizeof(reply));

    if(ps_global->full_header == 2
       && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
      reply_raw_body = 1;

    /* Setup possible role */
    if(role_arg)
      role = copy_action(role_arg);

    if(!role && F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)){
	for(msgno = mn_first_cur(pine_state->msgmap);
	    msgno > 0L;  msgno = mn_next_cur(pine_state->msgmap)){

	    env = pine_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((rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE , env)) != NULL){
	        RULELIST *list = get_rulelist_from_code(V_REPLY_INDENT_RULES,
                                                ps_global->rule_list);
	        RULE_S *prule = get_rule(list, rule->number);
		if(condition_contains_token(prule->condition, ROLE_TOKEN))
		  do_role_early++;
		if(rule->result)
		  fs_give((void **)&rule->result);
		fs_give((void **)&rule);
	    }
	}
    }

    if(do_role_early){
	rflags = ROLE_REPLY;
	if(nonempty_patterns(rflags, &dummy)){
	    /* setup default role */
	    nrole = NULL;
	    j = mn_first_cur(pine_state->msgmap);
	    do {
		role = nrole;
		nrole = set_role_from_msg(pine_state, rflags,
					  mn_m2raw(pine_state->msgmap, j),
					  NULL);
	    } while(nrole && (!role || nrole == role)
		    && (j=mn_next_cur(pine_state->msgmap)) > 0L);

	    if(!role || nrole == role)
	      role = nrole;
	    else
	      role = NULL;

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

    if (role)
	ps_global->role = cpystr(role->nick); /* remember the role */

    /*
     * 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_ALT_REPLY_MENU, pine_state) 
	|| 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 = pine_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 = pine_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,env,&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 wants
	 * to post to news *and* via email, add relevant addresses to the
	 * outgoing envelope...
	 *
	 * The single message case gets 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;

	/* collect a list of addresses that are us in to and cc fields */
	if(env->to)
	  if((ap=reply_cp_addr(pine_state, 0L, NULL,
			       NULL, us_in_to_and_cc, NULL,
			       env->to, RCA_ONLY_US)) != NULL)
	    reply_append_addr(&us_in_to_and_cc, ap);

	if(env->cc)
	  if((ap=reply_cp_addr(pine_state, 0L, NULL,
			       NULL, us_in_to_and_cc, NULL,
			       env->cc, RCA_ONLY_US)) != NULL)
	    reply_append_addr(&us_in_to_and_cc, ap);

	/*------------ 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(!same_subject(outgoing->subject,
		       reply_subject(env->subject,tmp_20k_buf,SIZEOF_20KBUF))){
		fs_give((void **)&outgoing->subject);
		outgoing->subject = cpystr("Re: several messages");
	    }
	}
	else{
	   RULE_RESULT *rule;
	   rule = get_result_rule(V_RESUB_RULES,FOR_RESUB|FOR_TRIM , env);
	   if (rule){
	     outgoing->subject = reply_subject(rule->result, NULL, 0);
	     if (rule->result)
	        fs_give((void **)&rule->result);
	     fs_give((void **)&rule);
	   }
	   else
	       outgoing->subject = reply_subject(env->subject, NULL, 0);
	}
    }

    /* fill reply header */
    reply_seed(pine_state, outgoing, env, saved_from,
	       saved_to, saved_cc, saved_resent,
	       &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
    if(errmsg){
	if(*errmsg){
	    q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
	    display_message(NO_OP_COMMAND);
	}

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

    if(sp_expunge_count(pine_state->mail_stream))	/* cur msg expunged */
      goto done_early;

    if (ps_global->reply.role_chosen){
      if(role_arg)
        free_action(&role);
      role = ps_global->reply.role_chosen;
    }

    if(!role && !do_role_early){
       rflags = ROLE_REPLY;
       if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){
	    /* setup default role */
	    nrole = NULL;
	    j = mn_first_cur(pine_state->msgmap);
	    do {
		role = nrole;
		nrole = set_role_from_msg(pine_state, rflags,
					  mn_m2raw(pine_state->msgmap, j),
					  NULL);
	    } while(nrole && (!role || nrole == role)
		    && (j=mn_next_cur(pine_state->msgmap)) > 0L);

	    if(!role || nrole == role)
	      role = nrole;
	    else
	      role = NULL;

	    if(confirm_role(rflags, &role))
	      role = combine_inherited_role(role);
	    else{				/* cancel reply */
		role = NULL;
		cmd_cancelled("Reply");
		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.
     * Similarly for set_role_from_message.
     */
    env = pine_mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);

    if(role){
	rolemsg++;
	/* override fcc gotten in reply_seed */
	if(role->fcc && fcc)
	  fs_give((void **) &fcc);
    }

    if(F_ON(F_COPY_TO_TO_FROM, pine_state) && !(role && role->from)){
	/*
	 * A list of all of our addresses that appear in the To
	 * and cc fields is in us_in_to_and_cc.
	 * If there is exactly one address in that list then
	 * use it for the outgoing From.
	 */
	if(us_in_to_and_cc && !us_in_to_and_cc->next){
	    PINEFIELD *custom, *pf;
	    ADDRESS *a = NULL;
	    char *addr = NULL;

	    /*
	     * Check to see if this address is different from what
	     * we would have used anyway. If it is, notify the user
	     * with a status message. This is pretty hokey because we're
	     * mimicking how pine_send would set the From address and
	     * there is no coordination between the two.
	     */

	    /* in case user has a custom From value */
	    custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);

	    pf = (PINEFIELD *) fs_get(sizeof(*pf));
	    memset((void *) pf, 0, sizeof(*pf));
	    pf->name = cpystr("From");
	    pf->addr = &a;
	    if(set_default_hdrval(pf, custom) >= UseAsDef
	       && pf->textbuf && pf->textbuf[0]){
		removing_trailing_white_space(pf->textbuf);
		(void)removing_double_quotes(pf->textbuf);
		build_address(pf->textbuf, &addr, NULL, NULL, NULL);
		rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
		if(addr)
		  fs_give((void **) &addr);
	    }

	    if(!*pf->addr)
	      *pf->addr = generate_from();

	    if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
		copytomsg++;
		if(!role){
		    role = (ACTION_S *) fs_get(sizeof(*role));
		    memset((void *) role, 0, sizeof(*role));
		    role->is_a_role = 1;
		}

		role->from = us_in_to_and_cc;
		us_in_to_and_cc = NULL;
	    }

	    free_customs(custom);
	    free_customs(pf);
	}
    }

    if(role){
	if(rolemsg && copytomsg)
	  q_status_message1(SM_ORDER, 3, 4,
			    _("Replying using role \"%s\" and To as From"), role->nick);
	else if(rolemsg)
	  q_status_message1(SM_ORDER, 3, 4,
			    _("Replying using role \"%s\""), role->nick);
	else if(copytomsg)
	  q_status_message(SM_ORDER, 3, 4,
			   _("Replying using incoming To as outgoing From"));
    }

    if(us_in_to_and_cc)
      mail_free_address(&us_in_to_and_cc);


    seq[++times] = -1L;		/* mark end of sequence list */

    /*==========  Other miscellaneous fields ===================*/   
    outgoing->in_reply_to = reply_in_reply_to(env);
    outgoing->references = reply_build_refs(env);
    outgoing->message_id = generate_message_id(role);

    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!"));


   /*==================== 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(role, env, 0,
			       ps_global->reply.signature_bottom,
			       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, env, &redraft_pos, &impl)) &&
			!ps_global->reply.signature_bottom){

	    /*
	     * If CURSORPOS was set explicitly in sig_file, and there was a
	     * template file before that, we need to adjust the offset by the
	     * length of the template file. However, if the template had
	     * a set CURSORPOS in it then impl was 2 before getting to the
	     * signature, so offset wouldn't have been reset by the signature
	     * CURSORPOS and offset would already be correct. That case will
	     * be ok here because template_len will be 0 and adding it does
	     * nothing. If template
	     * didn't have CURSORPOS in it, then impl was 1 and got set to 2
	     * by the CURSORPOS in the sig. In that case we have to adjust the
	     * offset. That's what the next line does. It adjusts it if
	     * template_len is nonzero and if CURSORPOS was set in sig_file.
	     */
	    if(impl == 2)
	      redraft_pos->offset += template_len;
	    
	    if(*sig)
	      so_puts((STORE_S *)msgtext, sig);
	    
	    /*
	     * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
	     * is set, we won't have used it yet and want it to be non-NULL.
	     */
	    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 = pine_mail_fetchstructure(pine_state->mail_stream,
					   mn_m2raw(pine_state->msgmap, msgno),
					   &orig_body);
	    if(!env){
		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_raw_body) {
		reply_delimiter(env, role, pc);
		if(ps_global->reply.include_header)
		  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, reply_raw_body ? NULL : orig_body,
				   mn_m2raw(pine_state->msgmap, msgno),
				   reply_raw_body ? NULL : "1", 0L, pc, prefix,
				   NULL, GBPT_NONE);
	    }
	    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, role, pc);
		    if(ps_global->reply.include_header)
		      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", 0L, pc, prefix, NULL, GBPT_NONE);
		}
		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 = pine_mail_fetchstructure(pine_state->mail_stream, msgno,
				       &orig_body);

	/*
	 * If the charset of the body part is different from ascii and
	 * charset conversion is _not_ happening, then preserve the original
	 * charset from the message so that if we don't enter any new
	 * chars with the hibit set we can use the original charset.
	 * If not all those things, then don't try to preserve it.
	 */
	if(orig_body){
	    char *charset;

	    charset = parameter_val(orig_body->parameter, "charset");
	    if(charset && strucmp(charset, "us-ascii") != 0){
		CONV_TABLE *ct;

		/*
		 * There is a non-ascii charset, is there conversion happening?
		 */
		if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
		    reply.orig_charset = charset;
		    charset = NULL;
		}
	    }

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

	if(env) {
	    if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
				   msgno, NULL, msgtext, prefix,
				   include_text, role, 1, &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.origmbox	= cpystr(pine_state->mail_stream->original_mailbox
				    ? pine_state->mail_stream->original_mailbox
				    : pine_state->mail_stream->mailbox);
    reply.data.uid.msgs = (imapuid_t *) fs_get((times + 1) * sizeof(imapuid_t));
    if((reply.data.uid.validity = pine_state->mail_stream->uid_validity) != 0){
	reply.uid = 1;
	for(i = 0; i < times ; i++)
	  reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
    }
    else{
	reply.msgno = 1;
	for(i = 0; i < times ; i++)
	  reply.data.uid.msgs[i] = 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, &reply, redraft_pos, NULL, NULL, 0);

    rv++;
    pine_free_body(&body);
    if(reply.mailbox)
      fs_give((void **) &reply.mailbox);
    if(reply.origmbox)
      fs_give((void **) &reply.origmbox);
    if(reply.orig_charset)
      fs_give((void **) &reply.orig_charset);
    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 (ps_global->role)
	fs_give((void **)&ps_global->role);

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

    free_redraft_pos(&redraft_pos);
    free_action(&role);
    return rv;
}


/*
 * 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(long int rflags, ACTION_S **role)
{
    ACTION_S       *role_p = NULL;
    ACTION_S       *default_role = NULL;
    char            prompt[80], *prompt_fodder;
    int             cmd, done, ret = 1;
    void (*prev_screen)(struct pine *) = ps_global->prev_screen,
	 (*redraw)(void) = ps_global->redrawer;
    PAT_S          *curpat, *pat;
    PAT_STATE       pstate;
    HelpType        help;
    ESCKEY_S        ekey[4];

    if(!nonempty_patterns(ROLE_DO_ROLES, &pstate) || !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(((rflags & ROLE_REPLY) &&
	*role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
       ((rflags & ROLE_FORWARD) &&
	*role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
       ((rflags & ROLE_COMPOSE) &&
	*role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
       (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global)
        && !ps_global->default_role))
      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(&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(&pstate) && *role)	/* no ^T */
      ekey[2].ch    = -1;
    
    /*
     * Setup default role
     * Go through the loop just in case default_role doesn't point
     * to a real current role.
     */
    if(ps_global->default_role){
	for(pat = first_pattern(&pstate);
	    pat;
	    pat = next_pattern(&pstate)){
	    if(pat->action == ps_global->default_role){
		default_role = ps_global->default_role;
		break;
	    }
	}
    }

    curpat = NULL;

    /* override default */
    if(*role){
	for(pat = first_pattern(&pstate);
	    pat;
	    pat = next_pattern(&pstate)){
	    if(pat->action == *role){
		curpat = pat;
		break;
	    }
	}
    }

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

    done = 0;
    while(!done){
	if(curpat){
	    char buf[100];

	    help = h_role_confirm;
	    ekey[0].name  = "Y";
	    ekey[0].label = N_("Yes");
	    ekey[1].name  = "N";
	    if(default_role)
	      ekey[1].label = N_("No, use default role");
	    else
	      ekey[1].label = N_("No, use default settings");

	    ekey[2].label = N_("To Select Alternate Role");

	    if(curpat->patgrp && curpat->patgrp->nick)
	      /* TRANSLATORS: This is something like Use role <nickname of role> for Reply? */
	      snprintf(prompt, sizeof(prompt), _("Use role \"%s\" for %s? "),
		      short_str(curpat->patgrp->nick, buf, sizeof(buf), 50, MidDots),
		      prompt_fodder);
	    else
	      snprintf(prompt, sizeof(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 = N_("To Select Role");
	    snprintf(prompt, sizeof(prompt),
		    _("Press Return to %s using %s role, or ^T to select a role "),
		    prompt_fodder, default_role ? _("default") : _("no"));
	}

	prompt[sizeof(prompt)-1] = '\0';

	cmd = ps_global->send_immediately ? 'n' :
		radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
			    'y', 'x', help, RB_NORM);

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

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

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

	  case 2:					/* ^T */
	    if(role_select_screen(ps_global, &role_p, 0) >= 0){
		if(role_p){
		    for(pat = first_pattern(&pstate);
			pat;
			pat = next_pattern(&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_to_all_query - Ask user about replying to all recipients
 *
 * Returns:  -1 if cancel, 0 otherwise
 *	     by reference: flagp
 */
int
reply_to_all_query(int *flagp)
{
    char prompt[80];
    ESCKEY_S  ekey[4];
    char cmd;

    ekey[0].name  = "Y";
    ekey[0].ch = 'y';
    ekey[0].rval = 'y';
    ekey[0].label = N_("Yes");
    ekey[1].name  = "N";
    ekey[1].ch = 'n';
    ekey[1].rval = 'n';
    ekey[1].label = N_("No");
    ekey[2].name = "P";
    ekey[2].ch = 'p';
    ekey[2].rval = 'p';
    ekey[3].ch = -1;

    ps_global->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps_global);
loop:
    ekey[2].label = ps_global->reply.preserve_fields ? N_("Not Preserve") : N_("Preserve");
    snprintf(prompt, sizeof(prompt), _("Reply to all recipients%s"),
		ps_global->reply.preserve_fields ? _(" (preserving fields)? ") : "? ");

    prompt[sizeof(prompt)-1] = '\0';

    switch(cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
				'n', 'x', h_preserve_field, RB_NORM)){

      case 'x' :
	return(-1);

      case 'y' :		/* set reply-all bit */
	(*flagp) |= RSF_FORCE_REPLY_ALL;
	break;

      case 'n' :		/* clear reply-all bit */
	(*flagp) &= ~RSF_FORCE_REPLY_ALL;
	break;

      case 'p' :
        ps_global->reply.preserve_fields = 
	    (ps_global->reply.preserve_fields + 1) % 2;
	goto loop;		/* ugly, but saves me a variable */
	break;
    }

    return(0);
}


/*
 * reply_using_replyto_query - Ask user about replying with reply-to value
 *
 * Returns:  'y' if yes
 *	     'x' if cancel
 */
int
reply_using_replyto_query(void)
{
    return(want_to("Use \"Reply-To:\" address instead of \"From:\" address",
		   'y', 'x', NO_HELP,WT_SEQ_SENSITIVE));
}


/*
 * reply_text_query - Ask user about replying with text, or in the case
 * of alternate reply menu, set values to the answer to all questions
 * asked during reply.
 *
 * Returns:  1 if include the text
 *	     0 if we're NOT to include the text
 *	    -1 on cancel or error
 */
#define MAX_REPLY_OPTIONS 10
int
reply_text_query(struct pine *ps, long int many, ENVELOPE *env, char **prefix)
{
    int ret, edited = 0, headers = 0;
    static ESCKEY_S compose_style[MAX_REPLY_OPTIONS];
    int	ekey_num;

    ps->reply.use_flowed = *prefix && **prefix ? (F_OFF(F_QUELL_FLOWED_TEXT, ps)
		&& F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
		&& (strcmp(*prefix, "> ") == 0
			|| strcmp(*prefix, ">") == 0)) : 0;
    ps->reply.strip_signature = ps->full_header == 0
		&& (F_ON(F_ENABLE_STRIP_SIGDASHES, ps)
			|| F_ON(F_ENABLE_SIGDASHES, ps));
    ps->reply.keep_attach = F_ON(F_ATTACHMENTS_IN_REPLY, ps);
    ps->reply.include_header = F_ON(F_INCLUDE_HEADER, ps);
    ps->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps);
    ps->reply.signature_bottom = F_ON(F_SIG_AT_BOTTOM, ps);
    ps->reply.role_chosen = NULL;

    if(F_OFF(F_ALT_REPLY_MENU, ps)
	&& F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
	&& F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps)
	&& F_OFF(F_ALT_REPLY_MENU,ps))
	return(1);

    while(1){ 
	compose_style[ekey_num = 0].ch    = 'y';
	compose_style[ekey_num].rval  = 'y';
	compose_style[ekey_num].name  = "Y";
	compose_style[ekey_num++].label = N_("Yes");

	compose_style[ekey_num].ch    = 'n';
	compose_style[ekey_num].rval  = 'n';
	compose_style[ekey_num].name  = "N";
	compose_style[ekey_num++].label = N_("No");

	if (F_OFF(F_ALT_REPLY_MENU, ps)){	/**** Standard menu  ****/
	    /* TRANSLATORS: The final five %s's can probably be safely ignored */
	    snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply%s%s%s%s%s%s? "),
		(many > 1L) ? comatose(many) : "",
		(many > 1L) ? " " : "",
		(many > 1L) ? "s" : "",
		(many > 1L) ? "s" : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
		ps->reply.role_chosen ? "\" and role \"" : "",
		ps->reply.role_chosen ? ps->reply.role_chosen->nick : "",
		F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
	    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';

	    if (F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
	       compose_style[ekey_num].ch      = ctrl('R');
	       compose_style[ekey_num].rval    = 'r';
	       compose_style[ekey_num].name    = "^R";
	       compose_style[ekey_num++].label = N_("Edit Indent String");
	    }
	} else { 	/***** Alternate Reply Menu ********/
	   snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply (using \"%s%s%s\")? "),
		(many > 1L) ? comatose(many) : "",
		(many > 1L) ? " " : "",
		(many > 1L) ? "s" : "",
		*prefix,
		ps->reply.role_chosen ? "\" and role \"" : "",
		ps->reply.role_chosen ? ps->reply.role_chosen->nick : "");
	   tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';

	   compose_style[ekey_num].ch      = 'h';
	   compose_style[ekey_num].rval    = 'H';
	   compose_style[ekey_num].name    = "H";
	   compose_style[ekey_num++].label = ps->reply.include_header 
						? N_("No Header") : N_("Inc Headr");

	   compose_style[ekey_num].ch      = 's';
	   compose_style[ekey_num].rval    = 'S';
	   compose_style[ekey_num].name    = "S";
	   compose_style[ekey_num++].label = ps->reply.strip_signature
						? N_("No Strip"): N_("Strip Sig");

	   compose_style[ekey_num].ch      = 'a';
	   compose_style[ekey_num].rval    = 'A';
	   compose_style[ekey_num].name    = "A";
	   compose_style[ekey_num++].label = ps->reply.keep_attach 
						? N_("No Attach"): N_("Inc Attach");

	   compose_style[ekey_num].ch      = 'b';
	   compose_style[ekey_num].rval    = 'B';
	   compose_style[ekey_num].name    = "B";
	   compose_style[ekey_num++].label = ps->reply.signature_bottom
						? N_("Sig Top") : N_("Sig Bottom");

	   compose_style[ekey_num].ch      = 'r';
	   compose_style[ekey_num].rval    = 'R';
	   compose_style[ekey_num].name    = "R";
	   compose_style[ekey_num++].label = N_("Set Role");

	   compose_style[ekey_num].ch      = ctrl('R');
	   compose_style[ekey_num].rval    = 'r';
	   compose_style[ekey_num].name    = "^R";
	   compose_style[ekey_num++].label = N_("Edit Indent String");

	   /***** End Alt Reply Menu  *********/
	}

	compose_style[ekey_num].ch    = -1;
	compose_style[ekey_num].name  = NULL;
	compose_style[ekey_num].label = NULL;

	switch(ret = radio_buttons(tmp_20k_buf, 
				   ps->ttyo->screen_rows > 4
				     ? -FOOTER_ROWS(ps) : -1,
				   compose_style,
				   (edited || headers || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps))
				    ? 'y' : 'n',
				   'x', NO_HELP, RB_SEQ_SENSITIVE)){
	  case 'x':
	    cmd_cancelled("Reply");
	    return(-1);

	  case 'A':
		ps->reply.keep_attach = (ps->reply.keep_attach + 1) % 2;
		break;

	  case 'B':
		ps->reply.signature_bottom = (ps->reply.signature_bottom + 1) % 2;
		break;

	  case 'F':
		ps->reply.use_flowed = (ps->reply.use_flowed + 1) % 2;
		break;

	  case 'S':
		ps->reply.strip_signature = (ps->reply.strip_signature + 1) % 2;
	   break;

	  case 'H':
		ps->reply.include_header = (ps->reply.include_header + 1) % 2;
		headers = ps->reply.include_header;
	   break;

	  case 'R':
	  {
           void (*prev_screen)(struct pine *) = ps->prev_screen,
               (*redraw)(void) = ps->redrawer;
           ps->redrawer = NULL;
           ps->next_screen = SCREEN_FUN_NULL;
           if(role_select_screen(ps, &ps->reply.role_chosen, 1) < 0){
             cmd_cancelled("Reply");
             ps->next_screen = prev_screen;
             ps->redrawer = redraw;
	     if (ps->redrawer)
		(*ps->redrawer)();
	     continue;
           }
           ps->next_screen = prev_screen;
           ps->redrawer = redraw;
           if(ps->reply.role_chosen)
              ps->reply.role_chosen = combine_inherited_role(ps->reply.role_chosen);
	  }
	  if (ps->redrawer)
	     (*ps->redrawer)();
	  break;

	  case 'r':
	    if(prefix && *prefix){
		int  done = 0;
		char buf[64];
		int  flags;

		while(!done){
		    strncpy(buf, *prefix, sizeof(buf)-1);
		    buf[sizeof(buf)-1] = '\0';

		    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) : -1,
					    0, sizeof(buf), "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));
			    ps->reply.use_flowed = *prefix && **prefix ?
					(F_OFF(F_QUELL_FLOWED_TEXT, ps)
					&& F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
					&& (strcmp(*prefix, "> ") == 0
					|| strcmp(*prefix, ">") == 0)) : 0;
			    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->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);
	}
    }
}


/*
 * reply_poster_followup - return TRUE if "followup-to" set to "poster"
 *
 * NOTE: queues status message indicating such
 */
int
reply_poster_followup(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(ENVELOPE *env, ENVELOPE *outgoing)
{
    int    ret = 1;
    static ESCKEY_S news_opt[] = { {'f', 'f', "F", N_("Follow-up")},
				   {'r', 'r', "R", N_("Reply")},
				   {'b', 'b', "B", N_("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(
	     _("Follow-up to news group(s), Reply via email to author or Both? "),
			   -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);
    }

    return(ret);
}


/*----------------------------------------------------------------------
  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(char *file, int prenewlines, int postnewlines, int is_sig)
{
    char     *sig, *tmp_sig = NULL, sig_path[MAXPATH+1];
    int       len, do_the_pipe_thang = 0;
    long      sigsize = 0L, cntdown;

    sig_path[0] = '\0';
    if(!signature_path(file, sig_path, MAXPATH))
      return(NULL);

    dprint((5, "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(NULL);
	}
	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(NULL);
	}
	    
	sig_path[len-1] = '\0';
	removing_trailing_white_space(sig_path);
	do_the_pipe_thang++;
    }

    if(!IS_REMOTE(sig_path) && ps_global->VAR_OPER_DIR &&
       !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
			  /* TRANSLATORS: First arg is the directory name, second is
			     the file user wants to read but can't. */
			  _("Can't read file outside %s: %s"),
			  ps_global->VAR_OPER_DIR, file);
	
	return(NULL);
    }

    if(IS_REMOTE(sig_path) || 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,
						  pipe_callback, pipe_report_error)) != NULL){
			unsigned char c;
			char         *error, *q;

			gf_set_so_writec(&pc, store);
			gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, READ_FROM_LOCALE);
			gf_filter_init();

			if((error = gf_pipe(gc, pc)) != NULL){
			    (void)close_system_pipe(&syspipe, NULL, pipe_callback);
			    gf_clear_so_writec(store);
			    so_give(&store);
			    q_status_message1(SM_ORDER | SM_DING, 3, 4,
					      _("Can't get file: %s"), error);
			    return(NULL);
			}

			if(close_system_pipe(&syspipe, NULL, pipe_callback)){
			    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,
				/* TRANSLATORS: Arg is a program name */
				_("Can't execute \"%s\": Permission denied"),
				sig_path);
	}
	else if((IS_REMOTE(sig_path) &&
		 (tmp_sig = simple_read_remote_file(sig_path, REMOTE_SIG_SUBTYPE))) ||
		(tmp_sig = read_file(sig_path, READ_FROM_LOCALE)))
	  sigsize = strlen(tmp_sig);
	else
	  q_status_message2(SM_ORDER | SM_DING, 3, 4,
			    /* TRANSLATORS: First arg is error description, 2nd is
			       filename */
			    _("Error \"%s\" reading file \"%s\""),
			    error_description(errno), sig_path);
    }

    sig = get_signature_lit(tmp_sig, prenewlines, postnewlines, is_sig, 0);
    if(tmp_sig)
      fs_give((void **)&tmp_sig);

    return(sig);
}



/*----------------------------------------------------------------------
       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 message.
  ----------------------------------------------------------------------*/
int
forward(struct pine *ps, ACTION_S *role_arg)
{
    char	  *sig;
    int		   ret, forward_raw_body = 0, rv = 0, i;
    long	   msgno = 0L, j, totalmsgs, rflags;
    ENVELOPE	  *env = NULL, *outgoing;
    BODY	  *orig_body, *body = NULL;
    REPLY_S        reply;
    void	  *msgtext = NULL;
    gf_io_t	   pc;
    int            impl, template_len = 0;
    PAT_STATE      dummy;
    REDRAFT_POS_S *redraft_pos = NULL;
    ACTION_S      *role = NULL, *nrole;
#if	defined(DOS) && !defined(_WINDOWS)
    char	  *reserve;
#endif

    dprint((4, "\n - forward -\n"));

    memset((void *)&reply, 0, sizeof(reply));
    outgoing              = mail_newenvelope();

    if(ps_global->full_header == 2
       && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
      forward_raw_body = 1;

    if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
	snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s forwarded messages...", comatose(totalmsgs));
	tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
	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 = pine_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 = (F_ON(F_FORWARD_AS_ATTACHMENT, ps_global))
	   ? 'y'
	   : (totalmsgs > 1L)
	      ? want_to(_("Forward messages as a MIME digest"), 'y', 'x', NO_HELP, WT_SEQ_SENSITIVE)
	      : (ps->full_header == 2)
		 ? 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(role_arg)
      role = copy_action(role_arg);

    if(!role){
	rflags = ROLE_FORWARD;
	if(nonempty_patterns(rflags, &dummy)){
	    /* setup default role */
	    nrole = NULL;
	    j = mn_first_cur(ps->msgmap);
	    do {
		role = nrole;
		nrole = set_role_from_msg(ps, rflags,
					  mn_m2raw(ps->msgmap, j), NULL);
	    } while(nrole && (!role || nrole == role)
		    && (j=mn_next_cur(ps->msgmap)) > 0L);

	    if(!role || nrole == role)
	      role = nrole;
	    else
	      role = NULL;

	    if(confirm_role(rflags, &role))
	      role = combine_inherited_role(role);
	    else{				/* cancel reply */
		role = NULL;
		cmd_cancelled("Forward");
		so_give((STORE_S **)&msgtext);
		goto clean;
	    }
	}
    }

    if (ps_global->role)
      fs_give((void **)&ps_global->role);

    if(role){
      q_status_message1(SM_ORDER, 3, 4,
			_("Forwarding using role \"%s\""), role->nick);
      ps_global->role = cpystr(role->nick);
    }

    outgoing->message_id  = generate_message_id(role);

    if(role && role->template){
	char *filtered;

	impl = 1;
	filtered = detoken(role, (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(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
	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");
	    snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Digest of %s messages", comatose(totalmsgs));
	    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
	    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   = pine_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 = pine_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 || forward_raw_body) {
		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, forward_raw_body ? NULL : orig_body,
				       mn_m2raw(ps->msgmap, msgno),
				       forward_raw_body ? NULL : "1", 0L, pc,
				       NULL, NULL, GBPT_NONE))
		  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", 0L, pc,
					   NULL, NULL, GBPT_NONE))
		      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 = pine_mail_fetchstructure(ps->mail_stream, msgno,
					      &orig_body))
	      && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
				      NULL, msgtext,
				      FWD_NONE)))){
	q_status_message1(SM_ORDER,3,4,
		      _("Error fetching message %s. Can't forward it."),
			  long2string(msgno));
	goto clean;
    }

    if(ret != 'y' && totalmsgs == 1L && orig_body){
	char *charset;

	charset = parameter_val(orig_body->parameter, "charset");
	if(charset && strucmp(charset, "us-ascii") != 0){
	    CONV_TABLE *ct;

	    /*
	     * There is a non-ascii charset, is there conversion happening?
	     */
	    if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
		reply.orig_charset = charset;
		charset = NULL;
	    }
	}

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

	/*
	 * I don't think orig_charset is ever used except possibly
	 * right here. Hubert 2008-01-15.
	 */
	if(reply.orig_charset)
	  reply.forw = 1;
    }

    /* fill in reply structure */
    reply.forwarded	= 1;
    reply.mailbox	= cpystr(ps->mail_stream->mailbox);
    reply.origmbox	= cpystr(ps->mail_stream->original_mailbox
				    ? ps->mail_stream->original_mailbox
				    : ps->mail_stream->mailbox);
    reply.data.uid.msgs = (imapuid_t *) fs_get((totalmsgs + 1) * sizeof(imapuid_t));
    if((reply.data.uid.validity = ps->mail_stream->uid_validity) != 0){
	reply.uid = 1;
	for(msgno = mn_first_cur(ps->msgmap), i = 0;
	    msgno > 0L;
	    msgno = mn_next_cur(ps->msgmap), i++)
	  reply.data.uid.msgs[i] = mail_uid(ps->mail_stream, mn_m2raw(ps->msgmap, msgno));
    }
    else{
	reply.msgno = 1;
	for(msgno = mn_first_cur(ps->msgmap), i = 0;
	    msgno > 0L;
	    msgno = mn_next_cur(ps->msgmap), i++)
	  reply.data.uid.msgs[i] = mn_m2raw(ps->msgmap, msgno);
    }

    reply.data.uid.msgs[i] = 0;			/* tie off list */

#if	defined(DOS) && !defined(_WINDOWS)
    free((void *)reserve);
#endif
    outgoing->sparep = env && env->from ? copyaddr(env->from) : NULL;
    pine_send(outgoing, &body, "FORWARD MESSAGE",
	      role, NULL, &reply, redraft_pos,
	      NULL, NULL, 0);
    rv++;

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

    if(reply.orig_charset)
      fs_give((void **)&reply.orig_charset);

    return rv;

  bomb:
    q_status_message(SM_ORDER | SM_DING, 4, 5,
		   _("Error fetching message contents.  Can't forward message."));
    goto clean;
}


/*----------------------------------------------------------------------
       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 message.
  ----------------------------------------------------------------------*/
void
forward_text(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;
    PAT_STATE dummy;
    long      rflags = ROLE_COMPOSE;

    if((msgtext = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
	env                   = mail_newenvelope();
	body                  = mail_newbody();
	body->type            = TYPETEXT;
	body->contents.text.data = (void *) msgtext;

	if(nonempty_patterns(rflags, &dummy)){
	    /*
	     * This is really more like Compose, even though it
	     * is called Forward.
	     */
	    if(confirm_role(rflags, &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);

	env->message_id       = generate_message_id(role);

	sig = detoken(role, 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, 0);

	if((enc_error = gf_pipe(gc, pc)) == NULL){
	    pine_send(env, &body, "SEND MESSAGE", role, NULL, NULL, NULL,
		      NULL, NULL, 0);
	    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 message.
  ----------------------------------------------------------------------*/
int
bounce(struct pine *pine_state, ACTION_S *role)
{
    ENVELOPE	  *env;
    long           msgno, rawno;
    char          *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
		  *prmpt_who = NULL, *prmpt_cnf = NULL;

    dprint((4, "\n - bounce -\n"));

    if(mn_total_cur(pine_state->msgmap) > 1L){
	save_toptr = &save_to;
	if(role)
	  snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages (using role %s) to : "),
		mn_total_cur(pine_state->msgmap), role->nick);
	else
	  snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages to : "),
		mn_total_cur(pine_state->msgmap));
	tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
	prmpt_who = cpystr(tmp_20k_buf);
	snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Send %ld messages "),
		mn_total_cur(pine_state->msgmap));
	tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
	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 = pine_mail_fetchstructure(pine_state->mail_stream, rawno, NULL)) != NULL)
	  errstr = bounce_msg(pine_state->mail_stream, rawno, NULL, role,
			      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);

    return(errstr ? 0 : 1);
}



char *
bounce_msg(MAILSTREAM *stream,
	   long int rawno,
	   char *part,
	   ACTION_S *role,
	   char **to,
	   char *subject,
	   char *pmt_who,
	   char *pmt_cnf)
{
    char     *errstr = NULL;
    int	      was_seen = -1;
    ENVELOPE *outgoing;
    BODY     *body = NULL;
    MESSAGECACHE *mc;

#ifdef SMIME
	/* When we bounce a message, we will leave the original message
	 * intact, which means that it will not be signed or encrypted,
	 * so we turn off signing and encrypting now. It will be turned
	 * on again in send_exit_for_pico().
	 */
	if(ps_global->smime)
	   ps_global->smime->do_sign = ps_global->smime->do_encrypt = 0;
#endif /* SMIME */

    if((errstr = bounce_msg_body(stream, rawno, part, to, subject, &outgoing, &body, &was_seen)) == NULL){
	if(pine_simple_send(outgoing, &body, &role, pmt_who, pmt_cnf, to,
			    !(to && *to) ? SS_PROMPTFORTO : 0) < 0){
	    errstr = "";		/* p_s_s() better have explained! */
	    /* clear seen flag */
	    if(was_seen == 0 && rawno > 0L
	       && stream && rawno <= stream->nmsgs
	       && (mc = mail_elt(stream,  rawno)) && mc->seen)
	      mail_flag(stream, long2string(rawno), "\\SEEN", 0);
	}
    }

    /* Just for good measure... */
    mail_free_envelope(&outgoing);
    pine_free_body(&body);

    return(errstr);		/* no problem-o */
}


/*----------------------------------------------------------------------
    Serve up the current signature within pico for editing

    Args: file to edit

  Result: signature changed or not.
  ---*/
char *
signature_edit(char *sigfile, char *title)
{
    int	     editor_result;
    char     sig_path[MAXPATH+1], errbuf[2000], *errstr = NULL;
    char    *ret = NULL;
    STORE_S *msgso, *tmpso = NULL;
    gf_io_t  gc, pc;
    PICO     pbf;
    struct variable *vars = ps_global->vars;
    REMDATA_S *rd = NULL;

    if(!signature_path(sigfile, sig_path, MAXPATH))
      return(cpystr(_("No signature file defined.")));
    
    if(IS_REMOTE(sigfile)){
	rd = rd_create_remote(RemImap, sig_path, REMOTE_SIG_SUBTYPE,
			      NULL, "Error: ",
			      _("Can't access remote configuration."));
	if(!rd)
	  return(cpystr(_("Error attempting to edit remote configuration")));
	
	(void)rd_read_metadata(rd);

	if(rd->access == MaybeRorW){
	    if(rd->read_status == 'R')
	      rd->access = ReadOnly;
	    else
	      rd->access = ReadWrite;
	}

	if(rd->access != NoExists){

	    rd_check_remvalid(rd, 1L);

	    /*
	     * If the cached info says it is readonly but
	     * it looks like it's been fixed now, change it to readwrite.
	     */
	    if(rd->read_status == 'R'){
		rd_check_readonly_access(rd);
		if(rd->read_status == 'W'){
		    rd->access = ReadWrite;
		    rd->flags |= REM_OUTOFDATE;
		}
		else
		  rd->access = ReadOnly;
	    }
	}

	if(rd->flags & REM_OUTOFDATE){
	    if(rd_update_local(rd) != 0){

		dprint((1,
		       "signature_edit: rd_update_local failed\n"));
		rd_close_remdata(&rd);
		return(cpystr(_("Can't access remote sig")));
	    }
	}
	else
	  rd_open_remote(rd);

	if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
	    rd_close_remdata(&rd);
	    return(cpystr(_("Can't get write permission for remote sig")));
	}

	rd->flags |= DO_REMTRIM;

	strncpy(sig_path, rd->lf, sizeof(sig_path)-1);
	sig_path[sizeof(sig_path)-1] = '\0';
    }

    standard_picobuf_setup(&pbf);
    pbf.tty_fix       = PineRaw;
    pbf.composer_help = h_composer_sigedit;
    pbf.exittest      = sigedit_exit_for_pico;
    pbf.upload	       = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
			   ? upload_msg_to_pico : NULL;
    pbf.alt_ed        = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
			    ? VAR_EDITOR : NULL;
    pbf.alt_spell     = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
    pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
    pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
    pbf.allow_flowed_text = 0;

    pbf.pine_anchor   = set_titlebar(title,
				      ps_global->mail_stream,
				      ps_global->context_current,
				      ps_global->cur_folder,
				      ps_global->msgmap,
				      0, FolderName, 0, 0, NULL);

    /* NOTE: at this point, a lot of pico struct fields are null'd out
     * thanks to the leading memset; in particular "headents" which tells
     * pico to behave like a normal editor (though modified slightly to
     * let the caller dictate the file to edit and such)...
     */

    if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, sig_path)){
	size_t l;

	l = strlen(VAR_OPER_DIR) + 100;
	ret = (char *) fs_get((l+1) * sizeof(char));
	snprintf(ret, l+1, _("Can't edit file outside of %s"), VAR_OPER_DIR);
	ret[l] = '\0';
	return(ret);
    }

    /*
     * Now alloc and init the text to pass pico
     */
    if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
	ret = cpystr(_("Error allocating space for file"));
	dprint((1, "Can't alloc space for signature_edit"));
	return(ret);
    }
    else
      pbf.msgtext = so_text(msgso);

    if(can_access(sig_path, READ_ACCESS) == 0
       && !(tmpso = so_get(FileStar, sig_path, READ_ACCESS|READ_FROM_LOCALE))){
	char *problem = error_description(errno);

	snprintf(errbuf, sizeof(errbuf), _("Error editing \"%s\": %s"),
		sig_path, problem ? problem : "<NULL>");
	errbuf[sizeof(errbuf)-1] = '\0';
	ret = cpystr(errbuf);

	dprint((1, "signature_edit: can't open %s: %s", sig_path,
		   problem ? problem : "<NULL>"));
	return(ret);
    }
    else if(tmpso){			/* else, fill pico's edit buffer */
	gf_set_so_readc(&gc, tmpso);	/* read from file, write pico buf */
	gf_set_so_writec(&pc, msgso);
	gf_filter_init();		/* no filters needed */
	if((errstr = gf_pipe(gc, pc)) != NULL){
	    snprintf(errbuf, sizeof(errbuf), _("Error reading file: \"%s\""), errstr);
	    errbuf[sizeof(errbuf)-1] = '\0';
	    ret = cpystr(errbuf);
	}

	gf_clear_so_readc(tmpso);
	gf_clear_so_writec(msgso);
	so_give(&tmpso);
    }

    if(!errstr){
#ifdef _WINDOWS
	mswin_setwindowmenu (MENU_COMPOSER);
#endif

	/*------ OK, Go edit the signature ------*/
	editor_result = pico(&pbf);

#ifdef _WINDOWS
	mswin_setwindowmenu (MENU_DEFAULT);
#endif
	if(editor_result & COMP_GOTHUP){
	    hup_signal();		/* do what's normal for a hup */
	}
	else{
	    fix_windsize(ps_global);
	    init_signals();
	}

	if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
	}
	else{
            /*------ Must have an edited buffer, write it to .sig -----*/
	    our_unlink(sig_path);	/* blast old copy */
	    if((tmpso = so_get(FileStar, sig_path, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
		so_seek(msgso, 0L, 0);
		gf_set_so_readc(&gc, msgso);	/* read from pico buf */
		gf_set_so_writec(&pc, tmpso);	/* write sig file */
		gf_filter_init();		/* no filters needed */
		if((errstr = gf_pipe(gc, pc)) != NULL){
		    snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
				      errstr);
		    errbuf[sizeof(errbuf)-1] = '\0';
		    ret = cpystr(errbuf);
		}

		gf_clear_so_readc(msgso);
		gf_clear_so_writec(tmpso);
		if(so_give(&tmpso)){
		    errstr = error_description(errno);
		    snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
				      errstr);
		    errbuf[sizeof(errbuf)-1] = '\0';
		    ret = cpystr(errbuf);
		}

		if(IS_REMOTE(sigfile)){
		    int   e, we_cancel;
		    char datebuf[200];

		    datebuf[0] = '\0';

		    we_cancel = busy_cue("Copying to remote sig", NULL, 1);
		    if((e = rd_update_remote(rd, datebuf)) != 0){
			if(e == -1){
			    q_status_message2(SM_ORDER | SM_DING, 3, 5,
			      _("Error opening temporary sig file %s: %s"),
				rd->lf, error_description(errno));
			    dprint((1,
			       "write_remote_sig: error opening temp file %s\n",
			       rd->lf ? rd->lf : "?"));
			}
			else{
			    q_status_message2(SM_ORDER | SM_DING, 3, 5,
					    _("Error copying to %s: %s"),
					    rd->rn, error_description(errno));
			    dprint((1,
			      "write_remote_sig: error copying from %s to %s\n",
			      rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
			}
			
			q_status_message(SM_ORDER | SM_DING, 5, 5,
	   _("Copy of sig to remote folder failed, changes NOT saved remotely"));
		    }
		    else{
			rd_update_metadata(rd, datebuf);
			rd->read_status = 'W';
		    }

		    rd_close_remdata(&rd);

		    if(we_cancel)
		      cancel_busy_cue(-1);
		}
	    }
	    else{
		snprintf(errbuf, sizeof(errbuf), _("Error writing \"%s\""), sig_path);
		errbuf[sizeof(errbuf)-1] = '\0';
		ret = cpystr(errbuf);
		dprint((1, "signature_edit: can't write %s",
			   sig_path));
	    }
	}
    }
    
    standard_picobuf_teardown(&pbf);
    so_give(&msgso);
    return(ret);
}


/*----------------------------------------------------------------------
    Serve up the current signature within pico for editing

    Args: literal signature to edit

  Result: raw edited signature is returned in result arg
  ---*/
char *
signature_edit_lit(char *litsig, char **result, char *title, HelpType composer_help)
{
    int	     editor_result;
    char    *errstr = NULL;
    char    *ret = NULL;
    STORE_S *msgso;
    PICO     pbf;
    struct variable *vars = ps_global->vars;

    standard_picobuf_setup(&pbf);
    pbf.tty_fix       = PineRaw;
    pbf.search_help   = h_sigedit_search;
    pbf.composer_help = composer_help;
    pbf.exittest      = sigedit_exit_for_pico;
    pbf.upload	       = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
			   ? upload_msg_to_pico : NULL;
    pbf.alt_ed        = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
			    ? VAR_EDITOR : NULL;
    pbf.alt_spell     = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
    pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
    pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
    pbf.allow_flowed_text = 0;

    pbf.pine_anchor   = set_titlebar(title,
				      ps_global->mail_stream,
				      ps_global->context_current,
				      ps_global->cur_folder,
				      ps_global->msgmap,
				      0, FolderName, 0, 0, NULL);

    /* NOTE: at this point, a lot of pico struct fields are null'd out
     * thanks to the leading memset; in particular "headents" which tells
     * pico to behave like a normal editor (though modified slightly to
     * let the caller dictate the file to edit and such)...
     */

    /*
     * Now alloc and init the text to pass pico
     */
    if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
	ret = cpystr(_("Error allocating space"));
	dprint((1, "Can't alloc space for signature_edit_lit"));
	return(ret);
    }
    else
      pbf.msgtext = so_text(msgso);
    
    so_puts(msgso, litsig ? litsig : "");


    if(!errstr){
#ifdef _WINDOWS
	mswin_setwindowmenu (MENU_COMPOSER);
#endif

	/*------ OK, Go edit the signature ------*/
	editor_result = pico(&pbf);

#ifdef _WINDOWS
	mswin_setwindowmenu (MENU_DEFAULT);
#endif
	if(editor_result & COMP_GOTHUP){
	    hup_signal();		/* do what's normal for a hup */
	}
	else{
	    fix_windsize(ps_global);
	    init_signals();
	}

	if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
	    ret = cpystr(_("Edit Cancelled"));
	}
	else{
            /*------ Must have an edited buffer, write it to .sig -----*/
	    unsigned char c;
	    int cnt = 0;
	    char *p;

	    so_seek(msgso, 0L, 0);
	    while(so_readc(&c, msgso))
	      cnt++;
	    
	    *result = (char *)fs_get((cnt+1) * sizeof(char));
	    p = *result;
	    so_seek(msgso, 0L, 0);
	    while(so_readc(&c, msgso))
	      *p++ = c;
	    
	    *p = '\0';
	}
    }
    
    standard_picobuf_teardown(&pbf);
    so_give(&msgso);
    return(ret);
}


/*
 * Returns  0  for Save Changes and exit
 *          1  for Cancel Exit
 *         -1  exit but Dont Save Changes
 */
int
sigedit_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
		      char **result)
{
    int	      rv;
    char     *rstr = NULL;
    void    (*redraw)(void) = ps_global->redrawer;
    static ESCKEY_S opts[] = {
	{'s', 's', "S", N_("Save changes")},
	{'d', 'd', "D", N_("Don't save changes")},
	{-1, 0, NULL, NULL}
    };

    ps_global->redrawer = redraw_pico;
    fix_windsize(ps_global);

    while(1){
	rv = radio_buttons(_("Exit editor? "),
			   -FOOTER_ROWS(ps_global), opts,
			   's', 'x', h_exit_editor, RB_NORM);
	if(rv == 's'){				/* user ACCEPTS! */
	    break;
	}
	else if(rv == 'd'){			/* Declined! */
	    rstr = _("No Changes Saved");
	    break;
	}
	else if(rv == 'x'){			/* Cancelled! */
	    rstr = _("Exit Cancelled");
	    break;
	}
    }

    if(result)
      *result = rstr;

    ps_global->redrawer = redraw;
    return((rv == 's') ? 0 : (rv == 'd') ? -1 : 1);
}


/*
 * Common stuff we almost always want to set when calling pico.
 */
void
standard_picobuf_setup(PICO *pbf)
{
    memset(pbf, 0, sizeof(*pbf));

    pbf->pine_version	= ALPINE_VERSION;
    pbf->fillcolumn	= ps_global->composer_fillcol;
    pbf->menu_rows	= FOOTER_ROWS(ps_global) - 1;
    pbf->colors		= colors_for_pico();
    pbf->wordseps	= user_wordseps(ps_global->VAR_WORDSEPS);
    pbf->helper		= helper;
    pbf->showmsg	= display_message_for_pico;
    pbf->suspend	= do_suspend;
    pbf->keybinput	= cmd_input_for_pico;
    pbf->tty_fix	= ttyfix;		/* watch out for this one */
    pbf->newmail	= new_mail_for_pico;
    pbf->ckptdir	= checkpoint_dir_for_pico;
    pbf->resize		= resize_for_pico;
    pbf->input_cs	= ps_global->input_cs;
    pbf->winch_cleanup	= winch_cleanup;
    pbf->search_help	= h_composer_search;
    pbf->ins_help	= h_composer_ins;
    pbf->ins_m_help	= h_composer_ins_m;
    pbf->composer_help	= h_composer;
    pbf->browse_help	= h_composer_browse;
    pbf->attach_help	= h_composer_ctrl_j;

    pbf->pine_flags =
       ( (F_ON(F_CAN_SUSPEND,ps_global)			? P_SUSPEND	: 0L)
       | (F_ON(F_USE_FK,ps_global)			? P_FKEYS	: 0L)
       | (ps_global->restricted				? P_SECURE	: 0L)
       | (F_ON(F_ALT_ED_NOW,ps_global)			? P_ALTNOW	: 0L)
       | (F_ON(F_USE_CURRENT_DIR,ps_global)		? P_CURDIR	: 0L)
       | (F_ON(F_SUSPEND_SPAWNS,ps_global)		? P_SUBSHELL	: 0L)
       | (F_ON(F_COMPOSE_MAPS_DEL,ps_global)		? P_DELRUBS	: 0L)
       | (F_ON(F_ENABLE_TAB_COMPLETE,ps_global)		? P_COMPLETE	: 0L)
       | (F_ON(F_SHOW_CURSOR,ps_global)			? P_SHOCUR	: 0L)
       | (F_ON(F_DEL_FROM_DOT,ps_global)		? P_DOTKILL	: 0L)
       | (F_ON(F_ENABLE_DOT_FILES,ps_global)		? P_DOTFILES	: 0L)
       | (F_ON(F_ALLOW_GOTO,ps_global)			? P_ALLOW_GOTO	: 0L)
       | (F_ON(F_ENABLE_SEARCH_AND_REPL,ps_global)	? P_REPLACE	: 0L)
       | (!ps_global->pass_ctrl_chars
          && !ps_global->pass_c1_ctrl_chars		? P_HICTRL	: 0L)
       | ((F_ON(F_ENABLE_ALT_ED,ps_global)
           || F_ON(F_ALT_ED_NOW,ps_global)
	   || (ps_global->VAR_EDITOR
	       && ps_global->VAR_EDITOR[0]
	       && ps_global->VAR_EDITOR[0][0]))
							? P_ADVANCED	: 0L)
       | ((!ps_global->keyboard_charmap
           || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
							? P_HIBITIGN	: 0L));

    if(ps_global->VAR_OPER_DIR){
	pbf->oper_dir    = ps_global->VAR_OPER_DIR;
	pbf->pine_flags |= P_TREE;
    }

    pbf->home_dir = ps_global->home_dir;
}


void
standard_picobuf_teardown(PICO *pbf)
{
    if(pbf){
	if(pbf->colors)
	  free_pcolors(&pbf->colors);

	if(pbf->wordseps)
	  fs_give((void **) &pbf->wordseps);
    }
}


/*----------------------------------------------------------------------
    Call back for pico to use to check for new mail.
     
Args: cursor -- pointer to in to tell caller if cursor location changed
                if NULL, turn off cursor positioning.
      timing -- whether or not it's a good time to check 
 

Returns: returns 1 on success, zero on error.
----*/      
long
new_mail_for_pico(int timing, int status)
{
    /*
     * If we're not interested in the status, don't display the busy
     * cue either...
     */
    /* don't know where the cursor's been, reset it */
    clear_cursor_pos();
    return(new_mail(0, timing,
		    (status ? NM_STATUS_MSG : NM_NONE) | NM_DEFER_SORT
						       | NM_FROM_COMPOSER));
}


void
cmd_input_for_pico(void)
{
    zero_new_mail_count();
}


/*----------------------------------------------------------------------
    Call back for pico to get newmail status messages displayed

Args: x -- char processed

Returns: 
----*/      
int
display_message_for_pico(int x)
{
    int rv;
    
    if(ps_global->send_immediately)
      return 0;
    clear_cursor_pos();			/* can't know where cursor is */
    mark_status_dirty();		/* don't count on cached text */
    fix_windsize(ps_global);
    init_sigwinch();
    display_message(x);
    rv = ps_global->mangled_screen;
    ps_global->mangled_screen = 0;
    return(rv);
}


/*----------------------------------------------------------------------
    Call back for pico to get desired directory for its check point file
     
  Args: s -- buffer to write directory name
	n -- length of that buffer

  Returns: pointer to static buffer
----*/      
char *
checkpoint_dir_for_pico(char *s, size_t n)
{
#if defined(DOS) || defined(OS2)
    /*
     * we can't assume anything about root or home dirs, so
     * just plunk it down in the same place as the pinerc
     */
    if(!getenv("HOME")){
	char *lc = last_cmpnt(ps_global->pinerc);

	if(lc != NULL){
	    strncpy(s, ps_global->pinerc, MIN(n-1,lc-ps_global->pinerc));
	    s[MIN(n-1,lc-ps_global->pinerc)] = '\0';
	}
	else{
	    strncpy(s, ".\\", n-1);
	    s[n-1] = '\0';
	}
    }
    else
#endif
    strncpy(s, ps_global->home_dir, n-1);
    s[n-1] = '\0';

    return(s);
}


/*----------------------------------------------------------------------
  Call back for pico to tell us the window size's changed

  Args: none

  Returns: none (but pine's ttyo structure may have been updated)
----*/
void
resize_for_pico(void)
{
    fix_windsize(ps_global);
}


PCOLORS *
colors_for_pico(void)
{
    PCOLORS *colors = NULL;
    struct variable *vars = ps_global->vars;

    if (pico_usingcolor()){
      colors = (PCOLORS *)fs_get(sizeof(PCOLORS));

      colors->tbcp = current_titlebar_color();

      if (VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
	colors->klcp = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
				      VAR_KEYLABEL_BACK_COLOR);
	if (!pico_is_good_colorpair(colors->klcp))
	  free_color_pair(&colors->klcp);
      }
      else colors->klcp = NULL;

      if (colors->klcp && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
	colors->kncp = new_color_pair(VAR_KEYNAME_FORE_COLOR, 
				      VAR_KEYNAME_BACK_COLOR);
      }
      else colors->kncp = NULL;

      if (VAR_STATUS_FORE_COLOR && VAR_STATUS_BACK_COLOR){
	colors->stcp = new_color_pair(VAR_STATUS_FORE_COLOR, 
				      VAR_STATUS_BACK_COLOR);
      }
      else colors->stcp = NULL;

      if (VAR_PROMPT_FORE_COLOR && VAR_PROMPT_BACK_COLOR){
	colors->prcp = new_color_pair(VAR_PROMPT_FORE_COLOR, 
				      VAR_PROMPT_BACK_COLOR);
      }
      else colors->prcp = NULL;

      if (VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR){
	colors->qlcp = new_color_pair(VAR_QUOTE1_FORE_COLOR, 
				      VAR_QUOTE1_BACK_COLOR);
      }
      else colors->qlcp = NULL;

      if (VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR){
	colors->qllcp = new_color_pair(VAR_QUOTE2_FORE_COLOR, 
				       VAR_QUOTE2_BACK_COLOR);
      }
      else colors->qllcp = NULL;

      if (VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR){
	colors->qlllcp = new_color_pair(VAR_QUOTE3_FORE_COLOR, 
				        VAR_QUOTE3_BACK_COLOR);
      }
      else colors->qlllcp = NULL;

      if (VAR_NORM_FORE_COLOR && VAR_NORM_BACK_COLOR){
	colors->ntcp = new_color_pair(VAR_NORM_FORE_COLOR,
				      VAR_NORM_BACK_COLOR);
      }
      else colors->ntcp = NULL;

      if (VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR){
	colors->rtcp = new_color_pair(VAR_REV_FORE_COLOR,
				      VAR_REV_BACK_COLOR);
      }
      else colors->rtcp = NULL;

      if (VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR){
	colors->sbcp = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
				      VAR_SIGNATURE_BACK_COLOR);
      }
      else colors->sbcp = NULL;
    }
    
    return colors;
}


void
free_pcolors(PCOLORS **colors)
{
    if (*colors){
	  if ((*colors)->tbcp)
		free_color_pair(&(*colors)->tbcp);
	  if ((*colors)->kncp)
		free_color_pair(&(*colors)->kncp);
	  if ((*colors)->klcp)
		free_color_pair(&(*colors)->klcp);
	  if ((*colors)->stcp)
		free_color_pair(&(*colors)->stcp);
	  if ((*colors)->prcp)
		free_color_pair(&(*colors)->prcp);
	  if ((*colors)->qlcp)
		free_color_pair(&(*colors)->qlcp);
	  if ((*colors)->qllcp)
		free_color_pair(&(*colors)->qllcp);
	  if ((*colors)->qlllcp)
		free_color_pair(&(*colors)->qlllcp);
	  if ((*colors)->ntcp)
		free_color_pair(&(*colors)->ntcp);
	  if ((*colors)->rtcp)
		free_color_pair(&(*colors)->rtcp);
	  if ((*colors)->sbcp)
		free_color_pair(&(*colors)->sbcp);
	  fs_give((void **)colors);
	  *colors = NULL;
	}
}
