#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailcmd.c,v 4.608 1999/11/17 01:51:37 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   *
    ***********************************************************************
 

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

/*======================================================================
     mailcmd.c
     The meat and pototoes of mail processing here:
       - initial command processing and dispatch
       - save message
       - capture address off incoming mail
       - jump to specific numbered message
       - open (broach) a new folder
       - search message headers (where is) command
  ====*/

#include "headers.h"


/*
 * Internal Prototypes
 */
void      cmd_delete PROTO((struct pine *, MSGNO_S *, int));
void      cmd_undelete PROTO((struct pine *, MSGNO_S *, int));
void      cmd_reply PROTO((struct pine *, MSGNO_S *, int));
void      cmd_forward PROTO((struct pine *, MSGNO_S *, int));
void      cmd_bounce PROTO((struct pine *, MSGNO_S *, int));
void      cmd_print PROTO((struct pine *, MSGNO_S *, int, int));
void      cmd_save PROTO((struct pine *, MAILSTREAM *, MSGNO_S *, int));
void      cmd_export PROTO((struct pine *, MSGNO_S *, int, int));
void      cmd_pipe PROTO((struct pine *, MSGNO_S *, int));
PIPE_S	 *cmd_pipe_open PROTO((char *, char **, int, gf_io_t *));
void	  prime_raw_text_getc PROTO((MAILSTREAM *, long));
STORE_S	 *list_mgmt_text PROTO((RFC2369_S *, long));
void	  list_mgmt_screen PROTO((STORE_S *));
void      cmd_flag PROTO((struct pine *, MSGNO_S *, int));
int	  cmd_flag_prompt PROTO((struct pine *, struct flag_screen *));
long	  save PROTO((struct pine *, MAILSTREAM *,
		      CONTEXT_S *, char *, MSGNO_S *, int));
void      get_save_fldr_from_env PROTO((char *, int, ENVELOPE *,
					 struct pine *, long, char *));
void	  saved_date PROTO((char *, char *));
int	  save_ex_output_body PROTO((MAILSTREAM *, long, char *, BODY *,
				     unsigned long *, gf_io_t));
int	  save_ex_replace_body PROTO((char *, unsigned long *,BODY *,gf_io_t));
int	  save_ex_mask_types PROTO((char *, unsigned long *, gf_io_t));
int	  save_ex_explain_body PROTO((BODY *, unsigned long *, gf_io_t));
int	  save_ex_explain_parts PROTO((BODY *, int, unsigned long *, gf_io_t));
int	  save_ex_output_line PROTO((char *, unsigned long *, gf_io_t));
int	  create_for_save PROTO((MAILSTREAM *, CONTEXT_S *, char *));
void	  flag_string PROTO((MESSAGECACHE *, long, char *));
int	  select_sort PROTO((struct pine *, int, SortOrder *, int *));
void	  aggregate_select PROTO((struct pine *, MSGNO_S *, int, int));
int	  select_number PROTO((MAILSTREAM *, MSGNO_S *, long));
int	  select_date PROTO((MAILSTREAM *, MSGNO_S *, long));
int	  select_text PROTO((MAILSTREAM *, MSGNO_S *, long));
int	  select_flagged PROTO((MAILSTREAM *, MSGNO_S *, long));
int	  apply_command PROTO((struct pine *, MAILSTREAM *, MSGNO_S *, int));
void	  search_headers PROTO((struct pine *, MAILSTREAM *, int, MSGNO_S *));
char	 *currentf_sequence PROTO((MAILSTREAM *, MSGNO_S *, long, long *,int));
char	 *invalid_elt_sequence PROTO((MAILSTREAM *, MSGNO_S *));
char	 *selected_sequence PROTO((MAILSTREAM *, MSGNO_S *, long *));
int	  any_messages PROTO((MSGNO_S *, char *, char *));
int	  can_set_flag PROTO((struct pine *, char *));
int	  bezerk_delimiter PROTO((ENVELOPE *, gf_io_t, int));
int	  move_filtered_msgs PROTO((MAILSTREAM *, MSGNO_S *, char *));
void	  delete_filtered_msgs PROTO((MAILSTREAM *));
char	 *move_read_msgs PROTO((MAILSTREAM *, char *, char *, long));
int	  read_msg_prompt PROTO((long, char *));
char	 *move_read_incoming PROTO((MAILSTREAM *, CONTEXT_S *, char *,
				    char **, char *));
void	  cross_delete_crossposts PROTO((MAILSTREAM *));
void      menu_clear_cmd_binding PROTO((struct key_menu *, int));
int	  update_folder_spec PROTO((char *, char *));
SEARCHSET *visible_searchset PROTO((MAILSTREAM *, MSGNO_S *));
int       trivial_patgrp PROTO((PATGRP_S *));


/*
 * List of Select options used by apply_* functions...
 */
static char *sel_pmt1 = "ALTER message selection : ";
ESCKEY_S sel_opts1[] = {
    {'a', 'a', "A", "unselect All"},
    {'c', 'c', "C", NULL},
    {'b', 'b', "B", "Broaden selctn"},
    {'n', 'n', "N", "Narrow selctn"},
    {'f', 'f', "F", "Flip selected"},
    {-1, 0, NULL, NULL}
};


char *sel_pmt2 = "SELECT criteria : ";
static ESCKEY_S sel_opts2[] = {
    {'a', 'a', "A", "select All"},
    {'c', 'c', "C", "select Cur"},
    {'n', 'n', "N", "Number"},
    {'d', 'd', "D", "Date"},
    {'t', 't', "T", "Text"},
    {'s', 's', "S", "Status"},
    {-1, 0, NULL, NULL}
};


static char *sel_pmt3 = "APPLY command : ";
static ESCKEY_S sel_opts3[] = {
    {'d', 'd',  "D", "Del"},
    {'u', 'u',  "U", "Undel"},
    {'r', 'r',  "R", "Reply"},
    {'f', 'f',  "F", "Forward"},
    {'%', '%',  "%", "Print"},
    {'t', 't',  "T", "TakeAddr"},
    {'s', 's',  "S", "Save"},
    {'e', 'e',  "E", "Export"},
    { -1,   0, NULL, NULL},
    { -1,   0, NULL, NULL},
    { -1,   0, NULL, NULL},
    { -1,   0, NULL, NULL},
    {-1, 0, NULL, NULL}
};


static char *sel_flag = 
    "Select New, Deleted, Answered, or Important messages ? ";
static char *sel_flag_not = 
    "Select NOT New, NOT Deleted, NOT Answered or NOT Tagged msgs ? ";
static ESCKEY_S sel_flag_opt[] = {
    {'n', 'n', "N", "New"},
    {'*', '*', "*", "Important"},
    {'d', 'd', "D", "Deleted"},
    {'a', 'a', "A", "Answered"},
    {'!', '!', "!", "Not"},
    {-1, 0, NULL, NULL}
};


static ESCKEY_S sel_date_opt[] = {
    {0, 0, NULL, NULL},
    {ctrl('P'), 12, "^P", "Prev Day"},
    {ctrl('N'), 13, "^N", "Next Day"},
    {ctrl('X'), 11, "^X", "Cur Msg"},
    {ctrl('W'), 14, "^W", "Toggle When"},
    {KEY_UP,    12, "", ""},
    {KEY_DOWN,  13, "", ""},
    {-1, 0, NULL, NULL}
};


static char *sel_text =
    "Select based on To, From, Cc, Recip, Subject fields or All message text ? ";
static char *sel_not_text =
    "Select based on NOT To, From, Cc, Recip, Subject fields or All message text ? ";
static ESCKEY_S sel_text_opt[] = {
    {'f', 'f', "F", "From"},
    {'s', 's', "S", "Subject"},
    {'t', 't', "T", "To"},
    {'a', 'a', "A", "All Text"},
    {'c', 'c', "C", "Cc"},
    {'!', '!', "!", "Not"},
    {'r', 'r', "R", "Recipient"},
    {'p', 'p', "P", "Participant"},
    {-1, 0, NULL, NULL}
};

static char *select_num =
  "Enter comma-delimited list of numbers (dash between ranges): ";


/*----------------------------------------------------------------------
         The giant switch on the commands for index and viewing

  Input:  command  -- The command char/code
          in_index -- flag indicating command is from index
          orig_command -- The original command typed before pre-processing
  Output: force_mailchk -- Set to tell caller to force call to new_mail().

  Result: Manifold

          Returns 1 if the message number or attachment to show changed 
 ---*/
int
process_cmd(state, stream, msgmap, command, in_index, force_mailchk)
    struct pine *state;
    MAILSTREAM  *stream;
    MSGNO_S     *msgmap;
    int		 command, in_index;
    int		*force_mailchk;
{
    int           question_line, a_changed, we_cancel;
    long          new_msgno, del_count, old_msgno, i, old_max_msgno;
    char         *newfolder, prompt[MAX_SCREEN_COLS+1];
    CONTEXT_S    *tc;
    COLOR_PAIR   *lastc = NULL, *newc = NULL;
#if	defined(DOS) && !defined(_WINDOWS)
    extern long coreleft();
#endif

    dprint(4, (debugfile, "\n - process_cmd((%d)%c) -\n",
                                                 command, (char)command));

    question_line         = -FOOTER_ROWS(state);
    state->mangled_screen = 0;
    state->mangled_footer = 0;
    state->mangled_header = 0;
    state->next_screen    = SCREEN_FUN_NULL;
    old_msgno             = mn_get_cur(msgmap);
    a_changed             = FALSE;
    *force_mailchk        = 0;

    switch (command) {
	/*------------- Help --------*/
      case MC_HELP :
	if(state->nr_mode) {
	    q_status_message(SM_ORDER, 0, 3,
			     "No help text currently available");
	    break;
	}

	/*
	 * We're not using the h_mail_view portion of this right now because
	 * that call is being handled in scrolltool() before it gets
	 * here.  Leave it in case we change how it works.
	 */
	helper(in_index?h_mail_index : h_mail_view,
	       in_index ? "HELP FOR MESSAGE INDEX VIEW":"HELP FOR MESSAGE VIEW",
	       HLPD_NONE);
	dprint(2, (debugfile,"MAIL_CMD: did help command\n"));
	state->mangled_screen = 1;
	break;


          /*--------- Return to main menu ------------*/
      case MC_MAIN :
	state->next_screen = main_menu_screen;
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	dprint(2, (debugfile,"MAIL_CMD: going back to main menu\n"));
	break;


          /*------- View message text --------*/
      case MC_VIEW_TEXT :
	if(any_messages(msgmap, NULL, "to View")){
	    state->next_screen = mail_view_screen;
#if	defined(DOS) && !defined(WIN32)
	    flush_index_cache();		/* save room on PC */
#endif
	}

	break;


          /*------- View attachment --------*/
      case MC_VIEW_ATCH :
	state->next_screen = attachment_screen;
	dprint(2, (debugfile,"MAIL_CMD: going to attachment screen\n"));
	break;


          /*---------- Previous message ----------*/
      case MC_PREVITEM :
	if(any_messages(msgmap, NULL, NULL)){
	    if((i = mn_get_cur(msgmap)) > 1L){
		mn_dec_cur(stream, msgmap);
		if(i == mn_get_cur(msgmap))
		  q_status_message(SM_ORDER, 0, 2,
				  "Already on first message in Zoomed Index");
	    }
	    else
	      q_status_message(SM_ORDER, 0, 1, "Already on first message");
	}

	break;


          /*---------- Next Message ----------*/
      case MC_NEXTITEM :
	if(mn_get_total(msgmap) > 0L
	   && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
	    mn_inc_cur(stream, msgmap);
	    if(i == mn_get_cur(msgmap))
	      any_messages(NULL, "more", "in Zoomed Index");
	}
	else{
	    prompt[0] = '\0';
	    if(!state->nr_mode
	       && (IS_NEWS(stream)
		   || (state->context_current->use & CNTXT_INCMNG))){
		char nextfolder[MAXPATH];

		strcpy(nextfolder, state->cur_folder);
		if(next_folder(NULL, nextfolder, nextfolder,
			       state->context_current, NULL))
		  strcat(prompt, ".  Press TAB for next folder.");
		else
		  strcat(prompt, ".  No more folders to TAB to.");
	    }

	    any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
			 prompt[0] ? prompt : NULL);

	    if(!IS_NEWS(stream))
	      *force_mailchk = 1;
	}

	break;


          /*---------- Delete message ----------*/
      case MC_DELETE :
	cmd_delete(state, msgmap, 0);
	break;
          

          /*---------- Undelete message ----------*/
      case MC_UNDELETE :
	cmd_undelete(state, msgmap, 0);
	break;


          /*---------- Reply to message ----------*/
      case MC_REPLY :
	cmd_reply(state, msgmap, 0);
	break;


          /*---------- Forward message ----------*/
      case MC_FORWARD :
	cmd_forward(state, msgmap, 0);
	break;


          /*---------- Quit pine ------------*/
      case MC_QUIT :
	state->next_screen = quit_screen;
	dprint(1, (debugfile,"MAIL_CMD: quit\n"));		    
	break;


          /*---------- Compose message ----------*/
      case MC_COMPOSE :
	state->prev_screen = in_index ? mail_index_screen :
                                        mail_view_screen;
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	compose_screen(state);
	state->mangled_screen = 1;
	if (state->next_screen)
	  a_changed = TRUE;
	break;


          /*---------- Alt Compose message ----------*/
      case MC_ROLE :
	state->prev_screen = in_index ? mail_index_screen :
                                        mail_view_screen;
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	alt_compose_screen(state);
	state->mangled_screen = 1;
	if (state->next_screen)
	  a_changed = TRUE;
	break;


          /*--------- Folders menu ------------*/
      case MC_FOLDERS :
	state->start_in_context = 1;

          /*--------- Top of Folders list menu ------------*/
      case MC_COLLECTIONS :
	state->next_screen = folder_screen;
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	dprint(2, (debugfile,"MAIL_CMD: going to folder/collection menu\n"));
	break;


          /*---------- Open specific new folder ----------*/
      case MC_GOTO :
	tc = (state->context_last && !NEWS_TEST(state->context_current)) 
	       ? state->context_last : state->context_current;

	newfolder = broach_folder(question_line, 1, &tc);
#if	defined(DOS) && !defined(_WINDOWS)
	if(newfolder && *newfolder == '{' && coreleft() < 20000){
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "Not enough memory to open IMAP folder");
	    newfolder = NULL;
	}
#endif
	if(newfolder){
	    visit_folder(state, newfolder, tc);
	    a_changed = TRUE;
	}

	break;
    	  
    	    
          /*------- Go to Index Screen ----------*/
      case MC_INDEX :
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	state->next_screen = mail_index_screen;
	break;

          /*------- Skip to next interesting message -----------*/
      case MC_TAB :
	new_msgno = next_sorted_flagged((F_UNDEL 
					 | F_UNSEEN
					 | ((IS_NEWS(stream)
					     && F_ON(F_FAKE_NEW_IN_NEWS,state))
					     ? F_RECENT : 0)
					 | ((F_ON(F_TAB_TO_NEW,state))
					     ? 0 : F_OR_FLAG)),
					stream, mn_get_cur(msgmap), NULL);

	/*
	 * If there weren't any unread messages left, OR there
	 * aren't any messages at all, we may want to offer to
	 * go on to the next folder...
	 */
	if(new_msgno == mn_get_cur(msgmap)){
	    char ret = 'n';
	    int  in_inbox = !strucmp(state->cur_folder,state->inbox_name);

	    if(!state->nr_mode && state->context_current
	       && ((NEWS_TEST(state->context_current)
		    && context_isambig(state->cur_folder))
		   || ((state->context_current->use & CNTXT_INCMNG)
		       && (in_inbox
			   || folder_index(state->cur_folder,
					   state->context_current,
					   FI_FOLDER) >= 0)))){
		char	    nextfolder[MAXPATH];
		MAILSTREAM *nextstream = NULL;
		long	    recent_cnt;

		strcpy(nextfolder, state->cur_folder);
		while(1){
		    if(!(next_folder(&nextstream, nextfolder, nextfolder,
				     state->context_current, &recent_cnt))){
			if(!in_inbox){
			    static ESCKEY_S inbox_opt[] = {
				{'y', 'y', "Y", "Yes"},
				{'n', 'n', "N", "No"},
				{TAB, 'z', "Tab", "To Inbox"},
				{-1, 0, NULL, NULL}
			    };

			    sprintf(prompt, "No more %ss.  Return to \"%s\"? ",
				    (state->context_current->use&CNTXT_INCMNG)
				      ? "incoming folder" : "news group", 
				    state->inbox_name);

			    ret = radio_buttons(prompt, -FOOTER_ROWS(state),
						inbox_opt, 'y', 'x',
						NO_HELP, RB_NORM);

			    /*
			     * 'z' is a synonym for 'y'.  It is not 'y'
			     * so that it isn't displayed as a default
			     * action with square-brackets around it
			     * in the keymenu...
			     */
			    if(ret == 'y' || ret == 'z'){
				visit_folder(state, state->inbox_name,
					     state->context_current);
				a_changed = TRUE;
			    }
			}
			else
			  q_status_message1(SM_ORDER, 0, 2, "No more %ss",
				     (state->context_current->use&CNTXT_INCMNG)
				        ? "incoming folder" : "news group");

			break;
		    }

		    sprintf(prompt, "View next %s \"%s\" (%s recent)? ",
			    (state->context_current->use & CNTXT_INCMNG)
			      ? "Incoming folder" : "news group",
			    nextfolder,
			    recent_cnt ? long2string(recent_cnt) : "some");

		    /*
		     * When help gets added, this'll have to become
		     * a loop like the rest...
		     */
		    if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
			static ESCKEY_S next_opt[] = {
			    {'y', 'y', "Y", "Yes"},
			    {'n', 'n', "N", "No"},
			    {TAB, 'n', "Tab", "NextNew"},
			    {-1, 0, NULL, NULL}
			};

			ret = radio_buttons(prompt, -FOOTER_ROWS(state),
					    next_opt, 'y', 'x', NO_HELP,
					    RB_NORM);
			if(ret == 'x'){
			    cmd_cancelled(NULL);
			    break;
			}
		    }

		    if(ret == 'y' || F_ON(F_AUTO_OPEN_NEXT_UNREAD, state)){
			visit_folder(state, nextfolder,state->context_current);
			a_changed = TRUE;
			break;
		    }
		}

		if(nextstream)
		  mail_close(nextstream);
	    }
	    else
	      any_messages(NULL,
			   (mn_get_total(msgmap) > 0L)
			     ? IS_NEWS(stream) ? "more undeleted" : "more new"
			     : NULL,
			   NULL);
	}
	else
	  mn_set_cur(msgmap, new_msgno);

	break;


          /*------- Zoom -----------*/
      case MC_ZOOM :
	/*
	 * Right now the way zoom is implemented is sort of silly.
	 * There are two per-message flags where just one and a 
	 * global "zoom mode" flag to suppress messags from the index
	 * should suffice.
	 */
	if(any_messages(msgmap, NULL, "to Zoom on")){
	    if(unzoom_index(state, msgmap)){
		dprint(4, (debugfile, "\n\n ---- Exiting ZOOM mode ----\n"));
		q_status_message(SM_ORDER,0,2, "Index Zoom Mode is now off");
	    }
	    else if(i = zoom_index(state, msgmap)){
		if(any_lflagged(msgmap, MN_HIDE)){
		    dprint(4,(debugfile,"\n\n ---- Entering ZOOM mode ----\n"));
		    q_status_message2(SM_ORDER, 0, 2,
	"In Zoomed Index of %s message%s.  Use \"Z\" to restore regular Index",
				      comatose(i), plural(i));
		}
		else
		  q_status_message(SM_ORDER, 0, 2,
		     "All messages selected, so not entering Index Zoom Mode");
	    }
	    else
	      any_messages(NULL, "selected", "to Zoom on");
	}

	break;


          /*---------- print message on paper ----------*/
      case MC_PRINTMSG :
	if(any_messages(msgmap, NULL, "to print"))
	  cmd_print(state, msgmap, 0, in_index);

	break;


          /*---------- Take Address ----------*/
      case MC_TAKE :
	if(any_messages(msgmap, NULL, "to Take address from"))
	  cmd_take_addr(state, msgmap, 0);

	break;


          /*---------- Save Message ----------*/
      case MC_SAVE :
	if(any_messages(msgmap, NULL, "to Save"))
	  cmd_save(state, stream, msgmap, 0);

	break;


          /*---------- Export message ----------*/
      case MC_EXPORT :
	if(any_messages(msgmap, NULL, "to Export")){
	    cmd_export(state, msgmap, question_line, 0);
	    state->mangled_footer = 1;
	}

	break;


          /*---------- Expunge ----------*/
      case MC_EXPUNGE :
	dprint(2, (debugfile, "\n - expunge -\n"));
	if(IS_NEWS(stream) && stream->rdonly){
	    if((del_count = count_flagged(stream, F_DEL)) > 0L){
		state->mangled_footer = 1;
		sprintf(prompt, "Exclude %ld message%s from %s", del_count,
			plural(del_count), pretty_fn(state->cur_folder));
		if(F_ON(F_FULL_AUTO_EXPUNGE, state)
		   || (F_ON(F_AUTO_EXPUNGE, state)
		       && (state->context_current
		           && (state->context_current->use & CNTXT_INCMNG))
		       && context_isambig(state->cur_folder))
		   || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
		    msgno_exclude_deleted(stream, msgmap);
		    clear_index_cache();

		    /*
		     * This is kind of surprising at first. For most sort
		     * orders, if the whole set is sorted, then any subset
		     * is also sorted. Not so for OrderedSubject sort.
		     * If you exclude the first message of a subject group
		     * then you change the date that group is to be sorted on.
		     */
		    if(mn_get_sort(msgmap) == SortSubject2)
		      refresh_sort(msgmap, FALSE);

		    state->mangled_body = 1;
		    state->mangled_header = 1;
		    q_status_message2(SM_ORDER, 0, 4, "%s message%s excluded",
				      long2string(del_count),
				      plural(del_count));
		}
		else
		  any_messages(NULL, NULL, "Excluded");
	    }
	    else
	      any_messages(NULL, "deleted", "to Exclude");

	    break;
	}
	else if(READONLY_FOLDER){
	    q_status_message(SM_ORDER, 0, 4,
			     "Can't expunge. Folder is read-only");
	    break;
	}

	if(del_count = count_flagged(stream, F_DEL)){
	    int ret;

	    sprintf(prompt, "Expunge %ld message%s from %s", del_count,
		    plural(del_count), pretty_fn(state->cur_folder));
	    state->mangled_footer = 1;
	    if(F_ON(F_FULL_AUTO_EXPUNGE, state)
	       || (F_ON(F_AUTO_EXPUNGE, state)
		   && ((!strucmp(state->cur_folder,state->inbox_name))
		       || (state->context_current->use & CNTXT_INCMNG))
		   && context_isambig(state->cur_folder))
	       || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
	      ret = 'y';

	    if(ret == 'x')
	      cmd_cancelled("Expunge");

	    if(ret != 'y')
	      break;
	}

	dprint(8,(debugfile, "Expunge max:%ld cur:%ld kill:%d\n",
		  mn_get_total(msgmap), mn_get_cur(msgmap), del_count));

	old_max_msgno = mn_get_total(msgmap);
	lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
				ps_global->VAR_TITLE_BACK_COLOR,
				PSC_REV|PSC_RET);

	PutLine0(0, 0, "**");			/* indicate delay */

	if(lastc){
	    (void)pico_set_colorp(lastc, PSC_NONE);
	    free_color_pair(&lastc);
	}

	MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
	fflush(stdout);

	we_cancel = busy_alarm(1, "Expunging", NULL, 0);
	delete_filtered_msgs(stream);
	ps_global->expunge_in_progress = 1;
	mail_expunge(stream);
	ps_global->expunge_in_progress = 0;
	if(we_cancel)
	  cancel_busy_alarm((state->expunge_count > 0) ? 0 : -1);

	dprint(2,(debugfile,"expunge complete cur:%ld max:%ld\n",
		  mn_get_cur(msgmap), mn_get_total(msgmap)));
	/*
	 * This is only actually necessary if this causes the width of the
	 * message number field to change.  That is, it depends on the
	 * format the user is using as well as on the max_msgno.  Since it
	 * should be rare, we'll just do it whenever it happens.
	 * Also have to check for an increase in max_msgno on new mail.
	 */
	if(old_max_msgno >= 1000L && mn_get_total(msgmap) < 1000L
	   || old_max_msgno >= 10000L && mn_get_total(msgmap) < 10000L
	   || old_max_msgno >= 100000L && mn_get_total(msgmap) < 100000L){
	    clear_index_cache();
	    state->mangled_body = 1;
	}

	/*
	 * mm_exists and mm_expunge take care of updating max_msgno
	 * and selecting a new message should the selected get removed
	 */
	reset_check_point();
	lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
				ps_global->VAR_TITLE_BACK_COLOR,
				PSC_REV|PSC_RET);
	PutLine0(0, 0, "  ");			/* indicate delay's over */

	if(lastc){
	    (void)pico_set_colorp(lastc, PSC_NONE);
	    free_color_pair(&lastc);
	}

	fflush(stdout);

	if(state->expunge_count <= 0)
	  if(del_count)
	    q_status_message1(SM_ORDER, 0, 3,
			      "No messages expunged from folder \"%s\"",
			      pretty_fn(state->cur_folder));
	  else
	    q_status_message(SM_ORDER, 0, 3,
			 "No messages marked deleted.  No messages expunged.");

	break;


          /*------- Unexclude -----------*/
      case MC_UNEXCLUDE :
	if(!(IS_NEWS(stream) && stream->rdonly)){
	    q_status_message(SM_ORDER, 0, 3,
			     "Unexclude not available for mail folders");
	}
	else if(any_lflagged(msgmap, MN_EXLD)){
	    SEARCHPGM *pgm;
	    long       i;
	    int	       exbits;

	    /*
	     * Since excluded means "hidden deleted" and "killed",
	     * the count should reflect the former.
	     */
	    pgm = mail_newsearchpgm();
	    pgm->deleted = 1;
	    mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
	    for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
	      if(mail_elt(stream, i)->searched
		 && get_lflag(stream, NULL, i, MN_EXLD)
		 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
		      && (exbits & MSG_EX_FILTERED)))
		del_count++;

	    if(del_count > 0L){
		state->mangled_footer = 1;
		sprintf(prompt, "UNexclude %ld message%s in %s", del_count,
			plural(del_count), pretty_fn(state->cur_folder));
		if(F_ON(F_FULL_AUTO_EXPUNGE, state)
		   || (F_ON(F_AUTO_EXPUNGE, state)
		       && (state->context_current
			   && (state->context_current->use & CNTXT_INCMNG))
		       && context_isambig(state->cur_folder))
		   || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
		    int visible = mn_get_total(msgmap) != 0;

		    msgno_include(stream, msgmap, FALSE);
		    clear_index_cache();

		    /*
		     * Have to add the excluded messages into the
		     * sort array.
		     */
		    refresh_sort(msgmap, FALSE);
		    state->mangled_header = 1;
		    q_status_message2(SM_ORDER, 0, 4,
				      "%s message%s UNexcluded",
				      long2string(del_count),
				      plural(del_count));

		    if(!visible)	/* hilite last message */
		      mn_set_cur(msgmap, mn_get_total(msgmap));
		}
		else
		  any_messages(NULL, NULL, "UNexcluded");
	    }
	    else
	      any_messages(NULL, "excluded", "to UNexclude");
	}
	else
	  any_messages(NULL, "excluded", "to UNexclude");

	break;


          /*------- Make Selection -----------*/
      case MC_SELECT :
	if(any_messages(msgmap, NULL, "to Select")){
	    aggregate_select(state, msgmap, question_line, in_index);
	    if(in_index && any_lflagged(msgmap, MN_SLCT) > 0L
	       && !any_lflagged(msgmap, MN_HIDE)
	       && F_ON(F_AUTO_ZOOM, state))
	      (void) zoom_index(state, msgmap);
	}

	break;


          /*------- Toggle Current Message Selection State -----------*/
      case MC_SELCUR :
	if(any_messages(msgmap, NULL, NULL)
	   && individual_select(state, msgmap, question_line, in_index)
	   && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
	    /* advance current */
	    mn_inc_cur(stream, msgmap);
	}

	break;


          /*------- Apply command -----------*/
      case MC_APPLY :
	if(any_messages(msgmap, NULL, NULL)){
	    if(any_lflagged(msgmap, MN_SLCT) > 0L){
		if(apply_command(state, stream, msgmap, question_line)
		   && F_ON(F_AUTO_UNZOOM, state))
		  unzoom_index(state, msgmap);
	    }
	    else
	      any_messages(NULL, NULL, "to Apply command to.  Try \"Select\"");
	}

	break;


          /*-------- Sort command -------*/
      case MC_SORT :
	{
	    SortOrder sort = mn_get_sort(msgmap);
	    int	      rev  = mn_get_revsort(msgmap);

	    dprint(1, (debugfile,"MAIL_CMD: sort\n"));		    
	    if(select_sort(state, question_line, &sort, &rev))
	      sort_folder(ps_global->msgmap, sort, rev, TRUE);

	    state->mangled_footer = 1;
	}

	break;


          /*------- Toggle Full Headers -----------*/
      case MC_FULLHDR :
	state->full_header = !state->full_header;
	q_status_message3(SM_ORDER, 0, 3,
		"Display of full headers is now o%s.  Use %s to turn back o%s",
			    state->full_header ? "n" : "ff",
			    F_ON(F_USE_FK, state) ? "F9" : "H",
			    !state->full_header ? "n" : "ff");
	a_changed = TRUE;
	break;


          /*------- Bounce -----------*/
      case MC_BOUNCE :
	cmd_bounce(state, msgmap, 0);
	break;


          /*------- Flag -----------*/
      case MC_FLAG :
	dprint(4, (debugfile, "\n - flag message -\n"));
	cmd_flag(state, msgmap, 0);
	break;


          /*------- Pipe message -----------*/
      case MC_PIPE :
	cmd_pipe(state, msgmap, 0);
	break;


          /*--------- Default, unknown command ----------*/
      default:
	panic("Unexpected command case");
	break;
    }

    return(a_changed || mn_get_cur(msgmap) != old_msgno);
}



/*----------------------------------------------------------------------
   Complain about bogus input

  Args: ch -- input command to complain about
	help -- string indicating where to get help

 ----*/
void
bogus_command(cmd, help)
    int   cmd;
    char *help;
{
    if(cmd == ctrl('Q') || cmd == ctrl('S'))
      q_status_message1(SM_ASYNC, 0, 2,
 "%s char received.  Set \"preserve-start-stop\" feature in Setup/Config.",
			pretty_command(cmd));
    else if(cmd == KEY_JUNK)
      q_status_message3(SM_ORDER, 0, 2,
		      "Invalid key pressed.%s%s%s",
		      (help) ? " Use " : "",
		      (help) ?  help   : "",
		      (help) ? " for help" : "");
    else
      q_status_message4(SM_ORDER, 0, 2,
		      "Command \"%s\" not defined for this screen.%s%s%s",
		      pretty_command(cmd),
		      (help) ? " Use " : "",
		      (help) ?  help   : "",
		      (help) ? " for help" : "");
}


/*----------------------------------------------------------------------
   Reveal Keymenu to Pine Command mappings

  Args: 

 ----*/
int
menu_command(keystroke, menu)
    int keystroke;
    struct key_menu *menu;
{
    int i, n;

    if(!menu)
      return(MC_UNKNOWN);

    if(F_ON(F_USE_FK,ps_global)){
	/* No alpha commands permitted in function key mode */
	if(keystroke < 0x0100 && isalpha((unsigned char) keystroke))
	  return(MC_UNKNOWN);

	/* Tres simple: compute offset, and test */
	if(keystroke >= F1 && keystroke <= F12){
	    n = (menu->which * 12) + (keystroke - F1);
	    if(bitnset(n, menu->bitmap))
	      return(menu->keys[n].bind.cmd);
	}
    }
    else if(keystroke >= F1 && keystroke <= F12)
      return(MC_UNKNOWN);

    /* if ascii, coerce lower case */
    if(keystroke < 0x0100 && isupper((unsigned char) keystroke))
      keystroke = tolower((unsigned char) keystroke);

    /* keep this here for Windows port */
    if((keystroke = validatekeys(keystroke)) == KEY_JUNK)
      return(MC_UNKNOWN);

    /* Scan the list for any keystroke/command binding */
    for(i = (menu->how_many * 12) - 1;  i >= 0; i--)
      if(bitnset(i, menu->bitmap))
	for(n = menu->keys[i].bind.nch - 1; n >= 0; n--)
	  if(keystroke == menu->keys[i].bind.ch[n])
	    return(menu->keys[i].bind.cmd);

    /*
     * If explicit mapping failed, check feature mappings and
     * hardwired defaults...
     */
    if(F_ON(F_ENABLE_PRYNT,ps_global)
	&& (keystroke == 'y' || keystroke == 'Y')){
	/* SPECIAL CASE: Scan the list for print bindings */
	for(i = (menu->how_many * 12) - 1;  i >= 0; i--)
	  if(bitnset(i, menu->bitmap))
	    if(menu->keys[i].bind.cmd == MC_PRINTMSG
	       || menu->keys[i].bind.cmd == MC_PRINTTXT)
	      return(menu->keys[i].bind.cmd);
    }

    if(F_ON(F_ENABLE_LESSTHAN_EXIT,ps_global)
       && (keystroke == '<' || keystroke == ','
	   || (F_ON(F_ARROW_NAV,ps_global) && keystroke == KEY_LEFT))){
	/* SPECIAL CASE: Scan the list for MC_EXIT bindings */
	for(i = (menu->how_many * 12) - 1;  i >= 0; i--)
	  if(bitnset(i, menu->bitmap))
	    if(menu->keys[i].bind.cmd == MC_EXIT)
	      return(MC_EXIT);
    }

    /*
     * If no match after scanning bindings, try universally
     * bound keystrokes...
     */
    switch(keystroke){
      case KEY_MOUSE :
	return(MC_MOUSE);

      case ctrl('P') :
      case KEY_UP :
	return(MC_CHARUP);

      case ctrl('N') :
      case KEY_DOWN :
	return(MC_CHARDOWN);

      case ctrl('F') :
      case KEY_RIGHT :
	return(MC_CHARRIGHT);

      case ctrl('B') :
      case KEY_LEFT :
	return(MC_CHARLEFT);

      case ctrl('A') :
	return(MC_GOTOBOL);

      case ctrl('E') :
	return(MC_GOTOEOL);

      case  ctrl('L') :
	return(MC_REPAINT);

      case KEY_RESIZE :
	return(MC_RESIZE);

      case NO_OP_IDLE:
      case NO_OP_COMMAND:
	if(USER_INPUT_TIMEOUT(ps_global))
	  user_input_timeout_exit(ps_global->hours_to_timeout); /* no return */

	return(MC_NONE);

      default :
	break;
    }

    return(MC_UNKNOWN);			/* utter failure */
}



/*----------------------------------------------------------------------
   Set up a binding for cmd, with one key bound to it.
   Use menu_add_binding to add more keys to this binding.

  Args: menu   -- the keymenu
	key    -- the initial key to bind to
	cmd    -- the command to initialize to
	name   -- a pointer to the string to point name to
	label  -- a pointer to the string to point label to
	keynum -- which key in the keys array to initialize

 ----*/
void
menu_init_binding(menu, key, cmd, name, label, keynum)
    struct key_menu *menu;
    int              key, cmd;
    char            *name, *label;
    int              keynum;
{
    /* if ascii, coerce to lower case */
    if(key < 0x0100 && isupper((unsigned char)key))
      key = tolower((unsigned char)key);

    /* remove binding from any other key */
    menu_clear_cmd_binding(menu, cmd);

    menu->keys[keynum].name     = name;
    menu->keys[keynum].label    = label;
    menu->keys[keynum].bind.cmd = cmd;
    menu->keys[keynum].bind.nch = 0;
    menu->keys[keynum].bind.ch[menu->keys[keynum].bind.nch++] = key;
}


/*----------------------------------------------------------------------
   Add a key/command binding to the given keymenu structure

  Args: 

 ----*/
void
menu_add_binding(menu, key, cmd)
    struct key_menu *menu;
    int		     key, cmd;
{
    int i, n;

    /* NOTE: cmd *MUST* already have had a binding */
    for(i = (menu->how_many * 12) - 1;  i >= 0; i--)
      if(menu->keys[i].bind.cmd == cmd){
	  for(n = menu->keys[i].bind.nch - 1;
	      n >= 0 && key != menu->keys[i].bind.ch[n];
	      n--)
	    ;

	  /* if ascii, coerce to lower case */
	  if(key < 0x0100 && isupper((unsigned char)key))
	    key = tolower((unsigned char)key);

	  if(n < 0)		/* not already bound, bind it */
	    menu->keys[i].bind.ch[menu->keys[i].bind.nch++] = key;

	  break;
      }
}


/*----------------------------------------------------------------------
   REMOVE a key/command binding from the given keymenu structure

  Args: 

 ----*/
int
menu_clear_binding(menu, key)
    struct key_menu *menu;
    int		     key;
{
    int i, n;

    /* if ascii, coerce to lower case */
    if(key < 0x0100 && isupper((unsigned char)key))
      key = tolower((unsigned char)key);

    for(i = (menu->how_many * 12) - 1;  i >= 0; i--)
      for(n = menu->keys[i].bind.nch - 1; n >= 0; n--)
	if(key == menu->keys[i].bind.ch[n]){
	    int cmd = menu->keys[i].bind.cmd;

	    for(--menu->keys[i].bind.nch; n < menu->keys[i].bind.nch; n++)
	      menu->keys[i].bind.ch[n] = menu->keys[i].bind.ch[n+1];

	    return(cmd);
	}

    return(MC_UNKNOWN);
}


void
menu_clear_cmd_binding(menu, cmd)
    struct key_menu *menu;
    int		     cmd;
{
    int i;

    for(i = (menu->how_many * 12) - 1;  i >= 0; i--){
	if(cmd == menu->keys[i].bind.cmd){
	    menu->keys[i].name     = NULL;
	    menu->keys[i].label    = NULL;
	    menu->keys[i].bind.cmd = 0;
	    menu->keys[i].bind.nch = 0;
	    menu->keys[i].bind.ch[0] = 0;
	}
    }
}


int
menu_binding_index(menu, cmd)
    struct key_menu *menu;
    int		     cmd;
{
    int i;

    for(i = 0; i < menu->how_many * 12; i++)
      if(cmd == menu->keys[i].bind.cmd)
	return(i);

    return(-1);
}


/*----------------------------------------------------------------------
   Complain about command on empty folder

  Args: map -- msgmap 
	type -- type of message that's missing
	cmd -- string explaining command attempted

 ----*/
int
any_messages(map, type, cmd)
    MSGNO_S *map;
    char *type, *cmd;
{
    if(mn_get_total(map) <= 0L){
	q_status_message4(SM_ORDER, 0, 2, "No %s%smessages%s%s",
			  type ? type : "",
			  type ? " " : "",
			  (!cmd || *cmd != '.') ? " " : "",
			  cmd ? cmd : "in folder");
	return(FALSE);
    }

    return(TRUE);
}


/*----------------------------------------------------------------------
   test whether or not we have a valid stream to set flags on

  Args: state -- pine state containing vital signs
	cmd -- string explaining command attempted

  Result: returns 1 if we can set flags, otw 0 and complains

 ----*/
int
can_set_flag(state, cmd)
    struct pine *state;
    char	*cmd;
{
    if(READONLY_FOLDER || state->dead_stream){
	q_status_message2(SM_ORDER | (state->dead_stream ? SM_DING : 0), 0, 3,
			  "Can't %s message.  Folder is %s.", cmd,
			  (state->dead_stream) ? "closed" : "read-only");
	return(FALSE);
    }

    return(TRUE);
}



/*----------------------------------------------------------------------
   Complain about command on empty folder

  Args: type -- type of message that's missing
	cmd -- string explaining command attempted

 ----*/
void
cmd_cancelled(cmd)
    char *cmd;
{
    q_status_message1(SM_INFO, 0, 2, "%s cancelled", cmd ? cmd : "Command");
}



/*----------------------------------------------------------------------
   Execute DELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag set

 ----*/
void
cmd_delete(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    int	  lastmsg, opts = NSF_TRUST_FLAGS;
    long  msgno, del_count = 0L;
    char *sequence = NULL, prompt[128];

    dprint(4, (debugfile, "\n - delete message -\n"));
    if(!(any_messages(msgmap, NULL, "to Delete")
	 && can_set_flag(state, "delete")))
      return;

    if(state->io_error_on_stream) {
	state->io_error_on_stream = 0;
	mail_check(state->mail_stream);		/* forces write */
    }

    if(agg){
	sequence = selected_sequence(state->mail_stream, msgmap, &del_count);
	sprintf(prompt, "%ld selected message%s marked for deletion",
		del_count, plural(del_count));
    }
    else{
	msgno	  = mn_get_cur(msgmap);
	del_count = 1L;				/* return current */
	sequence  = cpystr(long2string(mn_m2raw(msgmap, mn_get_cur(msgmap))));
	lastmsg	  = (msgno >= mn_get_total(msgmap));
	sprintf(prompt, "%s%s marked for deletion",
		lastmsg ? "Last message" : "Message ",
		lastmsg ? "" : long2string(msgno));
    }

    dprint(3,(debugfile, "DELETE: msg %s\n", sequence));
    mail_flag(state->mail_stream, sequence, "\\DELETED", ST_SET);
    fs_give((void **) &sequence);

    if(!agg){

	if(F_ON(F_DEL_SKIPS_DEL, state)){
	    mn_set_cur(msgmap, next_sorted_flagged(F_UNDEL, state->mail_stream,
						   msgno, &opts));
	}
	else{
	    mn_inc_cur(state->mail_stream, msgmap);
	}

	if(!state->nr_mode
	   && ((IS_NEWS(state->mail_stream)
		|| ((state->context_current->use & CNTXT_INCMNG)
		    && context_isambig(state->cur_folder)))
	       && (msgno == next_sorted_flagged(F_UNDEL|F_UNSEEN,
						state->mail_stream,
						msgno, &opts)))){
	    char nextfolder[MAXPATH];

	    strcpy(nextfolder, state->cur_folder);
	    strcat(prompt, next_folder(NULL, nextfolder, nextfolder,
				       state->context_current, NULL)
			      ? ".  Press TAB for next folder."
			      : ".  No more folders to TAB to.");
	}
    }

    q_status_message(SM_ORDER, 0, 3, prompt);
}



/*----------------------------------------------------------------------
   Execute UNDELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag UNset

 ----*/
void
cmd_undelete(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    long	  del_count;
    char	 *sequence;
    int		  wasdeleted = FALSE;
    MESSAGECACHE *mc;

    dprint(4, (debugfile, "\n - undelete -\n"));
    if(!(any_messages(msgmap, NULL, "to Undelete")
	 && can_set_flag(state, "undelete")))
      return;

    if(agg){
	del_count = 0L;				/* return current */
	sequence = selected_sequence(state->mail_stream, msgmap, &del_count);
    }
    else{
	del_count = 1L;				/* return current */
	sequence  = cpystr(long2string(mn_m2raw(msgmap, mn_get_cur(msgmap))));
	wasdeleted = ((mc = mail_elt(state->mail_stream,
				      mn_m2raw(msgmap, mn_get_cur(msgmap))))
		       && mc->valid
		       && mc->deleted);
    }

    dprint(3,(debugfile, "UNDELETE: msg %s\n", sequence));

    mail_flag(state->mail_stream, sequence, "\\DELETED", 0L);
    fs_give((void **) &sequence);

    if(del_count == 1L){
	update_titlebar_status();
	q_status_message(SM_ORDER, 0, 3,
			wasdeleted
			 ? "Deletion mark removed, message won't be deleted"
			 : "Message not marked for deletion; no action taken");
    }
    else
      q_status_message2(SM_ORDER, 0, 3,
			"Deletion mark removed from %s message%s",
			comatose(del_count), plural(del_count));

    if(state->io_error_on_stream) {
	state->io_error_on_stream = 0;
	mail_check(state->mail_stream);		/* forces write */
    }
}



/*----------------------------------------------------------------------
   Execute FLAG message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message FLAG flag set or UNset

 ----*/
void
cmd_flag(state, msgmap, agg)
    struct pine *state;
    MSGNO_S     *msgmap;
    int		 agg;
{
    char	  *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
    long	   unflagged, flagged, flags;
    MESSAGECACHE  *mc = NULL;
    struct	   flag_table  *fp;
    struct flag_screen flag_screen;
    static char   *flag_screen_text1[] = {
	"    Set desired flags for current message below.  An 'X' means set",
	"    it, and a ' ' means to unset it.  Choose \"Exit\" when finished.",
	NULL
    };
    static char   *flag_screen_text2[] = {
	"    Set desired flags below for selected messages.  A '?' means to",
	"    leave the flag unchanged, 'X' means to set it, and a ' ' means",
	"    to unset it.  Use the \"Return\" key to toggle, and choose",
	"    \"Exit\" when finished.",
	NULL
    };
    static char   *flag_screen_boiler_plate[] = {
	"",
	"            Set        Flag Name",
	"            ---   ----------------------",
	NULL
    };
    static struct  flag_table ftbl[] = {
	/*
	 * At some point when user defined flags are possible,
	 * it should just be a simple matter of grabbing this
	 * array from the heap and explicitly filling the
	 * non-system flags in at run time...
	 *  {NULL, h_flag_user, F_USER, 0, 0},
	 */
	{"Important", h_flag_important, F_FLAG, 0, 0},
	{"New",	  h_flag_new, F_SEEN, 0, 0},
	{"Answered",  h_flag_answered, F_ANS, 0, 0},
	{"Deleted",   h_flag_deleted, F_DEL, 0, 0},
	{NULL, NO_HELP, 0, 0, 0}
    };

    if(!(any_messages(msgmap, NULL, "to Flag")
	 && can_set_flag(state, "flag")))
      return;

    if(state->io_error_on_stream) {
	state->io_error_on_stream = 0;
	mail_check(state->mail_stream); /* forces write */
	return;
    }

    flag_screen.flag_table  = ftbl;
    flag_screen.explanation = screen_text;
    if(agg){
	if(!pseudo_selected(msgmap))
	  return;

	exp = flag_screen_text2;
	for(fp = ftbl; fp->name; fp++){
	    fp->set = CMD_FLAG_UNKN;		/* set to unknown */
	    fp->ukn = TRUE;
	}
    }
    else if(mc = mail_elt(state->mail_stream,
			  mn_m2raw(msgmap, mn_get_cur(msgmap)))){
	exp = flag_screen_text1;
	for(fp = &ftbl[0]; fp->name; fp++){
	    fp->ukn = 0;
	    fp->set = ((fp->flag == F_SEEN && !mc->seen)
		       || (fp->flag == F_DEL && mc->deleted)
		       || (fp->flag == F_FLAG && mc->flagged)
		       || (fp->flag == F_ANS && mc->answered))
			? CMD_FLAG_SET : CMD_FLAG_CLEAR;
	}
    }
    else{
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Error accessing message data");
	return;
    }

#ifdef _WINDOWS
    if (mswin_usedialog ()) {
	if (!os_flagmsgdialog (&ftbl[0]))
	  return;
    }
    else
#endif	    
    if(F_ON(F_FLAG_SCREEN_DFLT, ps_global)
       || !cmd_flag_prompt(state, &flag_screen)){
	screen_text[0] = "";
	for(p = &screen_text[1]; *exp; p++, exp++)
	  *p = *exp;

	for(exp = flag_screen_boiler_plate; *exp; p++, exp++)
	  *p = *exp;

	*p = NULL;

	flag_maintenance_screen(state, &flag_screen);
    }

    /* reaquire the elt pointer */
    mc = mail_elt(state->mail_stream,mn_m2raw(msgmap,mn_get_cur(msgmap)));

    for(fp = ftbl; fp->name; fp++){
	flags = -1;
	switch(fp->flag){
	  case F_SEEN:
	    if((!agg && fp->set != !mc->seen)
	       || (agg && fp->set != CMD_FLAG_UNKN)){
		flagit = "\\SEEN";
		if(fp->set){
		    flags     = 0L;
		    unflagged = F_SEEN;
		}
		else{
		    flags     = ST_SET;
		    unflagged = F_UNSEEN;
		}
	    }

	    break;

	  case F_ANS:
	    if((!agg && fp->set != mc->answered)
	       || (agg && fp->set != CMD_FLAG_UNKN)){
		flagit = "\\ANSWERED";
		if(fp->set){
		    flags     = ST_SET;
		    unflagged = F_UNANS;
		}
		else{
		    flags     = 0L;
		    unflagged = F_ANS;
		}
	    }

	    break;

	  case F_DEL:
	    if((!agg && fp->set != mc->deleted)
	       || (agg && fp->set != CMD_FLAG_UNKN)){
		flagit = "\\DELETED";
		if(fp->set){
		    flags     = ST_SET;
		    unflagged = F_UNDEL;
		}
		else{
		    flags     = 0L;
		    unflagged = F_DEL;
		}
	    }

	    break;

	  case F_FLAG:
	    if((!agg && fp->set != mc->flagged)
	       || (agg && fp->set != CMD_FLAG_UNKN)){
		flagit = "\\FLAGGED";
		if(fp->set){
		    flags     = ST_SET;
		    unflagged = F_UNFLAG;
		}
		else{
		    flags     = 0L;
		    unflagged = F_FLAG;
		}
	    }

	    break;

	  default:
	    break;
	}

	flagged = 0L;
	if(flags >= 0L
	   && (seq = currentf_sequence(state->mail_stream, msgmap,
				       unflagged, &flagged, 1))){
	    mail_flag(state->mail_stream, seq, flagit, flags);
	    fs_give((void **)&seq);
	    if(flagged){
		sprintf(tmp_20k_buf, "%slagged%s%s%s%s%s message%s%s \"%s\"",
			(fp->set) ? "F" : "Unf",
			agg ? " " : "",
			agg ? long2string(flagged) : "",
			(agg && flagged != mn_total_cur(msgmap))
			  ? " (of " : "",
			(agg && flagged != mn_total_cur(msgmap))
			  ? comatose(mn_total_cur(msgmap)) : "",
			(agg && flagged != mn_total_cur(msgmap))
			  ? ")" : "",
			agg ? plural(flagged) : " ",
			agg ? "" : long2string(mn_get_cur(msgmap)),
			fp->name);
		q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
	    }
	}
    }

    if(!answer)
      q_status_message(SM_ORDER, 0, 2, "No status flags changed.");

  fini:
    if(agg)
      restore_selected(msgmap);
}



/*----------------------------------------------------------------------
   Offer concise status line flag prompt 

  Args: state --  Various satate info
        flags -- flags to offer setting

 Result: TRUE if flag to set specified in flags struct or FALSE otw

 ----*/
int
cmd_flag_prompt(state, flags)
    struct pine	       *state;
    struct flag_screen *flags;
{
    int			r, setflag = 1;
    struct flag_table  *fp;
    static char *flag_text = "Flag New, Deleted, Answered, or Important ? ";
    static char *flag_text2	=
	"Flag NOT New, NOT Deleted, NOT Answered, or NOT Important ? ";
    static ESCKEY_S flag_text_opt[] = {
	{'n', 'n', "N", "New"},
	{'*', '*', "*", "Important"},
	{'d', 'd', "D", "Deleted"},
	{'a', 'a', "A", "Answered"},
	{'!', '!', "!", "Not"},
	{ctrl('T'), 10, "^T", "To Flag Details"},
	{-1, 0, NULL, NULL}
    };

    while(1){
	r = radio_buttons(setflag ? flag_text : flag_text2,
			  -FOOTER_ROWS(state), flag_text_opt, '*', 'x',
			  NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
	if(r == 'x')			/* ol'cancelrooney */
	  return(TRUE);
	else if(r == 10)		/* return and goto flag screen */
	  return(FALSE);
	else if(r == '!')		/* flip intention */
	  setflag = !setflag;
	else
	  break;
    }

    for(fp = flags->flag_table; fp->name; fp++)
      if((r == 'n' && fp->flag == F_SEEN)
	 || (r == '*' && fp->flag == F_FLAG)
	 || (r == 'd' && fp->flag == F_DEL)
	 || (r == 'a' && fp->flag == F_ANS)){
	  fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
	  break;
      }

    return(TRUE);
}



/*----------------------------------------------------------------------
   Execute REPLY message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: reply sent or not

 ----*/
void
cmd_reply(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(any_messages(msgmap, NULL, "to Reply to")){
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	reply(state);

	if(agg)
	  restore_selected(msgmap);

	state->mangled_screen = 1;
    }
}



/*----------------------------------------------------------------------
   Execute FORWARD message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: selected message[s] forwarded or not

 ----*/
void
cmd_forward(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(any_messages(msgmap, NULL, "to Forward")){
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();		/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	forward(state);

	if(agg)
	  restore_selected(msgmap);

	if(state->anonymous)
	  state->mangled_footer = 1;
	else
	  state->mangled_screen = 1;
    }
}



/*----------------------------------------------------------------------
   Execute BOUNCE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: selected message[s] bounced or not

 ----*/
void
cmd_bounce(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(any_messages(msgmap, NULL, "to Bounce")){
#if	defined(DOS) && !defined(WIN32)
	flush_index_cache();			/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	bounce(state);
	if(agg)
	  restore_selected(msgmap);

	state->mangled_footer = 1;
    }
}



/*----------------------------------------------------------------------
   Execute save message command: prompt for folder and call function to save

  Args: screen_line    --  Line on the screen to prompt on
        message        --  The MESSAGECACHE entry of message to save

 Result: The folder lister can be called to make selection; mangled screen set

   This does the prompting for the folder name to save to, possibly calling 
 up the folder display for selection of folder by user.                 
 ----*/
void
cmd_save(state, stream, msgmap, agg)
    struct pine *state;
    MAILSTREAM  *stream;
    MSGNO_S	*msgmap;
    int		 agg;
{
    char	      newfolder[MAILTMPLEN+1], nmsgs[32];
    int		      del = 0, we_cancel = 0;
    long	      i, raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
    CONTEXT_S	     *cntxt = NULL;
    ENVELOPE	     *e = NULL;

    dprint(4, (debugfile, "\n - saving message -\n"));

    if(msgno_any_deletedparts(stream, msgmap)
       && want_to("Saved copy will NOT include entire message!  Continue",
		  'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
	cmd_cancelled("Save message");
	return;
    }

    if(agg && !pseudo_selected(msgmap))
      return;

    if(mn_total_cur(msgmap) <= 1L){
	sprintf(nmsgs, "Msg #%ld ", mn_get_cur(msgmap));
	e = mail_fetchstructure(stream, raw, NULL);
	if(!e) {
	    q_status_message(SM_ORDER | SM_DING, 3, 4,
			     "Can't save message.  Error accessing folder");
	    restore_selected(msgmap);
	    return;
	}
    }
    else
      sprintf(nmsgs, "%s msgs ", comatose(mn_total_cur(msgmap)));

    if(save_prompt(state, &cntxt, newfolder, nmsgs, e, raw, NULL)){
	del = !READONLY_FOLDER && F_OFF(F_SAVE_WONT_DELETE, ps_global);
	we_cancel = busy_alarm(1, NULL, NULL, 0);
	i = save(state, stream, cntxt, newfolder, msgmap, del);
	if(we_cancel)
	  cancel_busy_alarm(0);

	if(i == mn_total_cur(msgmap)){
	    if(mn_total_cur(msgmap) <= 1L){
		if(cntxt
		   && ps_global->context_list->next
		   && context_isambig(newfolder))
		  sprintf(tmp_20k_buf, 
			  "Message %s copied to \"%.15s%s\" in <%.15s%s>",
			  long2string(mn_get_cur(msgmap)), newfolder,
			  (strlen(newfolder) > 15) ? "..." : "",
			  cntxt->nickname,
			  (strlen(cntxt->nickname) > 15) ? "..." : "");
		else
		  sprintf(tmp_20k_buf,
			  "Message %s copied to folder \"%.27s%s\"",
			  long2string(mn_get_cur(msgmap)), newfolder,
			  (strlen(newfolder) > 27) ? "..." : "");
	    }
	    else
	      sprintf(tmp_20k_buf, "%s messages saved",
		      comatose(mn_total_cur(msgmap)));

	    if(del)
	      strcat(tmp_20k_buf, " and deleted");

	    q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);

	    if(!agg && F_ON(F_SAVE_ADVANCES, state)){
		mn_inc_cur(stream, msgmap);
	    }
	}
    }

    if(agg)					/* straighten out fakes */
      restore_selected(msgmap);

    if(del)
      update_titlebar_status();			/* make sure they see change */
}


/*----------------------------------------------------------------------
   Do the dirty work of prompting the user for a folder name

  Args: 
        

 Result: 

 ----*/
int
save_prompt(state, cntxt, nfldr, nmsgs, env, rawmsgno, section)
    struct pine  *state;
    CONTEXT_S   **cntxt;
    char	 *nfldr;
    char	 *nmsgs;
    ENVELOPE	 *env;
    long	  rawmsgno;
    char	 *section;
{
    static char	      folder[MAILTMPLEN+1] = {'\0'};
    static CONTEXT_S *last_context = NULL;
    int		      rc, n, flags, last_rc = 0, saveable_count = 0, done = 0;
    char	      prompt[MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
    HelpType	      help;
    CONTEXT_S	     *tc;
    ESCKEY_S	      ekey[8];

    /* start with the default save context */
    if(((*cntxt) = default_save_context(state->context_list)) == NULL)
       (*cntxt) = state->context_list;

    if(!env || ps_global->save_msg_rule == MSG_RULE_LAST
	  || ps_global->save_msg_rule == MSG_RULE_DEFLT){
	if(ps_global->save_msg_rule == MSG_RULE_LAST && last_context)
	  (*cntxt) = last_context;
	else
	  strcpy(folder, ps_global->VAR_DEFAULT_SAVE_FOLDER);
    }
    else{
	get_save_fldr_from_env(folder, sizeof(folder), env, state,
			       rawmsgno, section);
	if(ps_global->expunge_count)	/* somebody expunged current message */
	  return(0);
    }


    /* how many context's can be saved to... */
    for(tc = state->context_list; tc; tc = tc->next)
      if(!NEWS_TEST(tc))
        saveable_count++;

    /* set up extra command option keys */
    rc = 0;
    ekey[rc].ch      = ctrl('T');
    ekey[rc].rval    = 2;
    ekey[rc].name    = "^T";
    ekey[rc++].label = "To Fldrs";

    if(saveable_count > 1){
	ekey[rc].ch      = ctrl('P');
	ekey[rc].rval    = 10;
	ekey[rc].name    = "^P";
	ekey[rc++].label = "Prev Collection";

	ekey[rc].ch      = ctrl('N');
	ekey[rc].rval    = 11;
	ekey[rc].name    = "^N";
	ekey[rc++].label = "Next Collection";
    }

    if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
	ekey[rc].ch      = TAB;
	ekey[rc].rval    = 12;
	ekey[rc].name    = "TAB";
	ekey[rc++].label = "Complete";
    }

    if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
	ekey[rc].ch      = ctrl('X');
	ekey[rc].rval    = 14;
	ekey[rc].name    = "^X";
	ekey[rc++].label = "ListMatches";
    }

    if(saveable_count > 1){
	ekey[rc].ch      = KEY_UP;
	ekey[rc].rval    = 10;
	ekey[rc].name    = "";
	ekey[rc++].label = "";

	ekey[rc].ch      = KEY_DOWN;
	ekey[rc].rval    = 11;
	ekey[rc].name    = "";
	ekey[rc++].label = "";
    }

    ekey[rc].ch = -1;

    *nfldr = '\0';
    help = NO_HELP;
    while(!done){
	/* only show collection number if more than one available */
	if(ps_global->context_list->next)
	  sprintf(prompt, "SAVE %sto folder in <%.25s> [%s] : ",
		  nmsgs, (*cntxt)->nickname, folder);
	else
	  sprintf(prompt, "SAVE %sto folder [%s] : ", nmsgs, folder);

	/*
	 * If the prompt won't fit, remove the extra info contained
	 * in nmsgs.
	 */
	if(state->ttyo->screen_cols < strlen(prompt) + 6 && *nmsgs){
	    if(ps_global->context_list->next)
	      sprintf(prompt, "SAVE to folder in <%.25s> [%s] : ",
		      (*cntxt)->nickname, folder);
	    else
	      sprintf(prompt, "SAVE to folder [%s] : ", folder);
	}
	
	flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
	rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, MAILTMPLEN,
			      prompt, ekey, help, &flags);

	switch(rc){
	  case -1 :
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "Error reading folder name");
	    done--;
	    break;

	  case 0 :
	    removing_trailing_white_space(nfldr);
	    removing_leading_white_space(nfldr);

	    if(*nfldr){
		char *p, *name, *fullname = NULL;
		int   exists, breakout = FALSE;

		if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt))))
		  name = nfldr;

		if(update_folder_spec(expanded, name))
		  strcpy(name = nfldr, expanded);

		exists = folder_name_exists(*cntxt, name, &fullname);

		if(exists == FEX_ERROR){
		    q_status_message1(SM_ORDER, 0, 3,
				      "Problem accessing folder \"%s\"",
				      nfldr);
		    done--;
		}
		else{
		    if(fullname){
			strcpy(name = nfldr, fullname);
			fs_give((void **) &fullname);
			breakout = TRUE;
		    }

		    if(exists & FEX_ISFILE){
			done++;
		    }
		    else if((exists & FEX_ISDIR)){
			tc = *cntxt;
			if(breakout){
			    CONTEXT_S *fake_context;
			    char	   tmp[MAILTMPLEN];
			    size_t	   l;

			    strcpy(tmp, name);
			    if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
				tmp[l] = tc->dir->delim;
				strcpy(&tmp[l+1], "[]");
			    }
			    else
			      strcat(tmp, "[]");

			    fake_context = new_context(tmp, 0);
			    nfldr[0] = '\0';
			    done = display_folder_list(&fake_context, nfldr,
						       1, folders_for_save);
			    free_context(&fake_context);
			}
			else if(tc->dir->delim
				&& (p = strrindex(name, tc->dir->delim))
				&& *(p+1) == '\0')
			  done = display_folder_list(cntxt, nfldr,
						     1, folders_for_save);
		    }
		    else{			/* Doesn't exist, create! */
			if(fullname = folder_as_breakout(*cntxt, name)){
			    strcpy(name = nfldr, fullname);
			    fs_give((void **) &fullname);
			    *cntxt = NULL;
			}

			switch(create_for_save(NULL, *cntxt, name)){
			  case 1 :		/* success */
			    done++;
			    break;
			  case 0 :		/* error */
			  case -1 :		/* declined */
			    done--;
			    break;
			}
		    }
		}

		break;
	    }
	    else if(*folder){
		done++;
		break;
	    }
	    /* else fall thru like they cancelled */

	  case 1 :
	    cmd_cancelled("Save message");
	    done--;
	    break;

	  case 2 :
	    if(display_folder_list(cntxt, nfldr, 0, folders_for_save))
	      done++;

	    break;

	  case 3 :
            help = (help == NO_HELP) ? h_oe_save : NO_HELP;
	    break;

	  case 4 :				/* redraw */
	    break;

	  case 10 :				/* previous collection */
	    for(tc = (*cntxt)->prev; tc; tc = tc->prev)
	      if(!NEWS_TEST(tc))
		break;

	    if(!tc){
		CONTEXT_S *tc2;

		for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
		  if(!NEWS_TEST(tc2))
		    tc = tc2;
	    }

	    *cntxt = tc;
	    break;

	  case 11 :				/* next collection */
	    tc = (*cntxt);

	    do
	      if(((*cntxt) = (*cntxt)->next) == NULL)
		(*cntxt) = ps_global->context_list;
	    while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
	    break;

	  case 12 :				/* file name completion */
	    if(!folder_complete(*cntxt, nfldr, &n)){
		if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
		    if(display_folder_list(cntxt, nfldr, 1, folders_for_save))
		      done++;			/* bingo! */
		    else
		      rc = 0;			/* burn last_rc */
		}
		else
		  Writechar(BELL, 0);
	    }

	    break;

	  case 14 :				/* file name completion */
	    if(display_folder_list(cntxt, nfldr, 2, folders_for_save))
	      done++;			/* bingo! */
	    else
	      rc = 0;			/* burn last_rc */

	    break;

	  default :
	    panic("Unhandled case");
	    break;
	}

	last_rc = rc;
    }

    ps_global->mangled_footer = 1;

    if(done < 0)
      return(0);

    if(*cntxt)
      last_context = *cntxt;		/* remember for next time */

    if(*nfldr)
      strcpy(folder, nfldr);
    else
      strcpy(nfldr, folder);

    /* nickname?  Copy real name to nfldr */
    if(*cntxt
       && context_isambig(nfldr)
       && (p = folder_is_nick(nfldr, FOLDERS(*cntxt))))
      strcpy(nfldr, p);

    return(1);
}


/*----------------------------------------------------------------------
   Grope through envelope to find default folder name to save to

  Args: fbuf     --  Buffer to return result in
        nfbuf    --  Size of fbuf
        e        --  The envelope to look in
        state    --  Usual pine state
        rawmsgno --  Raw c-client sequence number of message
	section  --  Mime section of header data (for message/rfc822)

 Result: The appropriate default folder name is copied into fbuf.
 ----*/
void
get_save_fldr_from_env(fbuf, nfbuf, e, state, rawmsgno, section)
    char         fbuf[];
    int          nfbuf;
    ENVELOPE    *e;
    struct pine *state;
    long	 rawmsgno;
    char	*section;
{
    char     fakedomain[2];
    ADDRESS *tmp_adr = NULL;
    char     buf[max(MAXFOLDER,MAX_NICKNAME) + 1];
    char    *bufp;
    char    *folder_name;
    static char botch[] = "programmer botch, unknown message save rule";
    unsigned save_msg_rule;

    if(!e)
      return;

    /* copy this because we might change it below */
    save_msg_rule = state->save_msg_rule;

    /* first get the relevant address to base the folder name on */
    switch(save_msg_rule){
      case MSG_RULE_FROM:
      case MSG_RULE_NICK_FROM:
      case MSG_RULE_FCC_FROM:
      case MSG_RULE_FCC_FROM_DEF:
      case MSG_RULE_NICK_FROM_DEF:
        tmp_adr = e->from ? copyaddr(e->from)
			  : e->sender ? copyaddr(e->sender) : NULL;
	break;

      case MSG_RULE_SENDER:
      case MSG_RULE_NICK_SENDER:
      case MSG_RULE_FCC_SENDER:
      case MSG_RULE_NICK_SENDER_DEF:
      case MSG_RULE_FCC_SENDER_DEF:
        tmp_adr = e->sender ? copyaddr(e->sender)
			    : e->from ? copyaddr(e->from) : NULL;
	break;

      case MSG_RULE_RECIP:
      case MSG_RULE_NICK_RECIP:
      case MSG_RULE_FCC_RECIP:
      case MSG_RULE_NICK_RECIP_DEF:
      case MSG_RULE_FCC_RECIP_DEF:
	/* news */
	if(state->mail_stream && IS_NEWS(state->mail_stream)){
	    char *tmp_a_string, *ng_name;

	    fakedomain[0] = '@';
	    fakedomain[1] = '\0';

	    /* find the news group name */
	    if(ng_name = strstr(state->mail_stream->mailbox,"#news"))
	      ng_name += 6;
	    else
	      ng_name = state->mail_stream->mailbox; /* shouldn't happen */

	    /* copy this string so rfc822_parse_adrlist can't blast it */
	    tmp_a_string = cpystr(ng_name);
	    /* make an adr */
	    rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
	    fs_give((void **)&tmp_a_string);
	    if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
	      tmp_adr->host[0] = '\0';
	}
	/* not news */
	else{
	    static char *fields[] = {"Resent-To", NULL};
	    char *extras, *values[sizeof(fields)/sizeof(fields[0])];

	    extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
					    section, fields);
	    if(extras){
		long i;

		memset(values, 0, sizeof(fields));
		simple_header_parse(extras, fields, values);
		fs_give((void **)&extras);

		for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
		  if(values[i]){
		      if(tmp_adr)		/* take last matching value */
			mail_free_address(&tmp_adr);

		      /* build a temporary address list */
		      fakedomain[0] = '@';
		      fakedomain[1] = '\0';
		      rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
		      fs_give((void **)&values[i]);
		  }
	    }

	    if(!tmp_adr)
	      tmp_adr = e->to ? copyaddr(e->to) : NULL;
	}

	break;
      
      default:
	panic(botch);
	break;
    }

    /* For that address, lookup the fcc or nickname from address book */
    switch(save_msg_rule){
      case MSG_RULE_NICK_FROM:
      case MSG_RULE_NICK_SENDER:
      case MSG_RULE_NICK_RECIP:
      case MSG_RULE_FCC_FROM:
      case MSG_RULE_FCC_SENDER:
      case MSG_RULE_FCC_RECIP:
      case MSG_RULE_NICK_FROM_DEF:
      case MSG_RULE_NICK_SENDER_DEF:
      case MSG_RULE_NICK_RECIP_DEF:
      case MSG_RULE_FCC_FROM_DEF:
      case MSG_RULE_FCC_SENDER_DEF:
      case MSG_RULE_FCC_RECIP_DEF:
	switch(save_msg_rule){
	  case MSG_RULE_NICK_FROM:
	  case MSG_RULE_NICK_SENDER:
	  case MSG_RULE_NICK_RECIP:
	  case MSG_RULE_NICK_FROM_DEF:
	  case MSG_RULE_NICK_SENDER_DEF:
	  case MSG_RULE_NICK_RECIP_DEF:
	    bufp = get_nickname_from_addr(tmp_adr, buf);
	    break;

	  case MSG_RULE_FCC_FROM:
	  case MSG_RULE_FCC_SENDER:
	  case MSG_RULE_FCC_RECIP:
	  case MSG_RULE_FCC_FROM_DEF:
	  case MSG_RULE_FCC_SENDER_DEF:
	  case MSG_RULE_FCC_RECIP_DEF:
	    bufp = get_fcc_from_addr(tmp_adr, buf);
	    break;
	}

	if(bufp && *bufp){
	    fbuf[nfbuf - 1] = '\0';
	    strncpy(fbuf, bufp, nfbuf - 1);
	}
	else
	  /* fall back to non-nick/non-fcc version of rule */
	  switch(save_msg_rule){
	    case MSG_RULE_NICK_FROM:
	    case MSG_RULE_FCC_FROM:
	      save_msg_rule = MSG_RULE_FROM;
	      break;

	    case MSG_RULE_NICK_SENDER:
	    case MSG_RULE_FCC_SENDER:
	      save_msg_rule = MSG_RULE_SENDER;
	      break;

	    case MSG_RULE_NICK_RECIP:
	    case MSG_RULE_FCC_RECIP:
	      save_msg_rule = MSG_RULE_RECIP;
	      break;
	    
	    default:
	      strcpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER);
	      break;
	  }

	break;
    }

    /* get the username out of the mailbox for this address */
    switch(save_msg_rule){
      case MSG_RULE_FROM:
      case MSG_RULE_SENDER:
      case MSG_RULE_RECIP:
	/*
	 * Fish out the user's name from the mailbox portion of
	 * the address and put it in folder.
	 */
	folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
		      ? tmp_adr->mailbox : NULL;
	if(!get_uname(folder_name, fbuf, nfbuf))
	  strcpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER);

	break;
    }

    if(tmp_adr)
      mail_free_address(&tmp_adr);
}



/*----------------------------------------------------------------------
        Do the work of actually saving messages to a folder

    Args: state -- pine state struct (for stream pointers)
	  context -- context to interpret name in if not fully qualified
	  folder  -- The folder to save the message in
          msgmap -- message map of currently selected messages
	  delete -- whether or not to delete messages saved

  Result: Returns number of messages saved

  Note: There's a bit going on here; temporary clearing of deleted flags
	since they are *not* preserved, picking or creating the stream for
	copy or append, and dealing with errors...
 ----*/
long
save(state, stream, context, folder, msgmap, delete)
    struct pine  *state;
    MAILSTREAM	 *stream;
    CONTEXT_S    *context;
    char         *folder;
    MSGNO_S	 *msgmap;
    int		  delete;
{
    int		  rv, rc, j, our_stream = 0, cancelled = 0;
    char	 *save_folder, *seq, flags[64], date[64];
    long	  i, nmsgs;
    STORE_S	 *so = NULL;
    MAILSTREAM	 *save_stream = NULL;
    MESSAGECACHE *mc;
#if	defined(DOS) && !defined(WIN32)
#define	SAVE_TMP_TYPE		TmpFileStar
#else
#define	SAVE_TMP_TYPE		CharStar
#endif

    if(strucmp(folder, state->inbox_name) == 0){
	save_folder = state->VAR_INBOX_PATH;
	context = NULL;
    }
    else
      save_folder = folder;

    /*
     * If any of the messages have exceptional attachment handling
     * we have to fall thru below to do the APPEND by hand...
     */
    if(!msgno_any_deletedparts(stream, msgmap))
      /*
       * Compare the current stream (the save's source) and the stream
       * the destination folder will need...
       */
      save_stream = context_same_stream(context, save_folder, stream);

    /* if needed, this'll get set in mm_notify */
    ps_global->try_to_create = 0;
    rv = rc = 0;
    nmsgs = 0L;

    /*
     * At this point, if we have a save_stream, then none of the messages
     * being saved involve special handling that would require our use
     * of mail_append, so go with mail_copy since in the IMAP case it
     * means no data on the wire...
     */
    if(save_stream){
	char *dseq, *oseq;

	if(dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,0))
	  mail_flag(stream, dseq, "\\DELETED", 0L);

	seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0);
	if(F_ON(F_AGG_SEQ_COPY, ps_global)
	   || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap))){
	    /*
	     * currentf_sequence() above lit all the elt "sequence"
	     * bits of the interesting messages.  Now, build a sequence
	     * that preserves sort order...
	     */
	    oseq = build_sequence(stream, msgmap, &nmsgs);
	}
	else{
	    oseq  = NULL;			/* no single sequence! */
	    nmsgs = 0L;
	    i = mn_first_cur(msgmap);		/* set first to copy */
	}

	do{
	    while(!(rv = (int) context_copy(context, save_stream,
				oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
				save_folder))){
		if(rc++ || !ps_global->try_to_create)   /* abysmal failure! */
		  break;			/* c-client returned error? */

		if((context->use & CNTXT_INCMNG)
		   && context_isambig(save_folder)){
		    q_status_message(SM_ORDER, 3, 5,
		   "Can only save to existing folders in Incoming Collection");
		    break;
		}

		ps_global->try_to_create = 0;	/* reset for next time */
		if((j = create_for_save(save_stream,context,save_folder)) < 1){
		    if(j < 0)
		      cancelled = 1;		/* user cancels */

		    break;
		}
	    }

	    if(rv){				/* failure or finished? */
		if(oseq)			/* all done? */
		  break;
		else
		  nmsgs++;
	    }
	    else{				/* failure! */
		if(oseq)
		  nmsgs = 0L;			/* nothing copy'd */

		break;
	    }
	}
	while((i = mn_next_cur(msgmap)) > 0L);

	if(rv && delete)			/* delete those saved */
	  mail_flag(stream, seq, "\\DELETED", ST_SET);
	else if(dseq)				/* or restore previous state */
	  mail_flag(stream, dseq, "\\DELETED", ST_SET);

	if(dseq)				/* clean up */
	  fs_give((void **)&dseq);

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

	fs_give((void **)&seq);
    }
    else{
	/*
	 * Special handling requires mail_append.  See if we can
	 * reuse stream source messages are on...
	 */
	save_stream = context_same_stream(context, save_folder, stream);

	/*
	 * IF the destination's REMOTE, open a stream here so c-client
	 * doesn't have to open it for each aggregate save...
	 */
	if(!save_stream){
	    char tmp[MAILTMPLEN];		/* must be within context */

	    if(context_apply(tmp, context, save_folder)[0] == '{'
	       && (save_stream = context_open(context, NULL,
					      save_folder, OP_HALFOPEN)))
	      our_stream = 1;
	}

	/*
	 * Allocate a storage object to temporarily store the message
	 * object in.  Below it'll get mapped into a c-client STRING struct
	 * in preparation for handing off to context_append...
	 */
	if(!(so = so_get(SAVE_TMP_TYPE, NULL, WRITE_ACCESS))){
	    dprint(1, (debugfile, "Can't allocate store for save: %s\n",
		       error_description(errno)));
	    q_status_message(SM_ORDER | SM_DING, 3, 4,
			     "Problem creating space for message text.");
	}

	/*
	 * get a sequence of invalid elt's so we can get thier flags...
	 */
	if(seq = invalid_elt_sequence(stream, msgmap)){
	    mail_fetchflags(stream, seq);
	    fs_give((void **) &seq);
	}

	/*
	 * If we're supposed set the deleted flag, clear the elt bit
	 * we'll use to build the sequence later...
	 */
	if(delete)
	  for(i = 1L; i <= stream->nmsgs; i++)
	    mail_elt(stream, i)->sequence = 0;

	nmsgs = 0L;
	for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
	    so_truncate(so, 0L);

	    /* store flags before the fetch so UNSEEN bit isn't flipped */
	    mc = mail_elt(stream, mn_m2raw(msgmap, i));
	    flag_string(mc, F_ANS|F_FLAG|F_SEEN, flags);
	    if(mc->day)
	      mail_date(date, mc);
	    else
	      *date = '\0';

	    rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
				   NULL, save_stream, save_folder, context,
				   mc->rfc822_size, flags, date, so);

	    if(ps_global->expunge_count)
	      rv = -1;			/* All bets are off! */

	    if(rv == 1){
		/*
		 * Success!  Count it, and if it's not already deleted and 
		 * it's supposed to be, mark it to get deleted later...
		 */
		nmsgs++;
		if(delete){
		    mc = mail_elt(stream, mn_m2raw(msgmap, i));
		    if(!mc->deleted){
			mc->sequence = 1;	/* mark for later deletion */
			clear_index_cache_ent(i);
			check_point_change();
		    }
		}
	    }
	    else{
		if(rv == -1)
		  cancelled = 1;		/* else horrendous failure */

		break;
	    }
	}

	if(our_stream)
	  mail_close(save_stream);

	if(so)
	  so_give(&so);

	if(delete && (seq = build_sequence(stream, NULL, NULL))){
	    mail_flag(stream, seq, "\\DELETED", ST_SET);
	    fs_give((void **)&seq);
	}
    }

    ps_global->try_to_create = 0;		/* reset for next time */
    if(!cancelled && nmsgs < mn_total_cur(msgmap)){
	dprint(1, (debugfile, "FAILED save of msg %s (c-client sequence #)\n",
		   long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
	if(mn_total_cur(msgmap) > 1L){
	    sprintf(tmp_20k_buf,
		    "%ld of %ld messages saved before error occurred",
		    nmsgs, mn_total_cur(msgmap));
	    dprint(1, (debugfile, "\t%s\n", tmp_20k_buf));
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
    }

    return(nmsgs);
}


/*----------------------------------------------------------------------
   FETCH an rfc822 message header and body and APPEND to destination folder

  Args: 
        

 Result: 

 ----*/
int
save_fetch_append(stream, raw, sect, save_stream, save_folder,
		  context, size, flags, date, so)
    MAILSTREAM	  *stream;
    long	   raw;
    char	  *sect;
    MAILSTREAM	  *save_stream;
    char	  *save_folder;
    CONTEXT_S	  *context;
    unsigned long  size;
    char	  *flags, *date;
    STORE_S	  *so;
{
    int		   rc, rv, old_imap_server = 0;
    long	   j;
    char	  *fetch;
    unsigned long  hlen, tlen, mlen;
    STRING	   msg;
#if	defined(DOS) && !defined(WIN32)
    struct {					/* hack! stolen from dawz.c */
	int fd;
	unsigned long pos;
    } d;
    extern STRINGDRIVER dawz_string;
#endif

    if(fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)){
	/*
	 * If there's no date string, then caller found the
	 * MESSAGECACHE for this message element didn't already have it.
	 * So, parse the "internal date" by hand since fetchstructure
	 * hasn't been called yet for this particular message, and
	 * we don't want to call it now just to get the date since
	 * the full header has what we want.  Likewise, don't even
	 * think about calling mail_fetchfast either since it also
	 * wants to load mc->rfc822_size (which we could actually
	 * use but...), which under some drivers is *very*
	 * expensive to acquire (can you say NNTP?)...
	 */
	if(!*date)
	  saved_date(date, fetch);
    }
    else
      return(0);			/* fetchtext writes error */

    rc = MSG_EX_DELETE;			/* "rc" overloaded */
    if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
	char	 section[64];
	int	 failure = 0;
	BODY	*body;
	gf_io_t  pc;

	size = 0;			/* all bets off, abort sanity test  */
	gf_set_so_writec(&pc, so);

	if(sect && *sect){
	    sprintf(section, "%s.1", sect);
	    if(!(body = mail_body(stream, raw, section)))
	      return(0);
	}
	else{
	    section[0] = '\0';
	    if(!mail_fetch_structure(stream, raw, &body, 0))
	      return(0);
	}

	/*
	 * Walk the MIME structure looking for exceptional segments,
	 * writing them in the requested fashion.
	 *
	 * First, though, check for the easy case...
	 */
	if(msgno_part_deleted(stream, raw, sect ? sect : "")){
	    tlen = 0;
	    failure = !save_ex_replace_body(fetch, &hlen, body, pc);
	}
	else{
	    /*
	     * Otherwise, roll up your sleeves and get to work...
	     * start by writing msg header and then the processed body
	     */
	    failure = !(so_nputs(so, fetch, (long) hlen)
			&& save_ex_output_body(stream, raw, section,
					       body, &tlen, pc));
	}

	gf_clear_so_writec(so);

	if(failure)
	  return(0);

	q_status_message(SM_ORDER, 3, 3,
		    "NOTE: Deleted message parts NOT included in saved copy!");

    }
    else{
	/* First, write the header we just fetched... */
	if(!so_nputs(so, fetch, (long) hlen))
	  return(0);

#if	defined(DOS) && !defined(WIN32)
	/*
	 * Set append file and install dos_gets so message text
	 * is fetched directly to disk.
	 */
	mail_parameters(stream, SET_GETS, (void *)dos_gets);
	append_file = (FILE *) so_text(so);
	mail_gc(stream, GC_TEXTS);
#endif

	old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);

	/* Second, go fetch the corresponding text... */
	fetch = mail_fetch_text(stream, raw, sect, &tlen,
				!old_imap_server ? FT_PEEK : 0);

	/*
	 * Special handling in case we're fetching a Message/rfc822
	 * segment and we're talking to an old server...
	 */
	if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
	    so_seek(so, 0L, 0);
	    fetch = mail_fetch_body(stream, raw, sect, &tlen, 0L);
	}

	/*
	 * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
	 * so if the message we are saving from was originally unseen,
	 * we have to change it back to unseen. Flags contains the
	 * string "SEEN" if the original message was seen.
	 */
	if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
	    char seq[10];

	    strcpy(seq, long2string(raw));
	    mail_flag(stream, seq, "\\SEEN", 0);
	}


#if	!(defined(DOS) && !defined(WIN32))
	/* If fetch succeeded, write the result */
	if(!(fetch && so_nputs(so, fetch, tlen)))
	   return(0);
#else
	/*
	 * Clean up after our DOS hacks...
	 */
	append_file = NULL;
	mail_parameters(stream, SET_GETS, (void *)NULL);
	mail_gc(stream, GC_TEXTS);

	if(!fetch)
	  return(0);
#endif
    }

    so_seek(so, 0L, 0);			/* rewind just in case */

    /*
     * Set up a c-client string driver so we can hand the
     * collected text down to mail_append.
     *
     * NOTE: We only test the size if and only if we already
     *	     have it.  See, in some drivers, especially under
     *	     dos, its too expensive to get the size (full
     *	     header and body text fetch plus MIME parse), so
     *	     we only verify the size if we already know it.
     */
#if	defined(DOS) && !defined(WIN32)
    d.fd  = fileno((FILE *)so_text(so));
    d.pos = 0L;
    mlen = filelength(d.fd);
#else
    mlen = hlen + tlen;
#endif

    if(size && mlen < size){
	char buf[128];

	sprintf(buf, "Message to save shrank!  (#%ld: %ld --> %ld)",
		raw, size, mlen);
	q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
	dprint(1, (debugfile, "BOTCH: %s\n", buf));
	return(0);
    }

#if	defined(DOS) && !defined(WIN32)
    INIT(&msg, dawz_string, (void *)&d, mlen);
#else
    INIT(&msg, mail_string, (void *)so_text(so), mlen);
#endif

    rc = 0;
    while(!(rv = (int) context_append_full(context, save_stream,
					   save_folder, flags,
					   *date ? date : NULL,
					   &msg))){
	if(rc++ || !ps_global->try_to_create)	/* abysmal failure! */
	  break;				/* c-client returned error? */

	if(context && (context->use & CNTXT_INCMNG)
	   && context_isambig(save_folder)){
	    q_status_message(SM_ORDER, 3, 5,
	       "Can only save to existing folders in Incoming Collection");
	    break;
	}

	ps_global->try_to_create = 0;		/* reset for next time */
	if((j = create_for_save(save_stream,context,save_folder)) < 1){
	    if(j < 0)
	      rv = -1;			/* user cancelled */

	    break;
	}

	SETPOS((&msg), 0L);			/* reset string driver */
    }

    return(rv);
}


/*
 * save_ex_replace_body -
 *
 * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
 *	  *BUT* which is to contain the count of written bytes on exit
 */
int
save_ex_replace_body(hdr, hlen, body, pc)
    char	   *hdr;
    unsigned long  *hlen;
    BODY	   *body;
    gf_io_t	    pc;
{
    unsigned long len;

    /*
     * "X-" out the given MIME headers unless they're
     * the same as we're going to substitute...
     */
    if(body->type == TYPETEXT
       && (!body->subtype || !strucmp(body->subtype, "plain"))
       && body->encoding == ENC7BIT){
	if(!gf_nputs(hdr, *hlen, pc))	/* write out header */
	  return(0);
    }
    else{
	int bol = 1;

	/*
	 * write header, "X-"ing out transport headers bothersome to
	 * software but potentially useful to the human recipient...
	 */
	for(len = *hlen; len; len--){
	    if(bol){
		unsigned long n;

		bol = 0;
		if(save_ex_mask_types(hdr, &n, pc))
		  *hlen += n;		/* add what we inserted */
		else
		  break;
	    }

	    if(*hdr == '\015' && *(hdr+1) == '\012'){
		bol++;			/* remember beginning of line */
		len--;			/* account for LF */
		if(gf_nputs(hdr, 2, pc))
		  hdr += 2;
		else
		  break;
	    }
	    else if(!(*pc)(*hdr++))
	      break;
	}

	if(len)				/* bytes remain! */
	  return(0);
    }

    /* Now, blat out explanatory text as the body... */
    if(save_ex_explain_body(body, &len, pc)){
	*hlen += len;
	return(1);
    }
    else
      return(0);
}



int
save_ex_output_body(stream, raw, section, body, len, pc)
    MAILSTREAM	  *stream;
    long	   raw;
    char	  *section;
    BODY	  *body;
    unsigned long *len;
    gf_io_t	   pc;
{
    char	  *txtp, newsect[128];
    unsigned long  ilen;

    txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);

    if(msgno_part_deleted(stream, raw, section))
      return(save_ex_replace_body(txtp, len, body, pc));

    if(body->type == TYPEMULTIPART){
	char	  *subsect, boundary[128];
	int	   n, blen;
	PART	  *part = body->nested.part;
	PARAMETER *param;

	/* Locate supplied supplied multipart boundary */
	for (param = body->parameter; param; param = param->next)
	  if (!strucmp(param->attribute, "boundary")){
	      sprintf(boundary, "--%s\015\012", param->value);
	      blen = strlen(boundary);
	      break;
	  }

	if(!param){
	    q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
	    return(0);
	}

	/*
	 * BUG: if multi/digest and a message deleted (which we'll
	 * change to text/plain), we need to coerce this composite
	 * type to multi/mixed !!
	 */
	if(!gf_nputs(txtp, *len, pc))		/* write MIME header */
	  return(0);

	/* Prepare to specify sub-sections */
	strcpy(newsect, section);
	subsect = &newsect[n = strlen(newsect)];
	if(n > 2 && !strcmp(&newsect[n-2], ".0"))
	  subsect--;
	else if(n)
	  *subsect++ = '.';

	n = 1;
	do {				/* for each part */
	    strcpy(subsect, int2string(n++));
	    if(gf_puts(boundary, pc)
		 && save_ex_output_body(stream, raw, newsect,
					&part->body, &ilen, pc))
	      *len += blen + ilen;
	    else
	      return(0);
	}
	while (part = part->next);	/* until done */

	sprintf(boundary, "--%s--\015\012", param->value);
	*len += blen + 2;
	return(gf_puts(boundary, pc));
    }

    /* Start by writing the part's MIME header */
    if(!gf_nputs(txtp, *len, pc))
      return(0);
    
    if(body->type == TYPEMESSAGE
       && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
	/* write RFC 822 message's header */
	if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
	     && gf_nputs(txtp, ilen, pc))
	  *len += ilen;
	else
	  return(0);

	/* then go deal with its body parts */
	sprintf(newsect, "%s%s%s", section, section ? "." : "",
		(body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
	if(save_ex_output_body(stream, raw, newsect,
			       body->nested.msg->body, &ilen, pc)){
	    *len += ilen;
	    return(1);
	}

	return(0);
    }

    /* Write corresponding body part */
    if((txtp = mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
       && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
	*len += ilen + 2;
	return(1);
    }

    return(0);
}



/*----------------------------------------------------------------------
    Mask off any header entries we don't want to show up in exceptional saves

Args:  hdr -- pointer to start of a header line
       pc -- function to write the prefix

  ----*/
int
save_ex_mask_types(hdr, len, pc)
    char	  *hdr;
    unsigned long *len;
    gf_io_t	   pc;
{
    char *s = NULL;

    if(!struncmp(hdr, "content-type:", 13))
      s = "Content-Type: Text/Plain; charset=US-ASCII\015\012X-";
    else if(!struncmp(hdr, "content-description:", 20))
      s = "Content-Description: Deleted Attachment\015\012X-";
    else if(!struncmp(hdr, "content-transfer-encoding:", 26)
	    || !struncmp(hdr, "content-disposition:", 20))
      s = "X-";

    return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
}


int
save_ex_explain_body(body, len, pc)
    BODY	  *body;
    unsigned long *len;
    gf_io_t	   pc;
{
    unsigned long   ilen;
    char	  **blurbp;
    static char    *blurb[] = {
	"The following attachments were DELETED when this message was saved:",
	NULL
    };

    *len = 0;
    for(blurbp = blurb; *blurbp; blurbp++)
      if(save_ex_output_line(*blurbp, &ilen, pc))
	*len += ilen;
      else
	return(0);

    if(!save_ex_explain_parts(body, 0, &ilen, pc))
      return(0);

    *len += ilen;
    return(1);
}


int
save_ex_explain_parts(body, depth, len, pc)
    BODY	  *body;
    int		   depth;
    unsigned long *len;
    gf_io_t	   pc;
{
    char	  tmp[MAILTMPLEN];
    unsigned long ilen;

    if(body->type == TYPEMULTIPART) {   /* multipart gets special handling */
	PART *part = body->nested.part;	/* first body part */

	*len = 0;
	if(body->description && *body->description){
	    sprintf(tmp, "%*.*sA %s/%s segment described",
		    depth, depth, " ", body_type_names(body->type),
		    body->subtype ? body->subtype : "Unknown");
	    if(!save_ex_output_line(tmp, len, pc))
	      return(0);

	    sprintf(tmp, "%*.*s  as \"%.50s\" containing:", depth, depth, " ",
		    (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
					    body->description, NULL));
	}
	else
	  sprintf(tmp, "%*.*sA %s/%s segment containing:", depth, depth, " ",
		  body_type_names(body->type),
		  body->subtype ? body->subtype : "Unknown");

	if(save_ex_output_line(tmp, &ilen, pc))
	  *len += ilen;
	else
	  return(0);

	depth++;
	do				/* for each part */
	  if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
	    *len += ilen;
	  else
	    return(0);
	while (part = part->next);	/* until done */
    }
    else{
	sprintf(tmp, "%*.*sA %s/%s segment of about %s bytes%s",
		depth, depth, " ",
		body_type_names(body->type), 
		body->subtype ? body->subtype : "Unknown",
		comatose((body->encoding == ENCBASE64)
			   ? ((body->size.bytes * 3)/4)
			   : body->size.bytes),
		body->description ? "," : ".");
	if(!save_ex_output_line(tmp, len, pc))
	  return(0);

	if(body->description && *body->description){
	    sprintf(tmp, "%*.*s   described as \"%s\"", depth, depth, " ",
		    (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
					    body->description, NULL));
	    if(save_ex_output_line(tmp, &ilen, pc))
	      *len += ilen;
	    else
	      return(0);
	}
    }

    return(1);
}



int
save_ex_output_line(line, len, pc)
    char	  *line;
    unsigned long *len;
    gf_io_t	   pc;
{
    sprintf(tmp_20k_buf, "  [ %-*.*s ]\015\012", 68, 68, line);
    *len = strlen(tmp_20k_buf);
    return(gf_puts(tmp_20k_buf, pc));
}



/*----------------------------------------------------------------------
    Offer to create a non-existant folder to copy message[s] into

   Args: stream -- stream to use for creation
	 context -- context to create folder in
	 name -- name of folder to create

 Result: 0 if create failed (c-client writes error)
	 1 if create successful
	-1 if user declines to create folder
 ----*/
int
create_for_save(stream, context, folder)
    MAILSTREAM *stream;
    CONTEXT_S  *context;
    char       *folder;
{
    if(context && ps_global->context_list->next && context_isambig(folder)){
	if(context->use & CNTXT_INCMNG){
	    sprintf(tmp_20k_buf,
		"\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen",
		folder, (strlen(folder) > 15) ? "..." : "");
	    q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
	    return(0);		/* error */
	}

	sprintf(tmp_20k_buf,
		"Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create",
		folder, (strlen(folder) > 15) ? "..." : "",
		context->nickname,
		(strlen(context->nickname) > 15) ? "..." : "");
    }
    else
      sprintf(tmp_20k_buf,"Folder \"%.40s\" doesn't exist.  Create", folder);

    if(want_to(tmp_20k_buf, 'y', 'n', NO_HELP, WT_SEQ_SENSITIVE) != 'y'){
	cmd_cancelled("Save message");
	return(-1);
    }

    return(context_create(context, NULL, folder));
}



/*----------------------------------------------------------------------
  Build flags string based on requested flags and what's set in messagecache

   Args: mc -- message cache element to dig the flags out of
	 flags -- flags to test
	 flagbuf -- place to write string representation of bits

 Result: flags represented in bits and mask written in flagbuf
 ----*/
void
flag_string(mc, flags, flagbuf)
    MESSAGECACHE *mc;
    long	  flags;
    char	 *flagbuf;
{
    char *p;

    *(p = flagbuf) = '\0';

    if((flags & F_DEL) && mc->deleted)
      sstrcpy(&p, "\\DELETED ");

    if((flags & F_ANS) && mc->answered)
      sstrcpy(&p, "\\ANSWERED ");

    if((flags & F_FLAG) && mc->flagged)
      sstrcpy(&p, "\\FLAGGED ");

    if((flags & F_SEEN) && mc->seen)
      sstrcpy(&p, "\\SEEN ");

    if(p != flagbuf)
      *--p = '\0';			/* tie off tmp_20k_buf   */
}



/*----------------------------------------------------------------------
   Save() helper function to create canonical date string from given header

   Args: date -- buf to recieve canonical date string
	 header -- rfc822 header to fish date string from

 Result: date filled with canonicalized date in header, or null string
 ----*/
void
saved_date(date, header)
    char *date, *header;
{
    char	 *d, *p, c;
    MESSAGECACHE  elt;

    *date = '\0';
    if((toupper((unsigned char)(*(d = header)))
	== 'D' && !strncmp(d, "date:", 5))
       || (d = srchstr(header, "\015\012date:"))){
	for(d += 7; *d == ' '; d++)
	  ;					/* skip white space */

	if(p = strstr(d, "\015\012")){
	    for(; p > d && *p == ' '; p--)
	      ;					/* skip white space */

	    c  = *p;
	    *p = '\0';				/* tie off internal date */
	}

	if(mail_parse_date(&elt, d))		/* let c-client normalize it */
	  mail_date(date, &elt);

	if(p)					/* restore header */
	  *p = c;
    }
}



/*----------------------------------------------------------------------
    Export a message to a plain file in users home directory

    Args: state -- pointer to struct holding a bunch of pine state
	 msgmap -- table mapping msg nums to c-client sequence nums
	  qline -- screen line to ask questions on
	    agg -- boolean indicating we're to operate on aggregate set

 Result: 
 ----*/
void
cmd_export(state, msgmap, qline, agg)
    struct pine *state;
    MSGNO_S     *msgmap;
    int          qline;
    int		 agg;
{
    HelpType  help = NO_HELP;
    char      filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
    int       r, leading_nl, failure = 0, orig_errno, over = 0;
    ENVELOPE *env;
    BODY     *b;
    long      i, count = 0L, start_of_append;
    gf_io_t   pc;
    STORE_S  *store;
    struct variable *vars = ps_global->vars;
    ESCKEY_S export_opts[5];

    if(ps_global->restricted){
	q_status_message(SM_ORDER, 0, 3,
	    "Pine demo can't export messages to files");
	return;
    }

    if(agg && !pseudo_selected(msgmap))
      return;

    export_opts[i = 0].ch  = ctrl('T');
    export_opts[i].rval	   = 10;
    export_opts[i].name	   = "^T";
    export_opts[i++].label = "To Files";

#if	!defined(DOS) && !defined(MAC) && !defined(OS2)
    if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
	export_opts[i].ch      = ctrl('V');
	export_opts[i].rval    = 12;
	export_opts[i].name    = "^V";
	export_opts[i++].label = "Downld Msg";
    }
#endif	/* !(DOS || MAC) */

    if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
	export_opts[i].ch      =  ctrl('I');
	export_opts[i].rval    = 11;
	export_opts[i].name    = "TAB";
	export_opts[i++].label = "Complete";
    }

#if	0
    /* Commented out since it's not yet support! */
    if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
	export_opts[i].ch      = ctrl('X');
	export_opts[i].rval    = 14;
	export_opts[i].name    = "^X";
	export_opts[i++].label = "ListMatches";
    }
#endif

    export_opts[i].ch = -1;
    filename[0] = '\0';

    r = get_export_filename(state, filename, full_filename, "message",
			    "EXPORT", export_opts, &over, qline,
			    GE_IS_EXPORT | GE_SEQ_SENSITIVE);

    if(r < 0){
	switch(r){
	  case -1:
	    cmd_cancelled("Export message");
	    break;

	  case -2:
	    q_status_message1(SM_ORDER, 0, 2,
			      "Can't export to file outside of %s",
			      VAR_OPER_DIR);
	    break;
	}

	goto fini;
    }
#if	!defined(DOS) && !defined(MAC) && !defined(OS2)
    else if(r == 12){			/* Download */
	char     cmd[MAXPATH], *tfp;
	int	     next = 0;
	PIPE_S  *syspipe;
	STORE_S *so;
	gf_io_t  pc;

	if(ps_global->restricted){
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "Download disallowed in restricted mode");
	    goto fini;
	}

	err = NULL;
	tfp = temp_nam(NULL, "pd");
	build_updown_cmd(cmd, ps_global->VAR_DOWNLOAD_CMD_PREFIX,
			 ps_global->VAR_DOWNLOAD_CMD, tfp);
	dprint(1, (debugfile, "Download cmd called: \"%s\"\n", cmd));
	if(so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY)){
	    gf_set_so_writec(&pc, so);

	    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
	      if(!(env = mail_fetchstructure(state->mail_stream,
					     mn_m2raw(msgmap, i), &b))
		 || !bezerk_delimiter(env, pc, next++)
		 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
				    env, b, FM_NEW_MESS | FM_NOWRAP, pc)){
		  q_status_message(SM_ORDER | SM_DING, 3, 3,
			   err = "Error writing tempfile for download");
		  break;
	      }

	    gf_clear_so_writec(so);
	    so_give(&so);			/* close file */

	    if(!err){
		if(syspipe = open_system_pipe(cmd, NULL, NULL,
					      PIPE_USER | PIPE_RESET, 0))
		  (void) close_system_pipe(&syspipe);
		else
		  q_status_message(SM_ORDER | SM_DING, 3, 3,
				err = "Error running download command");
	    }

	    unlink(tfp);
	}
	else
	  q_status_message(SM_ORDER | SM_DING, 3, 3,
			 err = "Error building temp file for download");

	fs_give((void **)&tfp);
	if(!err)
	  q_status_message(SM_ORDER, 0, 3, "Download Command Completed");

	goto fini;
    }
#endif	/* !(DOS || MAC) */


    switch(over){
      case 0:
      case 1:
	leading_nl = 0;
	break;

      case -1:
	leading_nl = 1;
	break;
    }

    dprint(5, (debugfile, "Opening file \"%s\" for export\n", full_filename));

    if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS))){
        q_status_message2(SM_ORDER | SM_DING, 3, 4,
			  "Error opening file \"%s\" to export message: %s",
                          full_filename, error_description(errno));
	goto fini;
    }
    else
      gf_set_so_writec(&pc, store);

    err = NULL;
    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
	env = mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i), &b);
	if(!env) {
	    err = "Can't export message. Error accessing mail folder";
	    failure = 1;
	    break;
	}

	start_of_append = ftell((FILE *)so_text(store));
	if(!bezerk_delimiter(env, pc, leading_nl)
	   || !format_message(mn_m2raw(msgmap, i), env, b,
			      FM_NEW_MESS | FM_NOWRAP, pc)){
	    orig_errno = errno;		/* save incase things are really bad */
	    failure    = 1;		/* pop out of here */
	    break;
	}

	leading_nl = 1;
    }

    gf_clear_so_writec(store);
    so_give(&store);				/* release storage */
    if(failure){
	truncate(full_filename, start_of_append);
	if(err){
	    dprint(1, (debugfile, "FAILED Export: fetch(%ld): %s\n",
		       i, err));
	    q_status_message(SM_ORDER | SM_DING, 3, 4, err);
	}
	else{
	    dprint(1, (debugfile, "FAILED Export: file \"%s\" : %s\n",
		       full_filename,  error_description(orig_errno)));
	    q_status_message2(SM_ORDER | SM_DING, 3, 4,
			      "Error exporting to \"%s\" : %s",
			      filename, error_description(orig_errno));
	}
    }
    else{
	if(mn_total_cur(msgmap) > 1L)
	  q_status_message4(SM_ORDER,0,3,"%s message%s %s to file \"%s\"",
			    long2string(count), plural(count),
			    over==0 ? "exported"
				    : over==1 ? "overwritten" : "appended",
			    filename);
	else
	  q_status_message3(SM_ORDER,0,3,"Message %s %s to file \"%s\"",
			    long2string(mn_get_cur(msgmap)),
			    over==0 ? "exported"
				    : over==1 ? "overwritten" : "appended",
			    filename);
    }

  fini:
    if(agg)
      restore_selected(msgmap);
}



/*
 * Ask user what file to export to. Export from srcstore to that file.
 *
 * Args     ps -- pine struct
 *     srctext -- pointer to source text
 *     srctype -- type of that source text
 *  prompt_msg -- see get_export_filename
 *  lister_msg --      "
 *
 * Returns: != 0 : error
 *             0 : ok
 */
int
simple_export(ps, srctext, srctype, prompt_msg, lister_msg)
    struct pine *ps;
    void        *srctext;
    SourceType   srctype;
    char        *prompt_msg;
    char        *lister_msg;
{
    int r = 1, over;
    char     filename[MAXPATH+1], full_filename[MAXPATH+1];
    STORE_S *store = NULL;
    struct variable *vars = ps->vars;
    static ESCKEY_S simple_export_opts[] = {
	{ctrl('T'), 10, "^T", "To Files"},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL}};

    if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
	simple_export_opts[r].ch    =  ctrl('I');
	simple_export_opts[r].rval  = 11;
	simple_export_opts[r].name  = "TAB";
	simple_export_opts[r].label = "Complete";
    }

    if(!srctext){
	q_status_message(SM_ORDER, 0, 2, "Error allocating space");
	r = -3;
	goto fini;
    }

    simple_export_opts[++r].ch = -1;
    filename[0] = '\0';
    full_filename[0] = '\0';

    r = get_export_filename(ps, filename, full_filename, prompt_msg,
			    lister_msg, simple_export_opts, &over,
			    -FOOTER_ROWS(ps), GE_IS_EXPORT);

    if(r < 0)
      goto fini;
    else if(!full_filename[0]){
	r = -1;
	goto fini;
    }

    dprint(5, (debugfile, "Opening file \"%s\" for export\n", full_filename));

    if((store = so_get(FileStar, full_filename, WRITE_ACCESS)) != NULL){
	char *pipe_err;
	gf_io_t pc, gc;

	gf_set_so_writec(&pc, store);
	gf_set_readc(&gc, srctext, (srctype == CharStar)
					? strlen((char *)srctext)
					: 0L,
		     srctype);
	gf_filter_init();
	if((pipe_err = gf_pipe(gc, pc)) != NULL){
	    q_status_message2(SM_ORDER | SM_DING, 3, 3,
			      "Problem saving to \"%s\": %s",
			      filename, pipe_err);
	    r = -3;
	}
	else
	  r = 0;

	gf_clear_so_writec(store);
	so_give(&store);
    }
    else{
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
			  "Error opening file \"%s\" for export: %s",
			  full_filename, error_description(errno));
	r = -3;
    }

fini:
    switch(r){
      case  0:
	/* overloading full_filename */
	sprintf(full_filename, "%c%s",
		(prompt_msg && prompt_msg[0])
		  ? (islower((unsigned char)prompt_msg[0])
		    ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
		  : 'T',
	        (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
	q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
			  full_filename,
			  over==0 ? "exported" :
			    over==1 ? "overwritten" : "appended",
			  filename);
	break;

      case -1:
	cmd_cancelled("Export");
	break;

      case -2:
	q_status_message1(SM_ORDER, 0, 2,
	    "Can't export to file outside of %s", VAR_OPER_DIR);
	break;
    }

    ps->mangled_footer = 1;
    return(r);
}



/*
 * Ask user what file to export to.
 *
 *       filename -- On input, this it the filename to start with. On exit,
 *                   this is the filename chosen. (but this isn't used)
 *  full_filename -- This is the full filename on exit.
 *     prompt_msg -- Message to insert in prompt.
 *     lister_msg -- Message to insert in file_lister.
 *           opts -- Key options.
 *           over -- This is a return value. over ==  1 => did overwrite
 *                                                         of existing file
 *                                                ==  0 => file didn't exist
 *                                                == -1 => did append
 *          qline -- Command line to prompt on.
 *          flags -- Logically OR'd flags
 *                     GE_IS_EXPORT     - The command was an Export command
 *                                        so the prompt should include
 *                                        EXPORT:.
 *                     GE_SEQ_SENSITIVE - The command that got us here is
 *                                        sensitive to sequence number changes
 *                                        caused by unsolicited expunges.
 *
 *  Returns:  -1  cancelled
 *            -2  prohibited by VAR_OPER_DIR
 *            -3  other error, already reported here
 *             0  ok
 *            12  user chose 12 command from opts
 */
int
get_export_filename(ps, filename, full_filename, prompt_msg,
		    lister_msg, opts, over, qline, flags)
    struct pine *ps;
    char        *filename;
    char        *full_filename;
    char        *prompt_msg;
    char        *lister_msg;
    ESCKEY_S     opts[];
    int         *over;
    int          qline;
    int          flags;
{
    HelpType  help = NO_HELP;
    char      dir[MAXPATH+1], dir2[MAXPATH+1];
    char      precolon[MAXPATH+1], postcolon[MAXPATH+1];
    char      filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
    int       l, i, r, fatal, homedir = 0, was_abs_path=0;
    char      prompt_buf[200];
    struct variable *vars = ps->vars;

    if(F_ON(F_USE_CURRENT_DIR, ps))
      dir[0] = '\0';
    else if(VAR_OPER_DIR)
      strcpy(dir, VAR_OPER_DIR);
#if	defined(DOS) || defined(OS2)
    else if(VAR_FILE_DIR)
      strcpy(dir, VAR_FILE_DIR);
#endif
    else{
	dir[0] = '~';
	dir[1] = '\0';
	homedir=1;
    }
    strcpy(precolon, dir);
    postcolon[0] = '\0';

    /*---------- Prompt the user for the file name -------------*/
    while(1){
	int oeflags;

	sprintf(prompt_buf, "%sCopy %s to file%s%s: ",
		(flags & GE_IS_EXPORT) ? "EXPORT: " : "SAVE: ",
		prompt_msg,
		is_absolute_path(filename) ? "" : " in ",
		is_absolute_path(filename) ? "" :
		  (!dir[0] ? "current directory"
			   : (dir[0] == '~' && !dir[1]) ? "home directory"
						        : dir));
	oeflags = OE_APPEND_CURRENT |
		  ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
	r = optionally_enter(filename, qline, 0, MAXPATH, prompt_buf,
			     opts, help, &oeflags);

        /*--- Help ----*/
	if(r == 3){
	    help = (help == NO_HELP) ? h_oe_export : NO_HELP;
	    continue;
        }
	else if(r == 10 || r == 11){	/* Browser or File Completion */
	    if(filename[0]=='~'){
	      if(filename[1] == C_FILESEP && filename[2]!='\0'){
		precolon[0] = '~';
		precolon[1] = '\0';
		for(i=0; filename[i+2] != '\0' && i+2<MAXPATH; i++)
		  filename[i] = filename[i+2];
		filename[i] = '\0';
		strcpy(dir, precolon);
	      }
	      else if(filename[1]=='\0' || 
		 (filename[1] == C_FILESEP && filename[2] == '\0')){
		precolon[0] = '~';
		precolon[1] = '\0';
		filename[0] = '\0';
		strcpy(dir,precolon);
	      }
	    }
	    else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
	      if(homedir){
		precolon[0] = '~';
		precolon[1] = '\0';
		strcpy(dir, precolon);
	      }
	      else{
		precolon[0] = '\0';
		dir[0] = '\0';
	      }
	    }
	    l = MAXPATH;
	    dir2[0] = '\0';
	    strncpy(tmp, filename, MAXPATH);
	    tmp[MAXPATH] = '\0';
	    if(*tmp && is_absolute_path(tmp))
	      fnexpand(tmp, MAXPATH);
	    if(strncmp(tmp,postcolon, strlen(postcolon)))
	      postcolon[0] = '\0';

	    if(*tmp && (fn = last_cmpnt(tmp))){
	        l -= fn - tmp;
		strcpy(filename2, fn);
		if(is_absolute_path(tmp)){
		    strncpy(dir2, tmp, fn - tmp);
		    dir2[fn - tmp] = '\0';
#ifdef _WINDOWS
		    if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
		      dir2[2] = '\\';
		      dir2[3] = '\0';
		    }
#endif
		    strcpy(postcolon, dir2);
		    precolon[0] = '\0';
		}
		else{
		    char *p = NULL;
		    /*
		     * Just building the directory name in dir2,
		     * full_filename is overloaded.
		     */
		    sprintf(full_filename, "%.*s", fn - tmp, tmp);
		    strncpy(postcolon, full_filename, MAXPATH);
		    postcolon[MAXPATH] = '\0';
		    build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
					     : (dir[0] == '~' && !dir[1])
					       ? ps->home_dir
					       : dir,
			       full_filename);
		    if(p)
		      free(p);
		}
	    }
	    else{
		if(is_absolute_path(tmp)){
		    strcpy(dir2, tmp);
#ifdef _WINDOWS
		    if(dir2[2]=='\0' && dir2[1]==':'){
		      dir2[2]='\\';
		      dir2[3]='\0';
		      strcpy(postcolon,dir2);
		    }
#endif
		    filename2[0] = '\0';
		    precolon[0] = '\0';
		}
		else{
		    strcpy(filename2, tmp);
		    if(!dir[0])
		      (void)getcwd(dir2, MAXPATH);
		    else if(dir[0] == '~' && !dir[1])
		      strcpy(dir2, ps->home_dir);
		    else
		      strcpy(dir2, dir);
		    postcolon[0] = '\0';
		}
	    }

	    build_path(full_filename, dir2, filename2);
	    if(!strcmp(full_filename, dir2))
	      filename2[0] = '\0';
	    if(full_filename[strlen(full_filename)-1] == C_FILESEP 
	       && isdir(full_filename,NULL,NULL)){
	      if(strlen(full_filename) == 1)
		strncpy(postcolon, full_filename, MAXPATH);
	      else if(filename2[0])
		strncpy(postcolon, filename2, MAXPATH);
	      postcolon[MAXPATH] = '\0';
	      strncpy(dir2, full_filename, MAXPATH);
	      dir2[MAXPATH] = '\0';
	      filename2[0] = '\0';
	    }
#ifdef _WINDOWS  /* use full_filename even if not a valid directory */
	    else if(full_filename[strlen(full_filename)-1] == C_FILESEP){ 
	      strncpy(postcolon, filename2, MAXPATH);
	      postcolon[MAXPATH] = '\0';
	      strncpy(dir2, full_filename, MAXPATH);
	      dir2[MAXPATH] = '\0';
	      filename2[0] = '\0';
	    }
#endif
	    if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
	       && strcmp(dir2+1, ":\\")) 
	      /* last condition to prevent stripping of '\\' 
		 in windows partition */
	      dir2[strlen(dir2)-1] = '\0';

	    if(r == 10){			/* File Browser */
		r = file_lister(lister_msg ? lister_msg : "EXPORT",
				dir2, MAXPATH+1, filename2, MAXPATH+1, 
                                TRUE, FB_SAVE);
#ifdef _WINDOWS
/* Windows has a special "feature" in which entering the file browser will
   change the working directory if the directory is changed at all (even
   clicking "Cancel" will change the working directory).
*/
		(void)getcwd(dir2,MAXPATH);
#endif
		if(isdir(dir2,NULL,NULL)){
		  strncpy(precolon, dir2, MAXPATH);
		  precolon[MAXPATH] = '\0';
		}
		strncpy(postcolon, filename2, MAXPATH);
		postcolon[MAXPATH] = '\0';
		if(r == 1){
		    build_path(full_filename, dir2, filename2);
		    if(isdir(full_filename, NULL, NULL)){
			strcpy(dir, full_filename);
			filename[0] = '\0';
		    }
		    else{
			fn = last_cmpnt(full_filename);
			strncpy(dir, full_filename, fn - full_filename);
			dir[fn - full_filename] = '\0';
			if(fn - full_filename > 1)
			  dir[fn - full_filename - 1] = '\0';
		    }
		    
		    if(!strcmp(dir, ps->home_dir)){
			dir[0] = '~';
			dir[1] = '\0';
		    }

		    strcpy(filename, fn);
		}
	    }
	    else{				/* File Completion */
	      if(!pico_fncomplete(dir2, filename2, l - 1))
		  Writechar(BELL, 0);
	      strncat(postcolon, filename2, MAXPATH);
	      postcolon[MAXPATH] = '\0';
	      
	      was_abs_path = is_absolute_path(filename);

	      if(!strcmp(dir, ps->home_dir)){
		dir[0] = '~';
		dir[1] = '\0';
	      }
	    }
	    strcpy(filename, postcolon);
	    strcpy(dir, precolon);

	    if(filename[0] == '~' && !filename[1]){
		dir[0] = '~';
		dir[1] = '\0';
		filename[0] = '\0';
	    }

	    continue;
	}
	else if(r == 12){	/* Download, caller handles it */
	    return(r);
	}
#if	0
	else if(r == 14){	/* List file names matching partial? */
	    continue;
	}
#endif
        else if(r == 1 || (r == 0 && filename[0] == '\0')){
	    return(-1);		/* Cancel */
        }
        else if(r == 4){
	    continue;
	}
	else if(r != 0){
	    Writechar(BELL, 0);
	    continue;
	}

        removing_trailing_white_space(filename);
        removing_leading_white_space(filename);

#if	defined(DOS) || defined(OS2)
	if(is_absolute_path(filename)){
	    fixpath(filename, MAXPATH);
	}
#else
	if(filename[0] == '~'){
	    if(fnexpand(filename, MAXPATH) == NULL){
		char *p = strindex(filename, '/');
		if(p != NULL)
		  *p = '\0';
		q_status_message1(SM_ORDER | SM_DING, 3, 3,
			      "Error expanding file name: \"%s\" unknown user",
			      filename);
		continue;
	    }
	}
#endif

	if(is_absolute_path(filename))
	  strcpy(full_filename, filename);
	else{
	    if(!dir[0])
	      build_path(full_filename,(char *) getcwd(dir,MAXPATH), filename);
	    else if(dir[0] == '~' && !dir[1])
	      build_path(full_filename, ps->home_dir, filename);
	    else
	      build_path(full_filename, dir, filename);
	}

        if((ill = filter_filename(full_filename, &fatal)) != NULL){
	    if(fatal){
		q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
		continue;
	    }
	    else{
/* BUG: we should beep when the key's pressed rather than bitch later */
		/* Warn and ask for confirmation. */
		sprintf(prompt_buf, "File name contains a '%s'.  %s anyway",
			ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
		if(want_to(prompt_buf, 'n', 0, NO_HELP,
		  ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
		  continue;
	    }
	}

	break;		/* Must have got an OK file name */
    }

    if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename))
      return(-2);

    *over = 0;
    if(!can_access(full_filename, ACCESS_EXISTS)){
	int rbflags;

	static ESCKEY_S access_opts[] = {
	    {'o', 'o', "O", "Overwrite"},
	    {'a', 'a', "A", "Append"},
	    {-1, 0, NULL, NULL}};

	r = strlen(filename);
        sprintf(prompt_buf,
		"File \"%s%s\" already exists.  Overwrite or append it ? ",
		(r > 20) ? "..." : "",
                filename + ((r > 20) ? r - 20 : 0));
	rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
	switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global), access_opts,
			     'a', 'x', NO_HELP, rbflags)){
	  case 'o' :
	    *over = 1;
	    if(unlink(full_filename) < 0){	/* BUG: breaks links */
		q_status_message2(SM_ORDER | SM_DING, 3, 5,
				  "Error deleting old %s: %s",
				  full_filename, error_description(errno));
		return(-1);
	    }

	    break;

	  case 'a' :
 	    *over = -1;
	    break;

	  case 'x' :
	  default :
	    return(-1);
	}
    }

    return(0);
}


/*----------------------------------------------------------------------
  parse the config'd upload/download command

  Args: cmd -- buffer to return command fit for shellin'
	prefix --
	cfg_str --
	fname -- file name to build into the command

  Returns: pointer to cmd_str buffer or NULL on real bad error

  NOTE: One SIDE EFFECT is that any defined "prefix" string in the
	cfg_str is written to standard out right before a successful
	return of this function.  The call immediately following this
	function darn well better be the shell exec...
 ----*/
char *
build_updown_cmd(cmd, prefix, cfg_str, fname)
    char *cmd;
    char *prefix;
    char *cfg_str;
    char *fname;
{
    char *p;
    int   fname_found = 0;

    if(prefix && *prefix){
	/* loop thru replacing all occurances of _FILE_ */
	for(p = strcpy(cmd, prefix); (p = strstr(p, "_FILE_")); )
	  rplstr(p, 6, fname);

	fputs(cmd, stdout);
    }

    /* loop thru replacing all occurances of _FILE_ */
    for(p = strcpy(cmd, cfg_str); (p = strstr(p, "_FILE_")); ){
	rplstr(p, 6, fname);
	fname_found = 1;
    }

    if(!fname_found)
      sprintf(cmd + strlen(cmd), " %s", fname);

    dprint(4, (debugfile, "\n - build_updown_cmd = \"%s\" -\n", cmd));
    return(cmd);
}






/*----------------------------------------------------------------------
  Write a berzerk format message delimiter using the given putc function

    Args: e -- envelope of message to write
	  pc -- function to use 

    Returns: TRUE if we could write it, FALSE if there was a problem

    NOTE: follows delimiter with OS-dependent newline
 ----*/
int
bezerk_delimiter(env, pc, leading_newline)
    ENVELOPE *env;
    gf_io_t   pc;
    int	      leading_newline;
{
    time_t  now = time(0);
    char   *p = ctime(&now);
    
    /* write "[\n]From mailbox[@host] " */
    if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
	 && gf_puts("From ", pc)
	 && gf_puts((env && env->from) ? env->from->mailbox
				       : "the-concourse-on-high", pc)
	 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
	 && gf_puts((env && env->from && env->from->host) ? env->from->host
							  : "", pc)
	 && (*pc)(' ')))
      return(0);

    while(p && *p && *p != '\n')	/* write date */
      if(!(*pc)(*p++))
	return(0);

    if(!gf_puts(NEWLINE, pc))		/* write terminating newline */
      return(0);

    return(1);
}



/*----------------------------------------------------------------------
      Execute command to jump to a given message number

    Args: qline -- Line to ask question on

  Result: returns true if the use selected a new message, false otherwise

 ----*/
int
jump_to(msgmap, qline, first_num)
     MSGNO_S *msgmap;
     int      qline, first_num;
{
    char     jump_num_string[80], *j, prompt[70];
    HelpType help;
    int      rc;
    long     jump_num = -1L;

    dprint(4, (debugfile, "\n - jump_to -\n"));

    if(!any_messages(msgmap, NULL, "to Jump to"))
      return(0);

    if(first_num && isdigit((unsigned char) first_num)){
	jump_num_string[0] = first_num;
	jump_num_string[1] = '\0';
    }
    else
      jump_num_string[0] = '\0';

    if(mn_total_cur(msgmap) > 1L){
	sprintf(prompt, "Unselect %s msgs in favor of number to be entered", 
		comatose(mn_total_cur(msgmap)));
	if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
	  return(0);
    }

    strcpy(prompt, "Message number to jump to : ");

    help = NO_HELP;
    while (1) {
	int flags = OE_APPEND_CURRENT;

        rc = optionally_enter(jump_num_string, qline, 0,
                              sizeof(jump_num_string) - 1, prompt,
                              NULL, help, &flags);
        if(rc == 3) {
            help = help == NO_HELP ? h_oe_jump : NO_HELP;
            continue;
        }

        if(rc == 0 && *jump_num_string != '\0') {
	    removing_trailing_white_space(jump_num_string);
	    removing_leading_white_space(jump_num_string);
            for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++);
	    if(*j != '\0') {
	        q_status_message(SM_ORDER | SM_DING, 2, 2,
                           "Invalid number entered. Use only digits 0-9");
            } else {
                jump_num = atol(jump_num_string);
                if(jump_num < 1L) {
	            q_status_message1(SM_ORDER | SM_DING, 2, 2,
			      "Message number (%s) must be greater than 0",
				      long2string(jump_num));
                } else if(jump_num > mn_get_total(msgmap)) {
                    q_status_message1(SM_ORDER | SM_DING, 2, 2,
	      "Message number must be no more than %s, the number of messages",
				      long2string(mn_get_total(msgmap)));
                } else if(get_lflag(ps_global->mail_stream, msgmap,
				    jump_num, MN_HIDE)){
	            q_status_message1(SM_ORDER | SM_DING, 2, 2,
			  "Message number (%s) is not in \"Zoomed Index\"",
				      long2string(jump_num));
		} else {
		    int rv = (mn_get_cur(msgmap) != jump_num);

		    if(mn_total_cur(msgmap) > 1L){
			mn_reset_cur(msgmap, jump_num);
		    }
		    else{
			mn_set_cur(msgmap, jump_num);
		    }

		    return(rv);
                }
            }

            jump_num_string[0] = '\0';
            continue;
	}

        if(rc != 4)
          break;
    }

    return(0);
}



/*----------------------------------------------------------------------
     Prompt for folder name to open, expand the name and return it

   Args: qline      -- Screen line to prompt on
         allow_list -- if 1, allow ^T to bring up collection lister

 Result: returns the folder name or NULL
         pine structure mangled_footer flag is set
         may call the collection lister in which case mangled screen will be set

 This prompts the user for the folder to open, possibly calling up
the collection lister if the user types ^T.
----------------------------------------------------------------------*/
char *
broach_folder(qline, allow_list, context)
    int qline, allow_list;
    CONTEXT_S **context;
{
    HelpType	help;
    static char newfolder[MAILTMPLEN];
    char        expanded[MAXPATH+1],
                prompt[MAX_SCREEN_COLS+1],
               *last_folder;
    CONTEXT_S  *tc, *tc2;
    ESCKEY_S    ekey[8];
    int		rc, n, flags, last_rc = 0, inbox, done = 0;

    /*
     * the idea is to provide a clue for the context the file name
     * will be saved in (if a non-imap names is typed), and to
     * only show the previous if it was also in the same context
     */
    help	   = NO_HELP;
    *expanded	   = '\0';
    *newfolder	   = '\0';
    last_folder	   = NULL;

    /*
     * There are three possibilities for the prompt's offered default.
     *  1) always the last folder visited
     *  2) if non-inbox current, inbox else last folder visited
     *  3) if non-inbox current, inbox else last folder visited in
     *     the first collection
     */
    if(ps_global->goto_default_rule == GOTO_LAST_FLDR){
	tc = (context && *context) ? *context : ps_global->context_current;
	inbox = 1;		/* fill in last_folder below */
    }
    else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN){
	tc = (ps_global->context_list->use & CNTXT_INCMNG)
	  ? ps_global->context_list->next : ps_global->context_list;
    }
    else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX){
	tc = (ps_global->context_list->use & CNTXT_INCMNG)
	  ? ps_global->context_list->next : ps_global->context_list;
	tc->last_folder[0] = '\0';
	inbox = 0;
    }
    else{
	inbox = strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0;
	if(!inbox)
	  tc = ps_global->context_list;		/* inbox's context */
	else if(ps_global->goto_default_rule == GOTO_INBOX_FIRST_CLCTN)
	  tc = (ps_global->context_list->use & CNTXT_INCMNG)
		 ? ps_global->context_list->next : ps_global->context_list;
	else
	  tc = (context && *context) ? *context : ps_global->context_current;
    }

    /* set up extra command option keys */
    rc = 0;
    ekey[rc].ch	     = (allow_list) ? ctrl('T') : 0 ;
    ekey[rc].rval    = (allow_list) ? 2 : 0;
    ekey[rc].name    = (allow_list) ? "^T" : "";
    ekey[rc++].label = (allow_list) ? "ToFldrs" : "";

    if(ps_global->context_list->next){
	ekey[rc].ch      = ctrl('P');
	ekey[rc].rval    = 10;
	ekey[rc].name    = "^P";
	ekey[rc++].label = "Prev Collection";

	ekey[rc].ch      = ctrl('N');
	ekey[rc].rval    = 11;
	ekey[rc].name    = "^N";
	ekey[rc++].label = "Next Collection";
    }

    if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
	ekey[rc].ch      = TAB;
	ekey[rc].rval    = 12;
	ekey[rc].name    = "TAB";
	ekey[rc++].label = "Complete";
    }

    if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
	ekey[rc].ch      = ctrl('X');
	ekey[rc].rval    = 14;
	ekey[rc].name    = "^X";
	ekey[rc++].label = "ListMatches";
    }

    if(ps_global->context_list->next){
	ekey[rc].ch      = KEY_UP;
	ekey[rc].rval    = 10;
	ekey[rc].name    = "";
	ekey[rc++].label = "";

	ekey[rc].ch      = KEY_DOWN;
	ekey[rc].rval    = 11;
	ekey[rc].name    = "";
	ekey[rc++].label = "";
    }

    ekey[rc].ch = -1;

    while(!done) {
	/*
	 * Figure out next default value for this context.  The idea
	 * is that in each context the last folder opened is cached.
	 * It's up to pick it out and display it.  This is fine
	 * and dandy if we've currently got the inbox open, BUT
	 * if not, make the inbox the default the first time thru.
	 */
	if(!inbox){
	    last_folder = ps_global->inbox_name;
	    inbox = 1;		/* pretend we're in inbox from here on out */
	}
	else
	  last_folder = (tc->last_folder[0]) ? tc->last_folder : NULL;

	if(last_folder)
	  sprintf(expanded, " [%s]", last_folder);
	else
	  *expanded = '\0';

	/* only show collection number if more than one available */
	if(ps_global->context_list->next){
	    sprintf(prompt, "GOTO %s in <%.20s> %s%s: ",
		    NEWS_TEST(tc) ? "news group" : "folder",
		    tc->nickname, expanded, *expanded ? " " : "");
	}
	else
	  sprintf(prompt, "GOTO folder %s: ", expanded, *expanded ? " " : "");

	flags = OE_APPEND_CURRENT;
        rc = optionally_enter(newfolder, qline, 0, MAILTMPLEN,
			      prompt, ekey, help, &flags);

	ps_global->mangled_footer = 1;

	switch(rc){
	  case -1 :				/* o_e says error! */
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "Error reading folder name");
	    return(NULL);

	  case 0 :				/* o_e says normal entry */
	    removing_trailing_white_space(newfolder);
	    removing_leading_white_space(newfolder);

	    if(*newfolder){
		char *p, *name, *fullname = NULL;
		int   exists, breakout = 0;

		if(!(name = folder_is_nick(newfolder, FOLDERS(tc))))
		  name = newfolder;

		if(update_folder_spec(expanded, name))
		  strcpy(name = newfolder, expanded);

		exists = folder_name_exists(tc, name, &fullname);

		if(fullname){
		    strcpy(name = newfolder, fullname);
		    fs_give((void **) &fullname);
		    breakout = TRUE;
		}

		if(exists & FEX_ISFILE){
		    done++;
		    break;
		}
		else if((exists & FEX_ISDIR)){
		    if(breakout){
			CONTEXT_S *fake_context;
			char	   tmp[MAILTMPLEN];
			size_t	   l;

			strcpy(tmp, name);
			if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
			    tmp[l] = tc->dir->delim;
			    strcpy(&tmp[l+1], "[]");
			}
			else
			  strcat(tmp, "[]");

			fake_context = new_context(tmp, 0);
			newfolder[0] = '\0';
			done = display_folder_list(&fake_context, newfolder,
						   1, folders_for_goto);
			free_context(&fake_context);
			break;
		    }
		    else if(!(tc->use & CNTXT_INCMNG)){
			done = display_folder_list(&tc, newfolder,
						   1, folders_for_goto);
			break;
		    }
		}

		if(exists == FEX_ERROR)
		  q_status_message1(SM_ORDER, 0, 3,
				    "Problem accessing folder \"%s\"",
				    newfolder);
		else if(tc->use & CNTXT_INCMNG)
		  q_status_message1(SM_ORDER, 0, 3,
				    "Can't find Incoming Folder: %s",
				    newfolder);
		else if(context_isambig(newfolder))
		  q_status_message3(SM_ORDER, 0, 3,
				    "Can't find folder \"%s\" in %.*s",
				    newfolder, (void *) 50, tc->nickname);
		else
		  q_status_message1(SM_ORDER, 0, 3,
				    "Can't find folder \"%s\"",
				    newfolder);

		return(NULL);
	    }
	    else if(last_folder){
		strcpy(newfolder, last_folder);
		done++;
		break;
	    }
	    /* fall thru like they cancelled */

	  case 1 :				/* o_e says user cancel */
	    cmd_cancelled("Open folder");
	    return(NULL);

	  case 2 :				/* o_e says user wants list */
	    if(display_folder_list(&tc, newfolder, 0, folders_for_goto))
	      done++;

	    break;

	  case 3 :				/* o_e says user wants help */
	    help = help == NO_HELP ? h_oe_broach : NO_HELP;
	    break;

	  case 4 :				/* redraw */
	    break;
	    
	  case 10 :				/* Previous collection */
	    tc2 = ps_global->context_list;
	    while(tc2->next && tc2->next != tc)
	      tc2 = tc2->next;

	    tc = tc2;
	    break;

	  case 11 :				/* Next collection */
	    tc = (tc->next) ? tc->next : ps_global->context_list;
	    break;

	  case 12 :				/* file name completion */
	    if(!folder_complete(tc, newfolder, &n)){
		if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
		    if(display_folder_list(&tc, newfolder, 1,folders_for_goto))
		      done++;			/* bingo! */
		    else
		      rc = 0;			/* burn last_rc */
		}
		else
		  Writechar(BELL, 0);
	    }

	    break;

	  case 14 :				/* file name completion */
	    if(display_folder_list(&tc, newfolder, 2, folders_for_goto))
	      done++;			/* bingo! */
	    else
	      rc = 0;			/* burn last_rc */

	    break;

	  default :
	    panic("Unhandled case");
	    break;
	}

	last_rc = rc;
    }

    dprint(2, (debugfile, "broach folder, name entered \"%s\"\n", newfolder));

    /*-- Just check that we can expand this. It gets done for real later --*/
    strcpy(expanded, newfolder);
    if (! expand_foldername(expanded)) {
        dprint(1, (debugfile,
                    "Error: Failed on expansion of filename %s (save)\n", 
    	  expanded));
        return(NULL);
    }

    *context = tc;
    return(newfolder);
}


/*----------------------------------------------------------------------
    Check to see if user input is in form of old c-client mailbox speck

  Args: old --
	new -- 

 Result:  1 if the folder was successfully updatedn
          0 if not necessary
      
  ----*/
int
update_folder_spec(new, old)
    char *new, *old;
{
    char *p;
    int	  nntp = 0;

    if(*(p = old) == '*')		/* old form? */
      old++;

    if(*old == '{')			/* copy host spec */
      do
	switch(*new = *old++){
	  case '\0' :
	    return(FALSE);

	  case '/' :
	    if(!struncmp(old, "nntp", 4))
	      nntp++;

	    break;

	  default :
	    break;
	}
      while(*new++ != '}');

    if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
	/*
	 * OK, some heuristics here.  If it looks like a newsgroup
	 * then we plunk it into the #news namespace else we
	 * assume that they're trying to get at a #public folder...
	 */
	for(p = old;
	    *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
	    p++)
	  ;

	sstrcpy(&new, (*p && !nntp) ? "#public/" : "#news.");
	strcpy(new, old);
	return(TRUE);
    }

    return(FALSE);
}


/*----------------------------------------------------------------------
    Actually attempt to open given folder 

  Args: newfolder -- The folder name to open

 Result:  1 if the folder was successfully opened
          0 if the folder open failed and went back to old folder
         -1 if open failed and no folder is left open
      
  Attempt to open the folder name given. If the open of the new folder
  fails then the previously open folder will remain open, unless
  something really bad has happened. The designate inbox will always be
  kept open, and when a request to open it is made the already open
  stream will be used. Making a folder the current folder requires
  setting the following elements of struct pine: mail_stream, cur_folder,
  current_msgno, max_msgno. Attempting to reopen the current folder is a 
  no-op.

  The first time the inbox folder is opened, usually as Pine starts up,
  it will be actually opened.
  ----*/

do_broach_folder(newfolder, new_context) 
     char      *newfolder;
     CONTEXT_S *new_context;
{
    MAILSTREAM *m;
    int         open_inbox, rv, old_tros, we_cancel = 0;
    char        expanded_file[max(MAXPATH,MAILTMPLEN)+1],
	       *old_folder, *old_path, *p;
    long        openmode;
    char        status_msg[81];
    SortOrder	old_sort;

#if	defined(DOS) && !defined(WIN32)
    openmode = OP_SHORTCACHE;
#else
    openmode = 0L;
#endif

    dprint(1, (debugfile, "About to open folder \"%s\"    inbox: \"%s\"\n",
	       newfolder, ps_global->inbox_name));

    /*----- Little to do to if reopening same folder -----*/
    if(new_context == ps_global->context_current && ps_global->mail_stream
       && strcmp(newfolder, ps_global->cur_folder) == 0){
	if(ps_global->dead_stream){
	    /* though, if it's not healthy, we reset things and fall thru
	     * to actually reopen it...
	     */
	    q_status_message1(SM_ORDER, 0, 4, 
			      "Attempting to reopen closed folder \"%s\"",
			      newfolder);
	    pine_close_stream(ps_global->mail_stream);
	    if(ps_global->mail_stream == ps_global->inbox_stream)
	      ps_global->inbox_stream = NULL;

	    ps_global->mail_stream = NULL;
	    ps_global->expunge_count       = 0;
	    ps_global->new_mail_count      = 0;
	    ps_global->noticed_dead_stream = 0;
	    ps_global->dead_stream         = 0;
	    ps_global->mangled_header	   = 1;
	    mn_give(&ps_global->msgmap);
	    clear_index_cache();
	    reset_check_point();
	}
	else
	  return(1);			/* successful open of same folder! */
    }

    /*--- Set flag that we're opening the inbox, a special case ---*/
    /*
     * We want to know if inbox is being opened either by name OR
     * fully qualified path...
     *
     * So, IF we're asked to open inbox AND it's already open AND
     * the only stream AND it's healthy, just return ELSE fall thru
     * and close mail_stream returning with inbox_stream as new stream...
     */
    if(open_inbox = (strucmp(newfolder, ps_global->inbox_name) == 0
		     || strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0)){
	new_context = ps_global->context_list; /* restore first context */
	if(ps_global->inbox_stream 
	   && (ps_global->inbox_stream == ps_global->mail_stream))
	  return(1);
    }

    /*
     * If ambiguous foldername (not fully qualified), make sure it's
     * not a nickname for a folder in the given context...
     */
    strcpy(expanded_file, newfolder); 	/* might get reset below */
    if(!open_inbox && new_context && context_isambig(newfolder)){
	if (p = folder_is_nick(newfolder, FOLDERS(new_context))){
	    strcpy(expanded_file, p);
	    dprint(2, (debugfile, "broach_folder: nickname for %s is %s\n",
		       expanded_file, newfolder));
	}
	else if ((new_context->use & CNTXT_INCMNG)
		 && (folder_index(newfolder, new_context, FI_FOLDER) < 0)){
	    q_status_message1(SM_ORDER, 3, 4,
			    "Can't find Incoming Folder %s.", newfolder);
	    return(0);
	}
    }

    /*--- Opening inbox, inbox has been already opened, the easy case ---*/
    if(open_inbox && ps_global->inbox_stream != NULL ) {
        expunge_and_close(ps_global->mail_stream, ps_global->context_current,
			  ps_global->cur_folder, NULL);

	ps_global->mail_stream              = ps_global->inbox_stream;
        ps_global->new_mail_count           = 0L;
        ps_global->expunge_count            = 0L;
        ps_global->mail_box_changed         = 0;
        ps_global->noticed_dead_stream      = 0;
        ps_global->noticed_dead_inbox       = 0;
        ps_global->dead_stream              = 0;
        ps_global->dead_inbox               = 0;
	mn_give(&ps_global->msgmap);
	ps_global->msgmap		    = ps_global->inbox_msgmap;
	ps_global->inbox_msgmap		    = NULL;

	dprint(7, (debugfile, "%ld %ld %x\n",
		   mn_get_cur(ps_global->msgmap),
                   mn_get_total(ps_global->msgmap),
		   ps_global->mail_stream));
	/*
	 * remember last context and folder
	 */
	if(context_isambig(ps_global->cur_folder)){
	    ps_global->context_last = ps_global->context_current;
	    strcpy(ps_global->context_current->last_folder,
		   ps_global->cur_folder);
	}

	strcpy(ps_global->cur_folder, ps_global->inbox_name);
	ps_global->context_current = ps_global->context_list;
	clear_index_cache();
        /* MUST sort before restoring msgno! */
	refresh_sort(ps_global->msgmap, FALSE);
        q_status_message3(SM_ORDER, 0, 3,
			  "Opened folder \"%s\" with %s message%s",
			  ps_global->inbox_name, 
                          long2string(mn_get_total(ps_global->msgmap)),
			  plural(mn_get_total(ps_global->msgmap)));
#ifdef	_WINDOWS
	mswin_settitle(ps_global->inbox_name);
#endif
	return(1);
    }

    if(!new_context && ! expand_foldername(expanded_file))
      return(0);

    old_folder = NULL;
    old_path   = NULL;
    old_sort   = SortArrival;			/* old sort */
    old_tros   = 0;				/* old reverse sort ? */
    /*---- now close the old one we had open if there was one ----*/
    if(ps_global->mail_stream != NULL){
        old_folder   = cpystr(ps_global->cur_folder);
        old_path     = cpystr(ps_global->mail_stream->mailbox);
	old_sort     = mn_get_sort(ps_global->msgmap);
	old_tros     = mn_get_revsort(ps_global->msgmap);
	if(strcmp(ps_global->cur_folder, ps_global->inbox_name) == 0){
	    /*-- don't close the inbox stream, save a bit of state --*/
	    if(ps_global->inbox_msgmap)
	      mn_give(&ps_global->inbox_msgmap);

	    ps_global->inbox_msgmap = ps_global->msgmap;
	    ps_global->msgmap       = NULL;

	    dprint(2, (debugfile,
		       "Close - saved inbox state: max %ld\n",
		       mn_get_total(ps_global->inbox_msgmap)));
	}
	else{
	    expunge_and_close(ps_global->mail_stream,
			      ps_global->context_current,
			      ps_global->cur_folder, NULL);
	    ps_global->mail_stream = NULL;
	}
    }

    strcat(strncat(strcpy(status_msg, "Opening \""),
	    pretty_fn(newfolder), 70), "\"");
    we_cancel = busy_alarm(1, status_msg, NULL, 1);

    /* 
     * if requested, make access to folder readonly (only once)
     */
    if (ps_global->open_readonly_on_startup) {
	openmode |= OP_READONLY ;
	ps_global->open_readonly_on_startup = 0 ;
    }

    /*
     * The name "inbox" is special, so treat it so 
     * (used to by handled by expand_folder)...
     */
    if(ps_global->nr_mode)
      ps_global->noshow_warn = 1;

    ps_global->first_unseen = 0L;

    m = context_open((new_context && !open_inbox) ? new_context : NULL, NULL, 
		     open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
		     openmode);

    if(ps_global->nr_mode)
      ps_global->noshow_warn = 0;

    dprint(8, (debugfile, "Opened folder %p \"%s\" (context: \"%s\")\n",
               m, (m) ? m->mailbox : "nil",
	       (new_context) ? new_context->context : "nil"));


    /* Can get m != NULL if correct passwd for remote, but wrong name */
    if(m == NULL || ((p = strindex(m->mailbox, '<')) != NULL &&
                      strcmp(p + 1, "no_mailbox>") == 0)) {
	/*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
        /* fall back to currently open mailbox */
	if(we_cancel)
	  cancel_busy_alarm(-1);

        rv = 0;
        dprint(8, (debugfile, "Old folder: \"%s\"\n",
               old_folder == NULL ? "" : old_folder));
        if(old_folder != NULL) {
            if(strcmp(old_folder, ps_global->inbox_name) == 0){
                ps_global->mail_stream = ps_global->inbox_stream;
		if(ps_global->msgmap)
		  mn_give(&ps_global->msgmap);

		ps_global->msgmap       = ps_global->inbox_msgmap;
		ps_global->inbox_msgmap = NULL;

                dprint(8, (debugfile, "Reactivate inbox %ld %ld %p\n",
                           mn_get_cur(ps_global->msgmap),
                           mn_get_total(ps_global->msgmap),
                           ps_global->mail_stream));
                strcpy(ps_global->cur_folder, ps_global->inbox_name);
            } else {
                ps_global->mail_stream = pine_mail_open(NULL, old_path,
							openmode);
                /* mm_log will take care of error message here */
                if(ps_global->mail_stream == NULL) {
                    rv = -1;
                } else {
		    mn_init(&(ps_global->msgmap),
			    ps_global->mail_stream->nmsgs);
		    mn_set_sort(ps_global->msgmap, old_sort);
		    mn_set_revsort(ps_global->msgmap, old_tros);
                    ps_global->expunge_count       = 0;
                    ps_global->new_mail_count      = 0;
                    ps_global->noticed_dead_stream = 0;
                    ps_global->dead_stream         = 0;
		    ps_global->mangled_header	   = 1;

		    clear_index_cache();
                    reset_check_point();
		    if(mn_get_total(ps_global->msgmap) > 0)
		      mn_set_cur(ps_global->msgmap, 1L);

		    if(!(mn_get_sort(ps_global->msgmap) == SortArrival
			 && !mn_get_revsort(ps_global->msgmap)))
		      refresh_sort(ps_global->msgmap, FALSE);

                    q_status_message1(SM_ORDER, 0, 3, "Folder \"%s\" reopened",
                                      old_folder);
                }
            }

	    if(rv == 0)
	      mn_set_cur(ps_global->msgmap,
			 min(mn_get_cur(ps_global->msgmap), 
			     mn_get_total(ps_global->msgmap)));

            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        } else {
            rv = -1;
        }
        if(rv == -1) {
            q_status_message(SM_ORDER | SM_DING, 0, 4, "No folder opened");
	    mn_set_total(ps_global->msgmap, 0L);
	    mn_set_nmsgs(ps_global->msgmap, 0L);
	    mn_set_cur(ps_global->msgmap, -1L);
            strcpy(ps_global->cur_folder, "");
        }
        return(rv);
    } else {
        if(old_folder != NULL) {
            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        }
    }

    /*----- success in opening the new folder ----*/
    dprint(2, (debugfile, "Opened folder \"%s\" with %ld messages\n",
	       m->mailbox, m->nmsgs));


    /*--- A Little house keeping ---*/
    ps_global->mail_stream	    = m;
    ps_global->expunge_count	    = 0L;
    ps_global->new_mail_count	    = 0L;
    ps_global->noticed_dead_stream  = 0;
    ps_global->noticed_dead_inbox   = 0;
    ps_global->dead_stream          = 0;
    ps_global->dead_inbox           = 0;
    mn_init(&(ps_global->msgmap), m->nmsgs);

    /*
     * remember old folder and context...
     */
    if(context_isambig(ps_global->cur_folder)
       || strucmp(ps_global->cur_folder, ps_global->inbox_name) == 0)
      strcpy(ps_global->context_current->last_folder,ps_global->cur_folder);

    /* folder in a subdir of context? */
    if(ps_global->context_current->dir->prev)
      sprintf(ps_global->cur_folder, "%s%s",
		ps_global->context_current->dir->ref,
		newfolder);
    else
      strcpy(ps_global->cur_folder,
	     (open_inbox) ? ps_global->inbox_name : newfolder);

    if(new_context){
	ps_global->context_last    = ps_global->context_current;
	ps_global->context_current = new_context;
    }

    reset_check_point();
    clear_index_cache();
    ps_global->mail_box_changed = 0;

    /*
     * Start news reading with messages the user's marked deleted
     * hidden from view...
     */
    if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
      msgno_exclude_deleted(ps_global->mail_stream, ps_global->msgmap);

    /*--- If we just opened the inbox remember it's special stream ---*/
    if(open_inbox && ps_global->inbox_stream == NULL)
      ps_global->inbox_stream = ps_global->mail_stream;
	
    process_filter_patterns(ps_global->mail_stream, ps_global->msgmap, 0L);

    if(we_cancel)
      cancel_busy_alarm(0);

    /* UWIN doesn't want to see this message */
    if(!ps_global->nr_mode)
      q_status_message7(SM_ORDER, 0, 4, "%s \"%s\" opened with %s message%s%s",
			IS_NEWS(ps_global->mail_stream)
			  ? "News group" : "Folder",
			pretty_fn(newfolder),
			comatose(mn_get_total(ps_global->msgmap)),
			plural(mn_get_total(ps_global->msgmap)),
			READONLY_FOLDER ? " READONLY" : "",
			NULL, NULL);

#ifdef	_WINDOWS
    mswin_settitle(pretty_fn(newfolder));
#endif

    sort_folder(ps_global->msgmap, ps_global->def_sort,
		ps_global->def_sort_rev, TRUE);

    if(mn_get_total(ps_global->msgmap) > 0L) {
	if(ps_global->start_entry > 0) {
	    mn_set_cur(ps_global->msgmap,
		       min(ps_global->start_entry,
			   mn_get_total(ps_global->msgmap)));
	    ps_global->start_entry = 0;
        }
	else if(open_inbox || (ps_global->context_current->use&CNTXT_INCMNG)){
	    switch(ps_global->inc_startup_rule){
	      /*
	       * For news in incoming collection we're doing the same thing
	       * for first-unseen and first-recent. In both those cases you
	       * get first-unseen if FAKE_NEW is off and first-recent if
	       * FAKE_NEW is on.
	       */
	      case IS_FIRST_UNSEEN:
		if(IS_NEWS(ps_global->mail_stream)
		   && F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
		    mn_set_cur(ps_global->msgmap,
			       first_sorted_flagged(F_RECENT|F_UNDEL, m, 0));
		}
		else{
		    mn_set_cur(ps_global->msgmap,
			    (ps_global->first_unseen
			     && (mn_get_sort(ps_global->msgmap) == SortArrival
				 && !mn_get_revsort(ps_global->msgmap)))
				 ? ps_global->first_unseen
				 : first_sorted_flagged(F_UNSEEN|F_UNDEL,m,0));
		}

		break;

	      case IS_FIRST_RECENT:
		if(IS_NEWS(ps_global->mail_stream)
		   && F_OFF(F_FAKE_NEW_IN_NEWS, ps_global)){
		    mn_set_cur(ps_global->msgmap,
			       first_sorted_flagged(F_UNSEEN|F_UNDEL, m, 0));
		}
		else{
		    mn_set_cur(ps_global->msgmap,
			       first_sorted_flagged(F_RECENT|F_UNDEL, m, 0));
		}
		break;

	      case IS_FIRST:
		{
		    long n = mn_m2raw(ps_global->msgmap, 1L);

		    mail_fetchflags(m, long2string(n));
		    mn_set_cur(ps_global->msgmap,
			       (mail_elt(m, n)->deleted)
				 ? first_sorted_flagged(F_UNDEL, m, 0) : 1L);
		}

		break;

	      case IS_LAST:
		{
		    long n = mn_m2raw(ps_global->msgmap,
				      mn_get_total(ps_global->msgmap));

		    mail_fetchflags(m, long2string(n));
		    mn_set_cur(ps_global->msgmap,
			       (mail_elt(m, n)->deleted)
				 ? first_sorted_flagged(F_UNDEL, m, 1)
				 : mn_get_total(ps_global->msgmap));
		}

		break;

	      default:
		panic("Unexpected incoming startup case");
		break;

	    }
	}
	/* if faking new in news, goto first recent, else unseen */
	else if(IS_NEWS(ps_global->mail_stream)){
	    if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
		mn_set_cur(ps_global->msgmap,
			   first_sorted_flagged(F_RECENT|F_UNDEL, m, 0));
	    }
	    else{
		mn_set_cur(ps_global->msgmap,
			   first_sorted_flagged(F_UNSEEN|F_UNDEL, m, 0));
	    }
	}
        else{
	    mn_set_cur(ps_global->msgmap,
		       mn_get_revsort(ps_global->msgmap)
		         ? 1L
			 : mn_get_total(ps_global->msgmap));
	}
    }
    else{
	mn_set_cur(ps_global->msgmap, -1L);
    }

    return(1);
}



/*----------------------------------------------------------------------
    Open the requested folder in the requested context

    Args: state -- usual pine state struct
	  newfolder -- folder to open
	  new_context -- folder context might live in

   Result: New folder open or not (if error), and we're set to
	   enter the index screen.
 ----*/
void
visit_folder(state, newfolder, new_context)
    struct pine *state;
    char	*newfolder;
    CONTEXT_S	*new_context;
{
    dprint(9, (debugfile, "do_broach_folder (%s, %s)\n",
	       newfolder, new_context ? new_context->context : "(NULL)"));
    if(do_broach_folder(newfolder, new_context) > 0
       || state->mail_stream != state->inbox_stream)
      state->next_screen = mail_index_screen;
}



/*----------------------------------------------------------------------
      Expunge (if confirmed) and close a mail stream

    Args: stream   -- The MAILSTREAM * to close
	  context  -- The contest to interpret the following name in
          folder   -- The name the folder is know by 
        final_msg  -- If non-null, this should be set to point to a
		      message to print out in the caller, it is allocated
		      here and freed by the caller.

   Result:  Mail box is expunged and closed. A message is displayed to
             say what happened
 ----*/
void
expunge_and_close(stream, context, folder, final_msg)
    MAILSTREAM *stream;
    CONTEXT_S  *context;
    char       *folder;
    char      **final_msg;
{
    long  i, delete_count, max_folder, seen_not_del;
    char  prompt_b[MAX_SCREEN_COLS+1], short_folder_name[MAX_SCREEN_COLS+1],
          temp[MAILTMPLEN+1], buff1[MAX_SCREEN_COLS+1], *moved_msg = NULL,
	  buff2[MAX_SCREEN_COLS+1];
    struct variable *vars = ps_global->vars;
    int ret, expunge = FALSE;
    char ing[4];

    if(final_msg)
      strcpy(ing, "ed");
    else
      strcpy(ing, "ing");

    buff1[0] = '\0';
    buff2[0] = '\0';
    if(stream != NULL){
        dprint(2, (debugfile, "expunge and close mail stream \"%s\"\n",
                   stream->mailbox));
        if(!stream->rdonly){

            q_status_message1(SM_INFO, 0, 1, "Closing \"%s\"...", folder);
	    flush_status_messages(1);

	    /*
	     * Be sure to expunge any excluded (filtered) msgs
	     * Do it here so they're not copyied into read/archived
	     * folders *AND* to be sure we don't refilter them
	     * next time the folder's opened.
	     */
	    for(i = 1L; i <= stream->nmsgs; i++)
	      if(get_lflag(stream, NULL, i, MN_EXLD)){
		  delete_filtered_msgs(stream);
		  expunge = TRUE;
		  break;
	      }

	    /* Save read messages? */
	    if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
	       && stream == ps_global->inbox_stream
	       && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){

		if(F_ON(F_AUTO_READ_MSGS,ps_global)
		   || read_msg_prompt(seen_not_del, VAR_READ_MESSAGE_FOLDER))
		  /* move inbox's read messages */
		  moved_msg = move_read_msgs(stream, VAR_READ_MESSAGE_FOLDER,
					     buff1, -1L);
	    }
	    else if(VAR_ARCHIVED_FOLDERS)
	      moved_msg = move_read_incoming(stream, context, folder,
					     VAR_ARCHIVED_FOLDERS,
					     buff1);

            delete_count = count_flagged(stream, F_DEL);
	    ret = 'n';
	    if(delete_count){
		max_folder = ps_global->ttyo->screen_cols - 50;
		strcpy(temp, pretty_fn(folder));
		if(strlen(temp) > max_folder){
		    strcpy(short_folder_name, "...");
		    strcat(short_folder_name,temp + strlen(temp) -max_folder);
		}
		else
		  strcpy(short_folder_name, temp);

		if(F_ON(F_FULL_AUTO_EXPUNGE,ps_global)
		   || (F_ON(F_AUTO_EXPUNGE, ps_global)
		       && ((!strucmp(folder,ps_global->inbox_name))
			   || (context && (context->use & CNTXT_INCMNG)))
		       && context_isambig(folder))){
		    ret = 'y';
		}
		else{
		    sprintf(prompt_b,
			    "Expunge the %ld deleted message%s from \"%s\"",
			    delete_count,
			    delete_count == 1 ? "" : "s",
			    short_folder_name);
		    ret = want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM);
		}

		/* get this message back in queue */
		if(moved_msg)
		  q_status_message(SM_ORDER,
		      F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);

		if(ret == 'y'){
		    sprintf(buff2,
		      "Clos%s \"%.30s\". %s %s message%s and delet%s %s.",
			ing,
	 		pretty_fn(folder),
			final_msg ? "Kept" : "Keeping",
			comatose((stream->nmsgs - delete_count)),
			plural(stream->nmsgs - delete_count),
			ing,
			long2string(delete_count));
		    if(final_msg)
		      *final_msg = cpystr(buff2);
		    else
		      q_status_message(SM_ORDER,
				       (F_ON(F_AUTO_EXPUNGE,ps_global)
					|| F_ON(F_FULL_AUTO_EXPUNGE,ps_global))
					    ? 0 : 3,
				       5, buff2);
		      
		    flush_status_messages(1);
		    ps_global->mm_log_error = 0;
		    ps_global->expunge_in_progress = 1;
		    mail_expunge(stream);
		    ps_global->expunge_in_progress = 0;
		    if(ps_global->mm_log_error && final_msg && *final_msg){
			fs_give((void **)final_msg);
			*final_msg = NULL;
		    }
		}
	    }

	    if(ret != 'y'){
		if(expunge){
		    MESSAGECACHE *mc;
		    char	 *seq;
		    int		  expbits;

		    /*
		     * filtered message monkey business.
		     * The Plan:
		     *   1) light sequence bits for legit deleted msgs
		     *      and store marker in local extension
		     *   2) clear their deleted flag
		     *   3) perform expunge to removed filtered msgs
		     *   4) restore deleted flags for legit msgs
		     *      based on local extension bit
		     */
		    for(i = 1L; i <= stream->nmsgs; i++)
		      if(!get_lflag(stream, NULL, i, MN_EXLD)
			 && (((mc = mail_elt(stream, i))->valid && mc->deleted)
			     || (!mc->valid && mc->searched))){
			  mc->sequence = 1;
			  expbits      = MSG_EX_DELETE;
			  msgno_exceptions(stream, i, "0", &expbits, TRUE);
		      }
		      else
			mail_elt(stream, i)->sequence = 0;

		    if(seq = build_sequence(stream, NULL, NULL)){
			mail_flag(stream, seq, "\\DELETED", ST_SILENT);
			fs_give((void **) &seq);
		    }

		    ps_global->mm_log_error = 0;
		    ps_global->expunge_in_progress = 1;
		    mail_expunge(stream);
		    ps_global->expunge_in_progress = 0;

		    for(i = 1L; i <= stream->nmsgs; i++)
		      mail_elt(stream, i)->sequence 
			   = (msgno_exceptions(stream, i, "0", &expbits, FALSE)
			      && (expbits & MSG_EX_DELETE));

		    if(seq = build_sequence(stream, NULL, NULL)){
			mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
			fs_give((void **) &seq);
		    }
		}

		if(stream->nmsgs){
		    sprintf(buff2,
		        "Clos%s folder \"%s\". %s%s%s message%s.",
			ing,
			pretty_fn(folder), 
			final_msg ? "Kept" : "Keeping",
			(stream->nmsgs == 1L) ? " single" : " all ",
			(stream->nmsgs > 1L)
			  ? comatose(stream->nmsgs) : "",
			plural(stream->nmsgs));
		}
		else{
		    sprintf(buff2, "Clos%s empty folder \"%s\"",
			ing, pretty_fn(folder));
		}

		if(final_msg)
		  *final_msg = cpystr(buff2);
		else
		  q_status_message(SM_ORDER, 0, 3, buff2);
	    }
        }
	else{
            if(IS_NEWS(stream)){
		/* first, look to archive read messages */
		if(moved_msg = move_read_incoming(stream, context, folder,
						  VAR_ARCHIVED_FOLDERS,
						  buff1))
		  q_status_message(SM_ORDER,
		      F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);

		sprintf(buff2, "Clos%s news group \"%s\"",
			ing, pretty_fn(folder));

		if(F_ON(F_NEWS_CATCHUP, ps_global)){
		    MESSAGECACHE *mc;

		    /* count visible messages */
		    (void) count_flagged(stream, F_DEL);
		    for(i = 1L, delete_count = 0L; i <= stream->nmsgs; i++)
		      if(!(get_lflag(stream, NULL, i, MN_EXLD)
			   || ((mc = mail_elt(stream, i))->valid
				&& mc->deleted)
			   || (!mc->valid && mc->searched)))
			delete_count++;

		    if(delete_count){
			sprintf(prompt_b,
				"Delete %s%ld message%s from \"%s\"",
				(delete_count > 1L) ? "all " : "",
				delete_count, plural(delete_count),
				pretty_fn(folder));
			if(want_to(prompt_b, 'y', 0,
				   NO_HELP, WT_NORM) == 'y'){
			    char seq[64];

			    sprintf(seq, "1:%ld", stream->nmsgs);
			    mail_flag(stream, seq,
				      "\\DELETED", ST_SET|ST_SILENT);
			}
		    }
		}

		if(F_ON(F_NEWS_CROSS_DELETE, ps_global))
		  cross_delete_crossposts(stream);
	    }
            else
	      sprintf(buff2,
			"Clos%s read-only folder \"%s\". No changes to save",
			ing, pretty_fn(folder));

	    if(final_msg)
	      *final_msg = cpystr(buff2);
	    else
	      q_status_message(SM_ORDER, 0, 2, buff2);
        }

	/*
	 * Make darn sure any mm_log fallout caused above get's seen...
	 */
	flush_status_messages(1);

	pine_close_stream(stream);
    }
}



/*----------------------------------------------------------------------
  Dispatch messages matching FILTER patterns.

  Args:
	stream -- mail stream serving messages
	msgmap -- sequence to msgno mapping table
	recent -- number of recent messages to check

 When we're done, any filtered messages are filtered and the message
 mapping table has any filtered messages removed.
  ---*/
void
process_filter_patterns(stream, msgmap, recent)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    long	recent;
{
    long	  i, n, raw;
    unsigned long uid;
    int		  exbits;
    char	 *charset = NULL;
    PAT_S        *pat;
    SEARCHPGM	 *pgm = NULL;
    SEARCHSET	 *srchset = NULL;
    long          flags = (SE_NOPREFETCH|SE_FREE);
    PAT_STATE     pstate;

    dprint(2, (debugfile, "process_filter_patterns\n"));

    if(stream && stream->nmsgs && nonempty_patterns()){

	uid = mail_uid(stream, stream->nmsgs);

	/*
	 * Some of the search stuff won't work on old servers so we
	 * get the data and search locally. Big performance hit.
	 */
	if(is_imap_stream(stream) && !modern_imap_stream(stream))
	  flags |= SO_NOSERVER;
	else if(stream->dtb && !strcmp(stream->dtb->name, "nntp"))
	  flags |= SO_OVERVIEW;

	/* Then start searching */
	for(pat = first_pattern(ROLE_DO_FILTER, &pstate);
	    pat;
	    pat = next_pattern(ROLE_DO_FILTER, &pstate)){
	    if(!trivial_patgrp(pat->patgrp)
	       && match_pattern_folder(pat->patgrp, stream)
	       && !match_pattern_folder_specific(pat->action->folder,
						 stream, 0)){
		/*
		 * ignore all previously filtered messages
		 * and, if requested, anything not a recent
		 * arrival...
		 */
		for(i = 1; i <= stream->nmsgs; i++)
		  if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
		      if(exbits & MSG_EX_FILTERED)
			mail_elt(stream, i)->sequence = 0;
		      else if(!recent || (exbits & MSG_EX_RECENT))
			mail_elt(stream, i)->sequence = 1;
		      else
			mail_elt(stream, i)->sequence = 0;
		  }
		  else 
		    mail_elt(stream, i)->sequence = !recent;

		if(!(srchset = build_searchset(stream)))
		  continue;		/* nothing to search, move on */

		pgm = match_pattern_srchpgm(pat->patgrp, stream,
					    &charset, srchset);

		mail_search_full(stream, NULL, pgm, flags);

		/* New mail arrival means start over from first filter */
		if(mail_uid(stream, stream->nmsgs) != uid){
		    mail_free_searchset(&srchset);
		    process_filter_patterns(stream, msgmap, recent);
		    return;
		}

		/* check scores */
		if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS &&
		   pat->patgrp->do_score){
		    SEARCHSET *s, *ss;

		    /*
		     * Build a searchset so we can get all the scores we
		     * need and only the scores we need efficiently.
		     */

		    for(i = 1; i <= stream->nmsgs; i++)
		      mail_elt(stream, i)->sequence = 0;

		    for(s = srchset; s; s = s->next)
		      for(i = s->first; i <= s->last; i++)
			if(mail_elt(stream, i)->searched &&
			   get_msg_score(stream, i) == SCORE_UNDEF)
			  mail_elt(stream, i)->sequence = 1;

		    if((ss = build_searchset(stream)) != NULL){
			calculate_some_scores(stream, ss);
			mail_free_searchset(&ss);
		    }

		    /*
		     * Now check the patterns which have matched so far
		     * to see if their score is in the score interval.
		     */
		    for(s = srchset; s; s = s->next)
		      for(i = s->first; i <= s->last; i++)
			if(mail_elt(stream, i)->searched){
			    int score;

			    score = get_msg_score(stream, i);

			    /*
			     * If the score is outside the interval,
			     * turn off the searched bit.
			     */
			    if(score != SCORE_UNDEF &&
			       (score < pat->patgrp->score_min ||
			        score > pat->patgrp->score_max))
			      mail_elt(stream, i)->searched = NIL;
			}
		}

		for(i = 1L, n = 0L; i <= msgmap->max_msgno; )
		  if((raw = mn_m2raw(msgmap, i))
		     && (mail_elt(stream, raw))->searched){
		      dprint(2, (debugfile, "FILTER matching \"%s\": %s\n",
			    pat->patgrp->nick ? pat->patgrp->nick : "unnamed",
			    pat->action->folder ? "filed" : "killed"));
		      msgno_exclude(stream, msgmap, i);
		      if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
			exbits |= MSG_EX_FILTERED;
		      else
			exbits = MSG_EX_FILTERED;

		      msgno_exceptions(stream, raw, "0", &exbits, TRUE);
		      n++;
		  }
		  else
		    i++;

		if(n && pat->action->folder){
		    MSGNO_S   *tmpmap = NULL;
		    PATTERN_S *p;
		    int	       err = 0;

		    mn_init(&tmpmap, stream->nmsgs);

		    /*
		     * For everything matching msg that hasn't
		     * already been saved somewhere, do it...
		     */
		    for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
		      if(mail_elt(stream, i)->searched
			 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
			      && (exbits & MSG_EX_FILED)))
			if(!n++){
			    mn_set_cur(tmpmap, i);
			}
			else{
			    mn_add_cur(tmpmap, i);
			}

		    if(n){
			for(p = pat->action->folder; p; p = p->next)
			  if(move_filtered_msgs(stream,tmpmap,p->substring)){

			      /*
			       * If we filtered into the current
			       * folder, chuck a ping down the
			       * stream so the user can notice it
			       * before the next new mail check...
			       */
			      if(ps_global->mail_stream
				 && ps_global->mail_stream != stream
				 && ps_global->inbox_stream
				 && ps_global->mail_stream
						   != ps_global->inbox_stream
				 && match_pattern_folder_specific(
				     pat->action->folder,
				     ps_global->mail_stream, 0)){
				  mail_ping(ps_global->mail_stream);
			      }				
			  }
			  else{
			      err = 1;
			      break;
			  }

			if(!err)
			  for(n = mn_first_cur(tmpmap);
			      n > 0L;
			      n = mn_next_cur(tmpmap)){

			      if(msgno_exceptions(stream, mn_m2raw(tmpmap, n),
						  "0", &exbits, FALSE))
				exbits |= MSG_EX_FILED;
			      else
				exbits = MSG_EX_FILED;

			      msgno_exceptions(stream, mn_m2raw(tmpmap, n),
					       "0", &exbits, TRUE);
			  }
		    }

		    mn_give(&tmpmap);
		}

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

		/* New mail arrival means start over from first filter */
		if(mail_uid(stream, stream->nmsgs) != uid){
		    mail_free_searchset(&srchset);
		    process_filter_patterns(stream, msgmap, recent);
		    return;
		}
	    }
	}
    }

    /* clear any private "recent" flags */
    for(i = 1; i <= stream->nmsgs; i++)
      if(msgno_exceptions(stream, i, "0", &exbits, FALSE))
	if(exbits & MSG_EX_RECENT){
	    exbits ^= MSG_EX_RECENT;
	    msgno_exceptions(stream, i, "0", &exbits,TRUE);
	}
}


/*
 * When killing or filtering we don't want to match by mistake. So if
 * a pattern has nothing set except the Current Folder Type (which is always
 * set to something) we'll consider it to be trivial and not a match.
 * This is the opposite of match_pattern which considers this to be a
 * match.
 */
int
trivial_patgrp(patgrp)
    PATGRP_S *patgrp;
{
    int ret = 1;

    if(patgrp){
	if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to ||
	   patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic ||
	   patgrp->alltext)
	  ret = 0;
	else if(patgrp->arbhdr){
	    ARBHDR_S *a;

	    for(a = patgrp->arbhdr; a && ret; a = a->next)
	      if(a->field && a->field[0] && a->p)
		ret = 0;
	}
    }

    return(ret);
}



/*----------------------------------------------------------------------
  Move all messages with sequence bit lit to dstfldr
 
  Args: stream -- stream to usr
	dstfldr -- folder to receive moved messages
	buf -- place to write success message

  Returns: nonzeron on success
  ----*/
int
move_filtered_msgs(stream, msgmap, dstfldr)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    char       *dstfldr;
{
    long	  n;
    int           we_cancel = 0, i;
    CONTEXT_S	 *save_context = NULL;
    char	  buf[MAX_SCREEN_COLS+1], *ellipsis, *dstfldrp;
#define	FILTMSG_MAX	30

    if(!is_absolute_path(dstfldr)
       && !(save_context = default_save_context(ps_global->context_list)))
      save_context = ps_global->context_list;

    if((i = strlen(dstfldr)) > FILTMSG_MAX){
	dstfldrp = dstfldr + (i - FILTMSG_MAX);
	ellipsis = "...";
    }
    else{
	dstfldrp = dstfldr;
	ellipsis = "";
    }

    sprintf(buf, "Moving %s filtered message%s to \"%s%s\"",
	    comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)),
	    ellipsis, dstfldrp);

    we_cancel = busy_alarm(1, buf, NULL, 1);

    n = save(ps_global, stream, save_context, dstfldr, msgmap, 1);
    if(n != mn_total_cur(msgmap)){
	int   exbits;
	long  x;

	q_status_message2(SM_ORDER | SM_DING, 4, 6,
			  "Error!  Some messages not moved to %s%s", 
			  ellipsis, dstfldrp);
	buf[0] = '\0';

	/* Clear "filtered" flags for failed messages */
	for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap))
	  if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x),
					  "0", &exbits, FALSE)){
	      exbits ^= MSG_EX_FILTERED;
	      msgno_exceptions(stream, mn_m2raw(msgmap, x),
			       "0", &exbits, TRUE);
	  }

	/* then re-incorporate them into folder they belong */
	msgno_include(stream,
		      (stream == ps_global->mail_stream)
			? ps_global->msgmap
			: (stream == ps_global->inbox_stream)
			    ? ps_global->inbox_msgmap : msgmap,
		      FALSE);
	clear_index_cache();
	refresh_sort(msgmap, FALSE);
	ps_global->mangled_header = 1;
    }
    else
      sprintf(buf, "Filtered all %s message%s to \"%.45s\"",
	      comatose(n), plural(n), dstfldr);

    if(we_cancel)
      cancel_busy_alarm(buf[0] ? 0 : -1);

    return(buf[0] != '\0');
}



void
delete_filtered_msgs(stream)
    MAILSTREAM *stream;
{
    int	  exbits;
    long  i;
    char *seq;

    for(i = 1L; i <= stream->nmsgs; i++)
      if(msgno_exceptions(stream, i, "0", &exbits, FALSE)
	 && (exbits & MSG_EX_FILTERED))
	mail_elt(stream, i)->sequence = 1;
      else
	mail_elt(stream, i)->sequence = 0;

    if(seq = build_sequence(stream, NULL, NULL)){
	mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT);
	fs_give((void **) &seq);
    }
}



/*----------------------------------------------------------------------
  Move all read messages from srcfldr to dstfldr
 
  Args: stream -- stream to usr
	dstfldr -- folder to receive moved messages
	buf -- place to write success message

  Returns: success message or NULL for failure
  ----*/
char *
move_read_msgs(stream, dstfldr, buf, searched)
    MAILSTREAM *stream;
    char       *dstfldr, *buf;
    long	searched;
{
    long	  i;
    int           we_cancel = 0;
    MSGNO_S	 *msgmap = NULL;
    CONTEXT_S	 *save_context = NULL;
    char	  *bufp = NULL;
    MESSAGECACHE *mc;

    if(!is_absolute_path(dstfldr)
       && !(save_context = default_save_context(ps_global->context_list)))
      save_context = ps_global->context_list;

    /*
     * Use the "sequence" bit to select the set of messages
     * we want to save.  If searched is non-neg, the message
     * cache already has the necessary "sequence" bits set.
     */
    if(searched < 0L)
      searched = count_flagged(stream, F_SEEN | F_UNDEL);

    if(searched){
	mn_init(&msgmap, stream->nmsgs);
	for(i = 1L; i <= mn_get_total(msgmap); i++)
	  set_lflag(stream, msgmap, i, MN_SLCT, 0);

	/*
	 * re-init msgmap to fix the MN_SLCT count, "flagged_tmp", in
	 * case there were any flagged such before we got here.
	 *
	 * BUG: this means the count of MN_SLCT'd msgs in the
	 * folder's real msgmap is instantly bogus.  Until Cancel
	 * after "Really quit?" is allowed, this isn't a problem since
	 * that mapping table is either gone or about to get nuked...
	 */
	mn_init(&msgmap, stream->nmsgs);

	/* select search results */
	for(i = 1L; i <= mn_get_total(msgmap); i++)
	  if(((mc = mail_elt(stream,mn_m2raw(msgmap, i)))->valid
	      && mc->seen && !mc->deleted)
	     || (!mc->valid && mc->searched))
	    set_lflag(stream, msgmap, i, MN_SLCT, 1);

	pseudo_selected(msgmap);
	sprintf(buf, "Moving %s read message%s to \"%.45s\"",
		comatose(searched), plural(searched), dstfldr);
	we_cancel = busy_alarm(1, buf, NULL, 1);
	if(save(ps_global, stream,
		save_context, dstfldr, msgmap, 1) != searched)
	  q_status_message1(SM_ORDER | SM_DING, 4, 6,
			    "Error saving to %.35s.  Not all messages moved.",
			    dstfldr);
	else
	  strncpy(bufp = buf + 1, "Moved", 5); /* change Moving to Moved */

	mn_give(&msgmap);
	if(we_cancel)
	  cancel_busy_alarm(bufp ? 0 : -1);
    }

    return(bufp);
}



/*----------------------------------------------------------------------
  Move read messages from folder if listed in archive
 
  Args: 

  ----*/
int
read_msg_prompt(n, f)
    long  n;
    char *f;
{
    char buf[MAX_SCREEN_COLS+1];

    sprintf(buf, "Save the %ld read message%s in \"%s\"", n, plural(n), f);
    return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
}



/*----------------------------------------------------------------------
  Move read messages from folder if listed in archive
 
  Args: 

  ----*/
char *
move_read_incoming(stream, context, folder, archive, buf)
    MAILSTREAM *stream;
    CONTEXT_S  *context;
    char       *folder;
    char      **archive;
    char       *buf;
{
    char *s, *d, *f = folder;
    long  seen_undel;

    buf[0] = '\0';

    if(archive && stream != ps_global->inbox_stream
       && context && (context->use & CNTXT_INCMNG)
       && ((context_isambig(folder)
	    && folder_is_nick(folder, FOLDERS(context)))
	   || folder_index(folder, context, FI_FOLDER) > 0)
       && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){

	for(; f && *archive; archive++){
	    char *p;

	    get_pair(*archive, &s, &d, 1);
	    if(s && d
	       && (!strcmp(s, folder)
		   || (context_isambig(folder)
		       && (p = folder_is_nick(folder, FOLDERS(context)))
		       && !strcmp(s, p)))){
		if(F_ON(F_AUTO_READ_MSGS,ps_global)
		   || read_msg_prompt(seen_undel, d))
		  buf = move_read_msgs(stream, d, buf, seen_undel);

		f = NULL;		/* bust out after cleaning up */
	    }

	    fs_give((void **)&s);
	    fs_give((void **)&d);
	}
    }

    return((buf && *buf) ? buf : NULL);
}


/*----------------------------------------------------------------------
    Delete all references to a deleted news posting

 
  ---*/
void
cross_delete_crossposts(stream)
    MAILSTREAM *stream;
{
    if(count_flagged(stream, F_DEL)){
	static char *fields[] = {"Xref", NULL};
	MAILSTREAM  *tstream;
	CONTEXT_S   *fake_context;
	char	    *xref, *p, *group, *uidp,
		    *newgrp, newfolder[MAILTMPLEN];
	long	     i, uid, hostlatch = 0L;
	int	     we_cancel = 0;

	if(!(newgrp = strstr(strcpy(newfolder, stream->mailbox),
					 "#news.")))
	  return;				/* weird mailbox */

	newgrp += 6;

	we_cancel = busy_alarm(1, "Busy deleting crosspostings", NULL, 0);

	/* build subscribed list */
	strcpy(newgrp, "[]");
	fake_context = new_context(newfolder, 0);
	build_folder_list(NULL, fake_context, "*", NULL, BFL_LSUB);

	for(i = 1L; i <= stream->nmsgs; i++)
	  if(!get_lflag(stream, NULL, i, MN_EXLD)
	     && mail_elt(stream, i)->deleted){

	      if(xref = pine_fetchheader_lines(stream, i, NULL, fields)){
		  if(p = strstr(xref, ": ")){
		      p	     += 2;
		      while(*p){
			  group = p;
			  uidp  = NULL;

			  /* get server */
			  while(*++p && !isspace((unsigned char) *p))
			    if(*p == ':'){
				*p   = '\0';
				uidp = p + 1;
			    }

			  /* tie off uid/host */
			  if(*p)
			    *p++ = '\0';

			  if(uidp){
			      /*
			       * For the nonce, we're only deleting valid
			       * uid's from outside the current newsgroup
			       * and inside only subscribed newsgroups
			       */
			      if(strcmp(group, stream->mailbox
							+ (newgrp - newfolder))
				 && folder_index(group, fake_context,
						 FI_FOLDER) >= 0){
				  if(uid = atol(uidp)){
				      strcpy(newgrp, group);
				      if(tstream = pine_mail_open(NULL,
							     newfolder, 0L)){
					  mail_flag(tstream, long2string(uid),
						    "\\DELETED",
						    ST_SET | ST_UID);
					  mail_close(tstream);
				      }
				  }
				  else
				    break;		/* bogus uid */
			      }
			  }
			  else if(!hostlatch++){
			      char *p, *q;

			      if(stream->mailbox[0] == '{'
				 && !((p = strpbrk(stream->mailbox+1, "}:/"))
				      && !struncmp(stream->mailbox + 1,
						   q = canonical_name(group),
						   p - (stream->mailbox + 1))
				      && q[p - (stream->mailbox + 1)] == '\0'))
				break;		/* different server? */
			  }
			  else
			    break;		/* bogus field! */
		      }
		  }
		  
		  fs_give((void **) &xref);
	      }
	  }

	free_context(&fake_context);

	if(we_cancel)
	  cancel_busy_alarm(0);
    }
}



/*----------------------------------------------------------------------
    Print current message[s] or folder index

    Args: state -- pointer to struct holding a bunch of pine state
	 msgmap -- table mapping msg nums to c-client sequence nums
	    agg -- boolean indicating we're to operate on aggregate set
       in_index -- boolean indicating we're called from Index Screen

 Filters the original header and sends stuff to printer
  ---*/
void
cmd_print(state, msgmap, agg, in_index)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg, in_index;
{
    char      prompt[250];
    long      i, msgs;
    int	      next = 0, do_index = 0;
    ENVELOPE *e;
    BODY     *b;

    if(agg && !pseudo_selected(msgmap))
      return;

    msgs = mn_total_cur(msgmap);

    if(in_index && F_ON(F_PRINT_INDEX, state)){
	char m[10];
	static ESCKEY_S prt_opts[] = {
	    {'i', 'i', "I", "Index"},
	    {'m', 'm', "M", NULL},
	    {-1, 0, NULL, NULL}};

	sprintf(m, "Message%s", (msgs>1L) ? "s" : "");
	prt_opts[1].label = m;
	sprintf(prompt, "Print %sFolder Index or %s %s? ",
	    agg ? "selected " : "", agg ? "selected" : "current", m);
	switch(radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
			     NO_HELP, RB_NORM|RB_SEQ_SENSITIVE)){
	  case 'x' :
	    cmd_cancelled("Print");
	    if(agg)
	      restore_selected(msgmap);

	    return;

	  case 'i':
	    do_index = 1;
	    break;

	  default :
	  case 'm':
	    break;
	}
    }

    if(do_index)
      sprintf(prompt, "%sFolder Index ", agg ? "Selected " : "");
    else if(msgs > 1L)
      sprintf(prompt, "%s messages ", long2string(msgs));
    else
      sprintf(prompt, "Message %s ", long2string(mn_get_cur(msgmap)));

    if(open_printer(prompt) < 0){
	if(agg)
	  restore_selected(msgmap);

	return;
    }
    
    if(do_index){
	/* Print titlebar... */
	print_text1("%s\n\n", format_titlebar(NULL));
	/* then all the index members... */
	if(!print_index(state, msgmap, agg))
	  q_status_message(SM_ORDER | SM_DING, 3, 3,
			   "Error printing folder index");
    }
    else{
        for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
	    if(next && F_ON(F_AGG_PRINT_FF, state))
	      if(!print_char(FORMFEED))
	        break;

	    if(!(e=mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap,i),
				       &b))
	       || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
		   && !bezerk_delimiter(e, print_char, next))
	       || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
				  e, b, FM_NEW_MESS, print_char)){
	        q_status_message(SM_ORDER | SM_DING, 3, 3,
			       "Error printing message");
	        break;
	    }
        }
    }

    close_printer();

    if(agg)
      restore_selected(msgmap);
}



/*
 * Support structure and functions to support piping raw message texts...
 */
static struct raw_pipe_data {
    MAILSTREAM *stream;
    long	msgno;
    char       *cur, *body;
} raw_pipe;


int
raw_pipe_getc(c)
     unsigned char *c;
{
    if((!raw_pipe.cur
	&& !(raw_pipe.cur = mail_fetchheader(raw_pipe.stream, raw_pipe.msgno)))
       || (!*raw_pipe.cur && !raw_pipe.body
	   && !(raw_pipe.cur = raw_pipe.body = mail_fetchtext(raw_pipe.stream,
							      raw_pipe.msgno)))
       || (!*raw_pipe.cur && raw_pipe.body))
      return(0);

    *c = (unsigned char) *raw_pipe.cur++;
    return(1);
}


void
prime_raw_text_getc(stream, msgno)
    MAILSTREAM *stream;
    long	msgno;
{
    raw_pipe.stream = stream;
    raw_pipe.msgno  = msgno;
    raw_pipe.cur    = raw_pipe.body = NULL;
}



/*----------------------------------------------------------------------
    Pipe message text

   Args: state -- various pine state bits
	 msgmap -- Message number mapping table
	 agg -- whether or not to aggregate the command on selected msgs

   Filters the original header and sends stuff to specified command
  ---*/
void
cmd_pipe(state, msgmap, agg)
     struct pine *state;
     MSGNO_S *msgmap;
     int	  agg;
{
    ENVELOPE      *e;
    BODY	  *b;
    PIPE_S	  *syspipe;
    char          *resultfilename = NULL, prompt[80];
    int            done = 0, flags;
    gf_io_t	   pc;
    int		   next = 0;
    long           i;
    HelpType       help;
    static	   capture = 1, raw = 0, delimit = 0, newpipe = 0;
    static char    pipe_command[MAXPATH+1];
    static ESCKEY_S pipe_opt[] = {
	{0, 0, "", ""},
	{ctrl('W'), 10, "^W", NULL},
	{ctrl('Y'), 11, "^Y", NULL},
	{ctrl('R'), 12, "^R", NULL},
	{0, 13, "^T", NULL},
	{-1, 0, NULL, NULL}
    };

    if(ps_global->restricted){
	q_status_message(SM_ORDER | SM_DING, 0, 4,
			 "Pine demo can't pipe messages");
	return;
    }
    else if(!any_messages(msgmap, NULL, "to Pipe"))
      return;

    if(agg){
	if(!pseudo_selected(msgmap))
	  return;
	else
	  pipe_opt[4].ch = ctrl('T');
    }
    else
      pipe_opt[4].ch = -1;

    help = NO_HELP;
    while (!done) {
	int flags;

	sprintf(prompt, "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
		raw ? "RAW " : "",
		agg ? "s" : " ",
		agg ? "" : comatose(mn_get_cur(msgmap)),
		(!capture || delimit || (newpipe && agg)) ? "(" : "",
		capture ? "" : "uncaptured",
		(!capture && delimit) ? "," : "",
		delimit ? "delimited" : "",
		((!capture || delimit) && newpipe && agg) ? "," : "",
		(newpipe && agg) ? "new pipe" : "",
		(!capture || delimit || (newpipe && agg)) ? ") " : "");
	pipe_opt[1].label = raw ? "Shown Text" : "Raw Text";
	pipe_opt[2].label = capture ? "Free Output" : "Capture Output";
	pipe_opt[3].label = delimit ? "No Delimiter" : "With Delimiter";
	pipe_opt[4].label = newpipe ? "To Same Pipe" : "To Individual Pipes";
	flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
	switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
				MAXPATH, prompt, pipe_opt, help, &flags)){
	  case -1 :
	    q_status_message(SM_ORDER | SM_DING, 3, 4,
			     "Internal problem encountered");
	    done++;
	    break;
      
	  case 10 :			/* flip raw bit */
	    raw = !raw;
	    break;

	  case 11 :			/* flip capture bit */
	    capture = !capture;
	    break;

	  case 12 :			/* flip delimit bit */
	    delimit = !delimit;
	    break;

	  case 13 :			/* flip newpipe bit */
	    newpipe = !newpipe;
	    break;

	  case 0 :
	    if(pipe_command[0]){
		flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
		if(!capture){
#ifndef	_WINDOWS
		    ClearScreen();
		    fflush(stdout);
		    clear_cursor_pos();
		    ps_global->mangled_screen = 1;
		    ps_global->in_init_seq = 1;
#endif
		    flags |= PIPE_RESET;
		}

		if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
							 (flags & PIPE_RESET)
							   ? NULL
							   : &resultfilename,
							 flags, &pc)))
		  done++;

		for(i = mn_first_cur(msgmap);
		    i > 0L && !done;
		    i = mn_next_cur(msgmap)){
		    e = mail_fetchstructure(ps_global->mail_stream,
					    mn_m2raw(msgmap, i), &b);

		    if((newpipe
			&& !(syspipe = cmd_pipe_open(pipe_command,
						     (flags & PIPE_RESET)
						       ? NULL
						       : &resultfilename,
						     flags, &pc)))
		       || (delimit && !bezerk_delimiter(e, pc, next++)))
		      done++;

		    if(!done){
			if(raw){
			    char    *pipe_err;

			    prime_raw_text_getc(ps_global->mail_stream,
						mn_m2raw(msgmap, i));
			    gf_filter_init();
			    gf_link_filter(gf_nvtnl_local, NULL);
			    if(pipe_err = gf_pipe(raw_pipe_getc, pc)){
				q_status_message1(SM_ORDER|SM_DING,
						  3, 3,
						  "Internal Error: %s",
						  pipe_err);
				done++;
			    }
			}
			else if(!format_message(mn_m2raw(msgmap, i), e, b,
						FM_NEW_MESS | FM_NOWRAP, pc))
			  done++;
		    }

		    if(newpipe)
		      (void) close_system_pipe(&syspipe);
		}

		if(!capture)
		  ps_global->in_init_seq = 0;

		if(!newpipe)
		  (void) close_system_pipe(&syspipe);

		if(done)		/* say we had a problem */
		  q_status_message(SM_ORDER | SM_DING, 3, 3,
				   "Error piping message");
		else if(resultfilename){
		    /* only display if no error */
		    display_output_file(resultfilename, "PIPE MESSAGE",
					NULL, DOF_EMPTY);
		    fs_give((void **)&resultfilename);
		}
		else
		  q_status_message(SM_ORDER, 0, 2, "Pipe command completed");

		done++;
		break;
	    }
	    /* else fall thru as if cancelled */

	  case 1 :
	    cmd_cancelled("Pipe command");
	    done++;
	    break;

	  case 3 :
            help = (help == NO_HELP) ? h_pipe_msg : NO_HELP;
	    break;

	  case 2 :                              /* no place to escape to */
	  case 4 :                              /* can't suspend */
	  default :
	    break;   
	}
    }

    ps_global->mangled_footer = 1;
    if(agg)
      restore_selected(msgmap);
}



/*----------------------------------------------------------------------
  Actually open the pipe used to write piped data down

   Args: 
   Returns: TRUE if success, otherwise FALSE

  ----*/
PIPE_S *
cmd_pipe_open(cmd, result, flags, pc)
    char     *cmd;
    char    **result;
    int       flags;
    gf_io_t  *pc;
{
    PIPE_S *pipe;

    if(pipe = open_system_pipe(cmd, result, NULL, flags, 0))
      gf_set_writec(pc, pipe->out.f, 0L, FileStar);
    else
      q_status_message(SM_ORDER | SM_DING, 3, 3, "Error opening pipe") ;

    return(pipe);
}



/*----------------------------------------------------------------------
  Screen to offer list management commands contained in message

    Args: state -- pointer to struct holding a bunch of pine state
	 msgmap -- table mapping msg nums to c-client sequence nums
	    agg -- boolean indicating we're to operate on aggregate set

 Result: 

   NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
 ----*/
void
rfc2369_display(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S	*msgmap;
     long	 msgno;
{
    int	       winner = 0;
    char      *h, *hdrs[MLCMD_COUNT + 1];
    long       index_no = mn_raw2m(msgmap, msgno);
    RFC2369_S  data[MLCMD_COUNT];

    /* for each header field */
    if(h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))){
	memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
	if(rfc2369_parse_fields(h, &data[0])){
	    STORE_S *explain;

	    if(explain = list_mgmt_text(data, index_no)){
		list_mgmt_screen(explain);
		ps_global->mangled_screen = 1;
		so_give(&explain);
		winner++;
	    }
	}

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

    if(!winner)
      q_status_message1(SM_ORDER, 0, 3,
			"Message %s contains no list management information",
			comatose(index_no));
}


STORE_S *
list_mgmt_text(data, msgno)
    RFC2369_S *data;
    long       msgno;
{
    STORE_S	  *store;
    int		   i, j, n, fields = 0;
    static  char  *rfc2369_intro1 =
      "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
    static char *rfc2369_intro2[] = {
	" has information associated with it ",
	"that explains how to participate in an email list.  An ",
	"email list is represented by a single email address that ",
	"users sharing a common interest can send messages to (known ",
	"as posting) which are then redistributed to all members ",
	"of the list (sometimes after review by a moderator).",
	"<P>List participation commands in this message include:",
	NULL
    };

    if(store = so_get(CharStar, NULL, EDIT_ACCESS)){

	/* Insert introductory text */
	so_puts(store, rfc2369_intro1);

	so_puts(store, comatose(msgno));

	for(i = 0; rfc2369_intro2[i]; i++)
	  so_puts(store, rfc2369_intro2[i]);

	so_puts(store, "<P>");
	for(i = 0; i < MLCMD_COUNT; i++)
	  if(data[i].data[0].value
	     || data[i].data[0].comment
	     || data[i].data[0].error){
	      if(!fields++)
		so_puts(store, "<UL>");

	      so_puts(store, "<LI>");
	      so_puts(store,
		      (n = (data[i].data[1].value || data[i].data[1].comment))
			? "Methods to "
			: "A method to ");

	      so_puts(store, data[i].field.description);
	      so_puts(store, ". ");

	      if(n)
		so_puts(store, "<OL>");

	      for(j = 0;
		  j < MLCMD_MAXDATA
		  && (data[i].data[j].comment
		      || data[i].data[j].value
		      || data[i].data[j].error);
		  j++){

		  so_puts(store, n ? "<P><LI>" : "<P>");

		  if(data[i].data[j].comment){
		      so_puts(store,
			      "With the provided comment:<P><BLOCKQUOTE>");
		      so_puts(store, data[i].data[j].comment);
		      so_puts(store, "</BLOCKQUOTE><P>");
		  }

		  if(data[i].data[j].value){
		      if(i == MLCMD_POST
			 && !strucmp(data[i].data[j].value, "NO")){
			  so_puts(store,
			   "Posting is <EM>not</EM> allowed on this list");
		      }
		      else{
			  so_puts(store, "Select <A HREF=\"");
			  so_puts(store, data[i].data[j].value);
			  so_puts(store, "\">HERE</A> to ");
			  so_puts(store, (data[i].field.action)
					   ? data[i].field.action
					   : "try it");
		      }

		      so_puts(store, ".");
		  }

		  if(data[i].data[j].error){
		      so_puts(store, "<P>Unfortunately, Pine can not offer");
		      so_puts(store, " to take direct action based upon it");
		      so_puts(store, " because it was improperly formatted.");
		      so_puts(store, " The unrecognized data associated with");
		      so_puts(store, " the \"");
		      so_puts(store, data[i].field.name);
		      so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
		      so_puts(store, data[i].data[j].error);
		      so_puts(store, "</BLOCKQUOTE>");
		  }

		  so_puts(store, "<P>");
	      }

	      if(n)
		so_puts(store, "</OL>");
	  }

	if(fields)
	  so_puts(store, "</UL>");

	so_puts(store, "</BODY></HTML>");
    }

    return(store);
}



static struct key listmgr_keys[] =
       {HELP_MENU,
	NULL_MENU,
	{"E","Exit CmdList",{MC_EXIT,1,{'e'}},KS_EXITMODE},
	{"Ret","[Try Command]",{MC_VIEW_HANDLE,3,
				{ctrl('m'),ctrl('j'),KEY_RIGHT}},KS_NONE},
	{"^B","Prev Cmd",{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
	{"^F","Next Cmd",{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
	PREVPAGE_MENU,
	NEXTPAGE_MENU,
	PRYNTTXT_MENU,
	WHEREIS_MENU,
	NULL_MENU,
	NULL_MENU};
INST_KEY_MENU(listmgr_keymenu, listmgr_keys);
#define	LM_TRY_KEY	3
#define	LM_PREV_KEY	4
#define	LM_NEXT_KEY	5


void
list_mgmt_screen(html)
    STORE_S *html;
{
    int		    cmd = MC_NONE;
    long	    offset = 0L;
    char	   *error = NULL;
    STORE_S	   *store;
    HANDLE_S	   *handles = NULL;
    gf_io_t	    gc, pc;

    do{
	so_seek(html, 0L, 0);
	gf_set_so_readc(&gc, html);

	init_handles(&handles);

	if(store = so_get(CharStar, NULL, EDIT_ACCESS)){
	    gf_set_so_writec(&pc, store);
	    gf_filter_init();

	    gf_link_filter(gf_html2plain,
			   gf_html2plain_opt(NULL,
					     ps_global->ttyo->screen_cols,
					     GFHP_HANDLES));

	    error = gf_pipe(gc, pc);

	    gf_clear_so_writec(store);

	    if(!error){
		SCROLL_S	sargs;
		struct key_menu km;
		struct key	keys[24];

		memset(&sargs, 0, sizeof(SCROLL_S));
		sargs.text.text	   = so_text(store);
		sargs.text.src	   = CharStar;
		sargs.text.desc	   = "list commands";
		sargs.text.handles = handles;
		if(offset){
		    sargs.start.on	   = Offset;
		    sargs.start.loc.offset = offset;
		}

		sargs.bar.title	   = "MAIL LIST COMMANDS";
		sargs.bar.style	   = MessageNumber;
		sargs.resize_exit  = 1;
		sargs.help.text	   = h_special_list_commands;
		sargs.help.title   = "HELP FOR LIST COMMANDS";
		sargs.keys.menu	   = &listmgr_keymenu;
		setbitmap(sargs.keys.bitmap);
		if(!handles){
		    clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
		    clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
		    clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
		}

		cmd = scrolltool(&sargs);
		offset = sargs.start.loc.offset;
	    }

	    so_give(&store);
	}

	free_handles(&handles);
	gf_clear_so_readc(html);
    }
    while(cmd == MC_RESIZE);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort desired

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

   NOTE: any and all functions that successfully exit the second
	 switch() statement below (currently "select_*() functions"),
	 *MUST* update the folder's MESSAGECACHE element's "searched"
	 bits to reflect the search result.  Functions using
	 mail_search() get this for free, the others must update 'em
	 by hand.

  ----*/
void
aggregate_select(state, msgmap, q_line, in_index)
    struct pine *state;
    MSGNO_S     *msgmap;
    int	  q_line, in_index;
{
    long       i, diff, old_tot, msgno;
    int        q = 0, rv = 0, narrow = 0, hidden;
    HelpType   help = NO_HELP;
    ESCKEY_S  *sel_opts;
    extern     MAILSTREAM *mm_search_stream;
    extern     long	   mm_search_count;

    hidden           = any_lflagged(msgmap, MN_HIDE) > 0L;
    mm_search_stream = state->mail_stream;
    mm_search_count  = 0L;

    sel_opts = sel_opts2;
    if(old_tot = any_lflagged(msgmap, MN_SLCT)){
	i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap), MN_SLCT);
	sel_opts1[1].label = "unselect Cur" + (i ? 0 : 2);
	sel_opts += 2;			/* disable extra options */
	switch(q = radio_buttons(sel_pmt1, q_line, sel_opts1, 'c', 'x', help,
				 RB_NORM)){
	  case 'f' :			/* flip selection */
	    msgno = 0L;
	    for(i = 1L; i <= mn_get_total(msgmap); i++){
		q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
		set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
		if(hidden){
		    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
		    if(!msgno && q)
		      mn_reset_cur(msgmap, msgno = i);
		}
	    }

	    return;

	  case 'n' :			/* narrow selection */
	    narrow++;
	  case 'b' :			/* broaden selection */
	    q = 0;			/* but don't offer criteria prompt */
	    break;

	  case 'c' :			/* Un/Select Current */
	  case 'a' :			/* Unselect All */
	  case 'x' :			/* cancel */
	    break;

	  default :
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "Unsupported Select option");
	    return;
	}
    }

    if(!q)
      q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', help, RB_NORM);

    /*
     * NOTE: See note about MESSAGECACHE "searched" bits above!
     */
    switch(q){
      case 'x':				/* cancel */
	cmd_cancelled("Select command");
	return;

      case 'c' :			/* select/unselect current */
	(void) individual_select(state, msgmap, q_line, in_index);
	return;

      case 'a' :			/* select/unselect all */
	msgno = any_lflagged(msgmap, MN_SLCT);
	diff    = (!msgno) ? mn_get_total(msgmap) : 0L;

	for(i = 1L; i <= mn_get_total(msgmap); i++){
	    if(msgno){		/* unmark 'em all */
		if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		    diff++;
		    set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
		}
		else if(hidden)
		  set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	    }
	    else			/* mark 'em all */
	      set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
	}

	q_status_message4(SM_ORDER,0,2,"%s%s message%s %sselected",
			  msgno ? "" : "All ", comatose(diff), 
			  plural(diff), msgno ? "UN" : "");
	return;

      case 'n' :			/* Select by Number */
	rv = select_number(state->mail_stream, msgmap, mn_get_cur(msgmap));
	break;

      case 'd' :			/* Select by Date */
	rv = select_date(state->mail_stream, msgmap, mn_get_cur(msgmap));
	break;

      case 't' :			/* Text */
	rv = select_text(state->mail_stream, msgmap, mn_get_cur(msgmap));
	break;

      case 's' :			/* Status */
	rv = select_flagged(state->mail_stream, msgmap, mn_get_cur(msgmap));
	break;

      default :
	q_status_message(SM_ORDER | SM_DING, 3, 3,
			 "Unsupported Select option");
	return;
    }

    if(rv)				/* bad return value.. */
      return;				/* error already displayed */

    if(narrow)				/* make sure something was selected */
      for(i = 1L; i <= mn_get_total(msgmap); i++)
	if(mail_elt(state->mail_stream, mn_m2raw(msgmap, i))->searched){
	    if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
	      break;
	    else
	      mm_search_count--;
	}

    diff = 0L;
    if(mm_search_count){
	/*
	 * loop thru all the messages, adjusting local flag bits
	 * based on their "searched" bit...
	 */
	for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
	  if(narrow){
	      /* turning OFF selectedness if the "searched" bit isn't lit. */
	      if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		  if(!mail_elt(state->mail_stream,
			       mn_m2raw(msgmap, i))->searched){
		      diff--;
		      set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
		      if(hidden)
			set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
		  }
		  else if(msgno < mn_get_cur(msgmap))
		    msgno = i;
	      }
	  }
	  else if(mail_elt(state->mail_stream,mn_m2raw(msgmap,i))->searched){
	      /* turn ON selectedness if "searched" bit is lit. */
	      if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		  diff++;
		  set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
		  if(hidden)
		    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	      }
	  }

	/* if we're zoomed and the current message was unselected */
	if(narrow && msgno
	   && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
	  mn_reset_cur(msgmap, msgno);
    }

    if(!diff){
	if(narrow)
	  q_status_message4(SM_ORDER, 0, 2,
			"%s.  %s message%s remain%s selected.",
			mm_search_count ? "No change resulted"
					: "No messages in intersection",
			comatose(old_tot), plural(old_tot),
			(old_tot == 1L) ? "s" : "");
	else if(old_tot && mm_search_count)
	  q_status_message(SM_ORDER, 0, 2,
		   "No change resulted.  Matching messages already selected.");
	else
	  q_status_message1(SM_ORDER | SM_DING, 0, 2,
			    "Select failed.  No %smessages selected.",
			    old_tot ? "additional " : "");
    }
    else if(old_tot){
	sprintf(tmp_20k_buf,
		"Select matched %ld message%s.  %s %smessage%s %sselected.",
		(diff > 0) ? diff : old_tot + diff,
		plural((diff > 0) ? diff : old_tot + diff),
		comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
		(diff > 0) ? "total " : "",
		plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
		(diff > 0) ? "" : "UN");
	q_status_message(SM_ORDER, 0, 2, tmp_20k_buf);
    }
    else
      q_status_message2(SM_ORDER, 0, 2, "Select matched %s message%s!",
			comatose(diff), plural(diff));
}



/*----------------------------------------------------------------------
 Toggle the state of the current message

   Args: state -- pointer pine's state variables
	 msgmap -- message collection to operate on
	 q_line -- line on display to write prompts
	 in_index -- in the message index view
   Returns: TRUE if current marked selected, FALSE otw
  ----*/
int
individual_select(state, msgmap, q_line, in_index)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  q_line, in_index;
{
    long i;
    int  rv;

    i = mn_get_cur(msgmap);
    if(rv = get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ /* set? */
	set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
	if(any_lflagged(msgmap, MN_HIDE) > 0L){
	    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
	    /*
	     * See if there's anything left to zoom on.  If so, 
	     * pick an adjacent one for highlighting, else make
	     * sure nothing is left hidden...
	     */
	    if(any_lflagged(msgmap, MN_SLCT)){
		mn_inc_cur(state->mail_stream, msgmap);
		if(mn_get_cur(msgmap) == i)
		  mn_dec_cur(state->mail_stream, msgmap);
	    }
	    else{			/* clear all hidden flags */
		for(i = 1L; i <= mn_get_total(msgmap); i++)
		  set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	    }
	}
    }
    else
      set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);

    if(!in_index)
      q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
			long2string(i), rv ? "UN" : "");

    return(!rv);
}



/*----------------------------------------------------------------------
 Prompt the user for the command to perform on selected messages

   Args: state -- pointer pine's state variables
	 msgmap -- message collection to operate on
	 q_line -- line on display to write prompts
   Returns: 1 if the selected messages are suitably commanded,
	    0 if the choice to pick the command was declined

  ----*/
int
apply_command(state, stream, msgmap, q_line)
     struct pine *state;
     MAILSTREAM	 *stream;
     MSGNO_S     *msgmap;
     int	  q_line;
{
    int i = 8, rv = 1;
    int we_cancel = 0;

    if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
	sel_opts3[i].ch      = '*';
	sel_opts3[i].rval    = '*';
	sel_opts3[i].name    = "*";
	sel_opts3[i++].label = "Flag";
    }

    if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
	sel_opts3[i].ch      = '|';
	sel_opts3[i].rval    = '|';
	sel_opts3[i].name    = "|";
	sel_opts3[i++].label = "Pipe";
    }

    /*
     * This doesn't fit on the normal keymenu, so it will go in the help
     * slot instead (see "hacking" in status.c).  If either of above two
     * commands are disabled then it does fit.
     */
    if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
	sel_opts3[i].ch      = 'b';
	sel_opts3[i].rval    = 'b';
	sel_opts3[i].name    = "B";
	sel_opts3[i++].label = "Bounce";
    }

    if(F_ON(F_ENABLE_PRYNT, state)){
	sel_opts3[i].ch      = 'y';
	sel_opts3[i].rval    = '%';
	sel_opts3[i].name    = "";
	sel_opts3[i++].label = "";
    }

    sel_opts3[i].ch = -1;
    /*
     * Can't put an actual help in here instead of NO_HELP.  See comment
     * above.
     */
    switch(radio_buttons(sel_pmt3, q_line, sel_opts3, 0, 'x', NO_HELP,
			 RB_SEQ_SENSITIVE)){
      case 'd' :			/* delete */
	we_cancel = busy_alarm(1, NULL, NULL, 0);
	cmd_delete(state, msgmap, 1);
	if(we_cancel)
	  cancel_busy_alarm(0);
	break;

      case 'u' :			/* undelete */
	we_cancel = busy_alarm(1, NULL, NULL, 0);
	cmd_undelete(state, msgmap, 1);
	if(we_cancel)
	  cancel_busy_alarm(0);
	break;

      case 'r' :			/* reply */
	cmd_reply(state, msgmap, 1);
	break;

      case 'f' :			/* Forward */
	cmd_forward(state, msgmap, 1);
	break;

      case '%' :			/* print */
	cmd_print(state, msgmap, 1, 1);
	break;

      case 't' :			/* take address */
	cmd_take_addr(state, msgmap, 1);
	break;

      case 's' :			/* save */
	cmd_save(state, stream, msgmap, 1);
	break;

      case 'e' :			/* export */
	cmd_export(state, msgmap, q_line, 1);
	break;

      case '|' :			/* pipe */
	cmd_pipe(state, msgmap, 1);
	break;

      case '*' :			/* flag */
	we_cancel = busy_alarm(1, NULL, NULL, 0);
	cmd_flag(state, msgmap, 1);
	if(we_cancel)
	  cancel_busy_alarm(0);
	break;

      case 'b' :			/* bounce */
	cmd_bounce(state, msgmap, 1);
	break;

      case 'x' :			/* cancel */
	cmd_cancelled("Apply command");
	rv = 0;
	break;

	default:
	break;
    }

    return(rv);
}



/*----------------------------------------------------------------------
  ZOOM the message index (set any and all necessary hidden flag bits)

   Args: state -- usual pine state
	 msgmap -- usual message mapping
   Returns: number of messages zoomed in on

  ----*/
long
zoom_index(state, msgmap)
    struct pine *state;
    MSGNO_S	*msgmap;
{
    long i, count = 0L, first = 0L;

    if(any_lflagged(msgmap, MN_SLCT)){
	for(i = 1L; i <= mn_get_total(msgmap); i++){
	    if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
	    }
	    else{
		count++;
		if(!first)
		  first = i;
	    }
	}

	if(!get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap), MN_SLCT))
	  mn_set_cur(msgmap, first);
    }

    return(count);
}



/*----------------------------------------------------------------------
  UnZOOM the message index (clear any and all hidden flag bits)

   Args: state -- usual pine state
	 msgmap -- usual message mapping
   Returns: 1 if hidden bits to clear and they were, 0 if none to clear

  ----*/
int
unzoom_index(state, msgmap)
    struct pine *state;
    MSGNO_S	*msgmap;
{
    register long i;

    if(!any_lflagged(msgmap, MN_HIDE))
      return(0);

    for(i = 1L; i <= mn_get_total(msgmap); i++)
      set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);

    return(1);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_number(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int r;
    long n1, n2;
    char number1[16], number2[16], numbers[80], *p, *t;
    HelpType help;

    numbers[0] = '\0';
    ps_global->mangled_footer = 1;
    help = NO_HELP;
    while(1){
	int flags = OE_APPEND_CURRENT;

        r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, 79,
                             select_num, NULL, help, &flags);
        if(r == 4)
	  continue;

        if(r == 3){
            help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
	    continue;
	}

	for(t = p = numbers; *p ; p++)	/* strip whitespace */
	  if(!isspace((unsigned char)*p))
	    *t++ = *p;

	*t = '\0';

        if(r == 1 || numbers[0] == '\0'){
	    cmd_cancelled("Selection by number");
	    return(1);
        }
	else
	  break;
    }

    for(n1 = 1; n1 <= stream->nmsgs; n1++)
      mail_elt(stream, n1)->searched = 0;	/* clear searched bits */

    for(p = numbers; *p ; p++){
	t = number1;
	while(*p && isdigit((unsigned char)*p))
	  *t++ = *p++;

	*t = '\0';

	if(number1[0] == '\0'){
	    if(*p == '-')
	      q_status_message1(SM_ORDER | SM_DING, 0, 2,
	       "Invalid message number range, missing number before \"-\": %s",
	       numbers);
	    else
	      q_status_message1(SM_ORDER | SM_DING, 0, 2,
			        "Invalid message number: %s", numbers);
	    return(1);
	}

	if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
	    q_status_message1(SM_ORDER | SM_DING, 0, 2,
			      "\"%s\" out of message number range",
			      long2string(n1));
	    return(1);
	}

	t = number2;
	if(*p == '-'){
	    while(*++p && isdigit((unsigned char)*p))
	      *t++ = *p;

	    *t = '\0';

	    if(number2[0] == '\0'){
		q_status_message1(SM_ORDER | SM_DING, 0, 2,
		 "Invalid message number range, missing number after \"-\": %s",
		 numbers);
		return(1);
	    }

	    if((n2 = atol(number2)) < 1L 
	       || n2 > mn_get_total(msgmap)){
		q_status_message1(SM_ORDER | SM_DING, 0, 2,
				  "\"%s\" out of message number range",
				  long2string(n2));
		return(1);
	    }

	    if(n2 <= n1){
		char t[20];

		strcpy(t, long2string(n1));
		q_status_message2(SM_ORDER | SM_DING, 0, 2,
				  "Invalid reverse message number range: %s-%s",
				  t, long2string(n2));
		return(1);
	    }

	    for(;n1 <= n2; n1++)
	      mm_searched(stream, mn_m2raw(msgmap, n1));
	}
	else
	  mm_searched(stream, mn_m2raw(msgmap, n1));

	if(*p == '\0')
	  break;
    }
    
    return(0);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_date(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int	       r, we_cancel = 0, when = 0;
    char       date[32], defdate[32], prompt[128];
    time_t     seldate = time(0);
    struct tm *seldate_tm;
    HelpType   help;
    static struct _tense {
	char *range, *scope;
    } tense[] = {
	{"SINCE", " (inclusive)"},
	{"BEFORE", " (exclusive)"},
	{"ON", ""}
    };

    date[0]		      = '\0';
    ps_global->mangled_footer = 1;
    help		      = NO_HELP;

    while(1){
	int flags = OE_APPEND_CURRENT;

	seldate_tm = localtime(&seldate);
	sprintf(defdate, "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
		month_abbrev(seldate_tm->tm_mon + 1),
		seldate_tm->tm_year + 1900);
	sprintf(prompt,"Select messages with Dates %s%s [%s]: ",
		tense[when].range, tense[when].scope, defdate);
	r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, 31,
			     prompt, sel_date_opt, help, &flags);
	switch (r){
	  case 1 :
	    cmd_cancelled("Selection by date");
	    return(1);

	  case 3 :
	    help = (help == NO_HELP) ? h_select_date : NO_HELP;
	    continue;

	  case 4 :
	    continue;

	  case 11 :
	    {
		MESSAGECACHE *mc;

		if(mc = mail_elt(stream, mn_m2raw(msgmap, msgno))){
		    /* mail_date returns fixed field width date */
		    *(mail_date(tmp_20k_buf, mc) + 11) = '\0';
		    strcpy(date, tmp_20k_buf);
		}
	    }

	    continue;

	  case 12 :			/* set default to PREVIOUS day */
	    seldate -= 86400;
	    continue;

	  case 13 :			/* set default to NEXT day */
	    seldate += 86400;
	    continue;

	  case 14 :
	    when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
	    continue;

	  default:
	    break;
	}

	removing_leading_white_space(date);
	removing_trailing_white_space(date);
	if(!*date)
	  strcpy(date, defdate);

	break;
    }

    we_cancel = busy_alarm(1, "Busy Selecting", NULL, 0);
    sprintf(prompt, "%s %s", tense[when].range, date);
    /* Note: it's just as cheap to use mail_criteria since we'd have
     *	     to parse the date just as it does (which of course is
     *	     then just turned back into a string in the imap driver,
     *	     yuk)...
     */
    mail_search_full(stream, NULL, mail_criteria(prompt),
		     SE_NOPREFETCH | SE_FREE);

    if(we_cancel)
      cancel_busy_alarm(0);

    return(0);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_text(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int          r, type, we_cancel = 0, not = 0, flags, old_imap;
    char         sstring[80], savedsstring[80], origcharset[16], tmp[128];
    char        *sval = NULL, *cset = NULL, *charset = NULL;
    ESCKEY_S     ekey[4];
    ENVELOPE    *env = NULL;
    HelpType     help;
    static char *recip = "RECIPIENTS";
    static char *partic = "PARTICIPANTS";
    long         searchflags;
    SEARCHPGM   *srchpgm, *pgm, *secondpgm = NULL, *thirdpgm = NULL;

    ps_global->mangled_footer = 1;
    origcharset[0] = '\0';
    savedsstring[0] = '\0';
    ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;

    while(1){
	type = radio_buttons(not ? sel_not_text : sel_text,
			     -FOOTER_ROWS(ps_global), sel_text_opt,
			     's', 'x', NO_HELP, RB_NORM);
	
	if(type == '!')
	  not = !not;
	else
	  break;
    }

    /*
     * prepare some friendly defaults...
     */
    switch(type){
      case 't' :			/* address fields, offer To or From */
      case 'f' :
      case 'c' :
      case 'r' :
      case 'p' :
	sval          = (type == 't') ? "TO" :
			  (type == 'f') ? "FROM" :
			    (type == 'c') ? "CC" :
			      (type == 'r') ? recip : partic;
	ekey[0].ch    = ctrl('T');
	ekey[0].name  = "^T";
	ekey[0].rval  = 10;
	ekey[0].label = "Cur To";
	ekey[1].ch    = ctrl('R');
	ekey[1].name  = "^R";
	ekey[1].rval  = 11;
	ekey[1].label = "Cur From";
	ekey[2].ch    = ctrl('W');
	ekey[2].name  = "^W";
	ekey[2].rval  = 12;
	ekey[2].label = "Cur Cc";
	break;

      case 's' :
	sval          = "SUBJECT";
	ekey[0].ch    = ctrl('X');
	ekey[0].name  = "^X";
	ekey[0].rval  = 13;
	ekey[0].label = "Cur Subject";
	break;

      case 'a' :
	sval = "TEXT";			/* fall thru */
	break;

      case 'x':
	break;

      default:
	dprint(1, (debugfile,"\n - BOTCH: select_text unrecognized option\n"));
	return(1);
    }

    if(type != 'x'){
	if(ekey[0].ch > -1 && msgno > 0L
	   && !(env=mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),NULL)))
	  ekey[0].ch = -1;

	sstring[0] = '\0';
	help = NO_HELP;
	r = type;
	while(r != 'x'){
	    sprintf(tmp, "String in message %s to %smatch : ", sval,
		    not ? "NOT " : "");
	    flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
	    r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
				 79, tmp, ekey, help, &flags);

	    switch(r){
	      case 3 :
		help = (help == NO_HELP)
			? (not
			    ? ((type == 'f') ? h_select_txt_not_from
			      : (type == 't') ? h_select_txt_not_to
			       : (type == 'c') ? h_select_txt_not_cc
				: (type == 's') ? h_select_txt_not_subj
				 : (type == 'a') ? h_select_txt_not_all
				  : (type == 'r') ? h_select_txt_not_recip
				   : (type == 'p') ? h_select_txt_not_partic
				    :                 NO_HELP)
			    : ((type == 'f') ? h_select_txt_from
			      : (type == 't') ? h_select_txt_to
			       : (type == 'c') ? h_select_txt_cc
				: (type == 's') ? h_select_txt_subj
				 : (type == 'a') ? h_select_txt_all
				  : (type == 'r') ? h_select_txt_recip
				   : (type == 'p') ? h_select_txt_partic
				    :                 NO_HELP))
			: NO_HELP;

	      case 4 :
		continue;

	      case 10 :			/* To: default */
		if(env && env->to && env->to->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->to->mailbox,
			  env->to->host ? "@" : "",
			  env->to->host ? env->to->host : "");
		continue;

	      case 11 :			/* From: default */
		if(env && env->from && env->from->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->from->mailbox,
			  env->from->host ? "@" : "",
			  env->from->host ? env->from->host : "");
		continue;

	      case 12 :			/* Cc: default */
		if(env && env->cc && env->cc->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->cc->mailbox,
			  env->cc->host ? "@" : "",
			  env->cc->host ? env->cc->host : "");
		continue;

	      case 13 :			/* Subject: default */
		if(env && env->subject && env->subject[0]){
		    char *q = NULL;
		    if(cset)
		      fs_give((void **) &cset);

		    q = (char *)rfc1522_decode((unsigned char *)tmp_20k_buf,
					       env->subject, &cset);
		    /*
		     * If decoding was done, and the charset of the decoded
		     * subject is different from ours (cset != NULL) then
		     * we save that charset information for the search.
		     */
		    if(q != env->subject && cset && cset[0]){
			charset = cset;
			sprintf(savedsstring, "%.70s", q);
		    }

		    sprintf(sstring, "%.70s", q);
		}

		continue;

	      default :
		break;
	    }

	    if(r == 1 || sstring[0] == '\0')
	      r = 'x';

	    break;
	}
    }

    if(type == 'x' || r == 'x'){
	cmd_cancelled("Selection by text");
	return(1);
    }

    old_imap = (is_imap_stream(stream) && !modern_imap_stream(stream));

    /* create a search program and fill it in */
    srchpgm = pgm = mail_newsearchpgm();
    if(not && !old_imap){
	srchpgm->not = mail_newsearchpgmlist();
	srchpgm->not->pgm = mail_newsearchpgm();
	pgm = srchpgm->not->pgm;
    }

    switch(type){
      case 'r' :				/* TO or CC */
	if(old_imap){
	    /* No OR on old servers */
	    pgm->to = mail_newstringlist();
	    pgm->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->to->text.size = strlen(sstring);
	    secondpgm = mail_newsearchpgm();
	    secondpgm->cc = mail_newstringlist();
	    secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
	    secondpgm->cc->text.size = strlen(sstring);
	}
	else{
	    pgm->or = mail_newsearchor();
	    pgm->or->first->to = mail_newstringlist();
	    pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->first->to->text.size = strlen(sstring);
	    pgm->or->second->cc = mail_newstringlist();
	    pgm->or->second->cc->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->second->cc->text.size = strlen(sstring);
	}

	break;

      case 'p' :				/* TO or CC or FROM */
	if(old_imap){
	    /* No OR on old servers */
	    pgm->to = mail_newstringlist();
	    pgm->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->to->text.size = strlen(sstring);
	    secondpgm = mail_newsearchpgm();
	    secondpgm->cc = mail_newstringlist();
	    secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
	    secondpgm->cc->text.size = strlen(sstring);
	    thirdpgm = mail_newsearchpgm();
	    thirdpgm->from = mail_newstringlist();
	    thirdpgm->from->text.data = (unsigned char *) cpystr(sstring);
	    thirdpgm->from->text.size = strlen(sstring);
	}
	else{
	    pgm->or = mail_newsearchor();
	    pgm->or->first->to = mail_newstringlist();
	    pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->first->to->text.size = strlen(sstring);

	    pgm->or->second->or = mail_newsearchor();
	    pgm->or->second->or->first->cc = mail_newstringlist();
	    pgm->or->second->or->first->cc->text.data =
					    (unsigned char *) cpystr(sstring);
	    pgm->or->second->or->first->cc->text.size = strlen(sstring);
	    pgm->or->second->or->second->from = mail_newstringlist();
	    pgm->or->second->or->second->from->text.data =
					    (unsigned char *) cpystr(sstring);
	    pgm->or->second->or->second->from->text.size = strlen(sstring);
	}

	break;

      case 'f' :				/* FROM */
	pgm->from = mail_newstringlist();
	pgm->from->text.data = (unsigned char *) cpystr(sstring);
	pgm->from->text.size = strlen(sstring);
	break;

      case 'c' :				/* CC */
	pgm->cc = mail_newstringlist();
	pgm->cc->text.data = (unsigned char *) cpystr(sstring);
	pgm->cc->text.size = strlen(sstring);
	break;

      case 't' :				/* TO */
	pgm->to = mail_newstringlist();
	pgm->to->text.data = (unsigned char *) cpystr(sstring);
	pgm->to->text.size = strlen(sstring);
	break;

      case 's' :				/* SUBJECT */
	pgm->subject = mail_newstringlist();
	pgm->subject->text.data = (unsigned char *) cpystr(sstring);
	pgm->subject->text.size = strlen(sstring);
	break;

      case 'a' :				/* ALL TEXT */
	pgm->text = mail_newstringlist();
	pgm->text->text.data = (unsigned char *) cpystr(sstring);
	pgm->text->text.size = strlen(sstring);
	break;

      default :
	dprint(1, (debugfile,"\n - BOTCH: select_text unrecognized type\n"));
	return(1);
    }

    /*
     * If the user gets the current subject with the ^X command, and
     * that subject has a different charset than what the user uses, and
     * what is left after editing by the user is still a substring of
     * the original subject, and it still has non-ascii characters in it;
     * then use that charset from the original subject in the search.
     */
    if(charset && strstr(savedsstring, sstring) == NULL){
	strncpy(origcharset, charset, 15);
	origcharset[15] = '\0';
	charset = NULL;
    }

    /* set the charset */
    if(!charset){
	for(sval = sstring; *sval && isascii(*sval); sval++)
	  ;

	/* if it's ascii, don't warn user about charset change */
	if(!*sval)
	  origcharset[0] = '\0';

	/* if it isn't ascii, use user's charset */
	charset = (*sval &&
		   ps_global->VAR_CHAR_SET &&
		   ps_global->VAR_CHAR_SET[0])
		     ? ps_global->VAR_CHAR_SET
		     : "US-ASCII";
    }

    if(*origcharset)
      q_status_message2(SM_ORDER, 5, 5,
		    "Warning: character set used for search changed (%s -> %s)",
		    origcharset, charset);

    /*
     * If we happen to have any messages excluded, make sure we
     * don't waste time searching their text...
     */
    srchpgm->msgno = visible_searchset(stream, msgmap);

    we_cancel = busy_alarm(1, "Busy Selecting", NULL, 0);

    searchflags = (SE_NOPREFETCH | SE_FREE);

    mail_search_full(stream, !old_imap ? charset : NULL, srchpgm, searchflags);
    
    /* search for To or Cc; or To or Cc or From on old imap server */
    if(secondpgm){
	searchflags |= SE_RETAIN;
	mail_search_full(stream, NULL, secondpgm, searchflags);

	if(thirdpgm)
	  mail_search_full(stream, NULL, thirdpgm, searchflags);
    }

    if(old_imap && not){
	MESSAGECACHE *mc;

	/* 
	 * Old imap server doesn't have a NOT, so we actually searched for
	 * the subject (or whatever) instead of !subject. Flip the searched
	 * bits.
	 */
	for(msgno = 1L; msgno <= mn_get_total(msgmap); msgno++)
	    if((mc=mail_elt(stream, msgno))->searched)
	      mc->searched = NIL;
	    else
	      mc->searched = T;
    }

    if(we_cancel)
      cancel_busy_alarm(0);

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

    return(0);
}


/*
 * visible_searchset -- return c-client search set unEXLDed
 *			sequence numbers
 */
SEARCHSET *
visible_searchset(stream, msgmap)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
{
    long       n, run;
    SEARCHSET *full_set = NULL, **set;

    /*
     * If we're talking to anything other than a server older than
     * imap 4rev1, build a searchset otherwise it'll choke.
     */
    if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
	if(any_lflagged(msgmap, MN_EXLD)){
	    for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
	      if(get_lflag(stream, NULL, n, MN_EXLD)){
		  if(run){		/* previous NOT excluded? */
		      if(run > 1L)
			(*set)->last = n - 1L;

		      set = &(*set)->next;
		      run = 0L;
		  }
	      }
	      else if(run++){		/* next in run */
		  (*set)->last = n;
	      }
	      else{				/* start of run */
		  *set = mail_newsearchset();
		  (*set)->first = n;
	      }
	}
	else{
	    full_set = mail_newsearchset();
	    full_set->first = 1L;
	    full_set->last  = stream->nmsgs;
	}
    }

    return(full_set);
}


/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_flagged(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int	       s, not = 0, we_cancel = 0;
    SEARCHPGM *pgm;

    while(1){
	s = radio_buttons((not) ? sel_flag_not : sel_flag,
			  -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
			  NO_HELP, RB_NORM);
			  
	if(s == 'x'){
	    cmd_cancelled("Selection by status");
	    return(1);
	}
	else if(s == '!')
	  not = !not;
	else
	  break;
    }

    pgm = mail_newsearchpgm();
    switch(s){
      case 'n' :
	if(not)
	  pgm->seen = 1;
	else
	  pgm->unseen = pgm->undeleted = pgm->unanswered = 1;
	  
	break;

      case 'd' :
	if(not)
	  pgm->undeleted = 1;
	else
	  pgm->deleted = 1;

	break;

      case 'a':
	if(not)
	  pgm->unanswered = 1;
	else
	  pgm->answered = pgm->undeleted = 1;
	break;

      default :
	if(not)
	  pgm->unflagged = 1;
	else
	  pgm->flagged = 1;
	  
	break;
    }

    we_cancel = busy_alarm(1, "Busy Selecting", NULL, 0);
    mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
    if(we_cancel)
      cancel_busy_alarm(0);

    return(0);
}



/*----------------------------------------------------------------------
   Prompt the user for the type of sort he desires

Args: state -- pine state pointer
      q1 -- Line to prompt on

      Returns 0 if it was cancelled, 1 otherwise.
  ----*/
int
select_sort(state, ql, sort, rev)
     struct pine *state;
     int	  ql;
     SortOrder	 *sort;
     int	 *rev;
{
    char      prompt[200], tmp[3], *p;
    int       s, i;
    int       deefault = 'a', retval = 1;
    HelpType  help;
    ESCKEY_S  sorts[12];

#ifdef _WINDOWS
    DLG_SORTPARAM	sortsel;

    if (mswin_usedialog ()) {

	sortsel.reverse = mn_get_revsort (state->msgmap);
	sortsel.cursort = mn_get_sort (state->msgmap);
	sortsel.helptext = get_help_text (h_select_sort);
	sortsel.rval = 0;

	if ((retval = os_sortdialog (&sortsel))) {
	    *sort = sortsel.cursort;
	    *rev  = sortsel.reverse;
        }

	free_list_array(&sortsel.helptext);

	return (retval);
    }
#endif

    /*----- String together the prompt ------*/
    tmp[1] = '\0';
    strcpy(prompt, "Choose type of sort, or Reverse current sort : ");
    for(i = 0; state->sort_types[i] != EndofList; i++) {
	sorts[i].rval	   = i;
	p = sorts[i].label = sort_name(state->sort_types[i]);
	while(*(p+1) && islower((unsigned char)*p))
	  p++;

	sorts[i].ch   = tolower((unsigned char)(tmp[0] = *p));
	sorts[i].name = cpystr(tmp);

        if(mn_get_sort(state->msgmap) == state->sort_types[i])
	  deefault = sorts[i].rval;
    }

    sorts[i].ch     = 'r';
    sorts[i].rval   = 'r';
    sorts[i].name   = cpystr("R");
    sorts[i].label  = "Reverse";
    sorts[++i].ch   = -1;
    help = h_select_sort;

    if((s = radio_buttons(prompt,ql,sorts,deefault,'x',help,RB_NORM)) != 'x'){
	state->mangled_body = 1;		/* signal screen's changed */
	if(s == 'r')
	  *rev = !mn_get_revsort(state->msgmap);
	else
	  *sort = state->sort_types[s];
    }
    else{
	retval = 0;
	cmd_cancelled("Sort");
    }

    while(--i >= 0)
      fs_give((void **)&sorts[i].name);

    blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
    return(retval);
}


/*---------------------------------------------------------------------
  Build list of folders in the given context for user selection

  Args: c -- pointer to pointer to folder's context context 
	f -- folder prefix to display
	sublist -- whether or not to use 'f's contents as prefix
	lister -- function used to do the actual display

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with local "selected" flag.
  ----*/
int
display_folder_list(c, f, sublist, lister)
    CONTEXT_S **c;
    char       *f;
    int	        sublist;
    int	      (*lister) PROTO((struct pine *, CONTEXT_S **, char *, int));
{
    int	       rc;
    CONTEXT_S *tc;
    void (*redraw)() = ps_global->redrawer;

    push_titlebar_state();
    tc = *c;
    if(rc = (*lister)(ps_global, &tc, f, sublist))
      *c = tc;

    ClearScreen();
    pop_titlebar_state();
    redraw_titlebar();
    if(ps_global->redrawer = redraw) /* reset old value, and test */
      (*ps_global->redrawer)();

    if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
      return(1);

    return(0);
}



/*----------------------------------------------------------------------
  Build comma delimited list of selected messages

  Args: stream -- mail stream to use for flag testing
	msgmap -- message number struct of to build selected messages in
	count -- pointer to place to write number of comma delimited

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with local "selected" flag.
  ----*/
char *
selected_sequence(stream, msgmap, count)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    long       *count;
{
    long  i;

    /*
     * The plan here is to use the c-client elt's "sequence" bit
     * to work around any orderings or exclusions in pine's internal
     * mapping that might cause the sequence to be artificially
     * lengthy.  It's probably cheaper to run down the elt list
     * twice rather than call nm_raw2m() for each message as
     * we run down the elt list once...
     */
    for(i = 1L; i <= stream->nmsgs; i++)
      mail_elt(stream, i)->sequence = 0;

    for(i = 1L; i <= mn_get_total(msgmap); i++)
      if(get_lflag(stream, msgmap, i, MN_SLCT)){
	  /*
	   * Forget we knew about it, and set "add to sequence"
	   * bit...
	   */
	  clear_index_cache_ent(i);
	  mail_elt(stream, mn_m2raw(msgmap, i))->sequence = 1;
      }

    return(build_sequence(stream, NULL, count));
}


/*----------------------------------------------------------------------
  Build comma delimited list of current, flagged messages

  Args: stream -- mail stream to use for flag testing
	msgmap -- message number struct of to build selected messages in
	flag -- system flag to 
	count -- pointer to place to write number of comma delimited
	mark -- mark index cache entry changed, and count state change

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with local "selected" flag (a flag
	   of zero means all current msgs).
  ----*/
char *
currentf_sequence(stream, msgmap, flag, count, mark)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    long	flag;
    long       *count;
    int		mark;
{
    char	 *seq;
    long	  i;
    MESSAGECACHE *mc;

    /* First, make sure elts are valid for all the interesting messages */
    if(seq = invalid_elt_sequence(stream, msgmap)){
	mail_fetchflags(stream, seq);
	fs_give((void **) &seq);
    }

    for(i = 1L; i <= stream->nmsgs; i++)
      mail_elt(stream, i)->sequence = 0;	/* clear "sequence" bits */

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
	/* if not already set, go on... */
	mc = mail_elt(stream, mn_m2raw(msgmap, i));
	if((flag == F_DEL && !mc->deleted)
	   || (flag == F_UNDEL && mc->deleted)
	   || (flag == F_SEEN && !mc->seen)
	   || (flag == F_UNSEEN && mc->seen)
	   || (flag == F_ANS && !mc->answered)
	   || (flag == F_UNANS && mc->answered)
	   || (flag == F_FLAG && !mc->flagged)
	   || (flag == F_UNFLAG && mc->flagged))
	  continue;

	mc->sequence = 1;			/* set "sequence" flag */
	if(mark){
	    clear_index_cache_ent(i);		/* force new index line */
	    check_point_change();		/* count state change */
	}
    }

    return(build_sequence(stream, NULL, count));
}


/*----------------------------------------------------------------------
  Return sequence numbers of messages with invalid MESSAGECACHEs

  Args: stream -- mail stream to use for flag testing
	msgmap -- message number struct of to build selected messages in

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with local "selected" flag (a flag
	   of zero means all current msgs).
  ----*/
char *
invalid_elt_sequence(stream, msgmap)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
{
    long	  i;
    MESSAGECACHE *mc;

    for(i = 1L; i <= stream->nmsgs; i++)
      mail_elt(stream, i)->sequence = 0;	/* clear "sequence" bits */

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
      if(!(mc = mail_elt(stream, mn_m2raw(msgmap, i)))->valid)
	mc->sequence = 1;

    return(build_sequence(stream, NULL, NULL));
}


/*----------------------------------------------------------------------
  Build comma delimited list of messages with elt "sequence" bit set

  Args: stream -- mail stream to use for flag testing
	msgmap -- struct containing sort to build sequence in
	count -- pointer to place to write number of comma delimited
		 NOTE: if non-zero, it's a clue as to how many messages
		       have the sequence bit lit.

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with elt's "sequence" bit set
  ----*/
char *
build_sequence(stream, msgmap, count)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    long       *count;
{
#define	SEQ_INCREMENT	128
    long    n = 0L, i, x, lastn = 0L, runstart = 0L;
    size_t  size = SEQ_INCREMENT;
    char   *seq = NULL, *p;

    if(count){
	if(*count > 0L)
	  size = min((*count) * 4, 16384);

	*count = 0L;
    }

    for(x = 1L; x <= stream->nmsgs; x++){
	if(msgmap){
	    if((i = mn_m2raw(msgmap, x)) == 0L)
	      continue;
	}
	else
	  i = x;

	if(mail_elt(stream, i)->sequence){
	    n++;
	    if(!seq)				/* initialize if needed */
	      seq = p = fs_get(size);

	    /*
	     * This code will coalesce the ascending runs of
	     * sequence numbers, but fails to break sequences
	     * into a reasonably sensible length for imapd's to
	     * swallow (reasonable addtition to c-client?)...
	     */
	    if(lastn){				/* if may be in a run */
		if(lastn + 1L == i){		/* and its the next raw num */
		    lastn = i;			/* skip writing anything... */
		    continue;
		}
		else if(runstart != lastn){
		    *p++ = (runstart + 1L == lastn) ? ',' : ':';
		    sstrcpy(&p, long2string(lastn));
		}				/* wrote end of run */
	    }

	    runstart = lastn = i;		/* remember last raw num */

	    if(n > 1L)				/* !first num, write delim */
	      *p++ = ',';

	    if(size - (p - seq) < 16){	/* room for two more nums? */
		size_t offset = p - seq;	/* grow the sequence array */
		size += SEQ_INCREMENT;
		fs_resize((void **)&seq, size);
		p = seq + offset;
	    }

	    sstrcpy(&p, long2string(i));	/* write raw number */
	}
    }

    if(lastn && runstart != lastn){		/* were in a run? */
	*p++ = (runstart + 1L == lastn) ? ',' : ':';
	sstrcpy(&p, long2string(lastn));	/* write the trailing num */
    }

    if(seq)					/* if sequence, tie it off */
      *p  = '\0';

    if(count)
      *count = n;

    return(seq);
}



/*----------------------------------------------------------------------
  If any messages flagged "selected", fake the "currently selected" array

  Args: map -- message number struct of to build selected messages in

  OK folks, here's the tradeoff: either all the functions have to
  know if the user want's to deal with the "current" hilited message
  or the list of currently "selected" messages, *or* we just
  wrap the call to these functions with some glue that tweeks
  what these functions see as the "current" message list, and let them
  do their thing.
  ----*/
int
pseudo_selected(map)
    MSGNO_S *map;
{
    long i, later = 0L;

    if(any_lflagged(map, MN_SLCT)){
	map->hilited = mn_m2raw(map, mn_get_cur(map));

	for(i = 1L; i <= mn_get_total(map); i++)
	  /* BUG: using the global mail_stream is kind of bogus since
	   * everybody that calls us get's a pine stuct passed it.
	   * perhaps a stream pointer in the message struct makes 
	   * sense?
	   */
	  if(get_lflag(ps_global->mail_stream, map, i, MN_SLCT)){
	      if(!later++){
		  mn_set_cur(map, i);
	      }
	      else{
		  mn_add_cur(map, i);
	      }
	  }

	return(1);
    }

    return(0);
}


/*----------------------------------------------------------------------
  Antidote for the monkey business committed above

  Args: map -- message number struct of to build selected messages in

  ----*/
void
restore_selected(map)
    MSGNO_S *map;
{
    if(map->hilited){
	mn_reset_cur(map, mn_raw2m(map, map->hilited));
	map->hilited = 0L;
    }
}


/*
 * Get the user name from the mailbox portion of an address.
 *
 * Args: mailbox -- the mailbox portion of an address (lhs of address)
 *       target  -- a buffer to put the result in
 *       len     -- length of the target buffer
 *
 * Returns the left most portion up to the first '%', ':' or '@',
 * and to the right of any '!' (as if c-client would give us such a mailbox).
 * Returns NULL if it can't find a username to point to.
 */
char *
get_uname(mailbox, target, len)
    char  *mailbox,
	  *target;
    int    len;
{
    int i, start, end;

    if(!mailbox || !*mailbox)
      return(NULL);

    end = strlen(mailbox) - 1;
    for(start = end; start > -1 && mailbox[start] != '!'; start--)
        if(strindex("%:@", mailbox[start]))
	    end = start - 1;

    start++;			/* compensate for either case above */

    for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
      target[i-start] = isupper((unsigned char)mailbox[i])
					  ? tolower((unsigned char)mailbox[i])
					  : mailbox[i];

    target[i-start] = '\0';	/* tie it off */

    return(*target ? target : NULL);
}


/*
 * file_lister - call pico library's file lister
 */
int
file_lister(title, path, pathlen, file, filelen, newmail, flags)
    char *title, *path, *file;
    int   pathlen, filelen, newmail, flags;
{
    PICO   pbuf;
    int	   rv;
    void (*redraw)() = ps_global->redrawer;

    push_titlebar_state();
    memset(&pbuf, 0, sizeof(PICO));
/* BUG: what about help command and text? */
    pbuf.tty_fix       = ttyfix;
    pbuf.showmsg       = display_message_for_pico;
    pbuf.suspend       = do_suspend;
    pbuf.keybinput     = cmd_input_for_pico;
    pbuf.helper        = helper;
    pbuf.resize	       = resize_for_pico;
    pbuf.browse_help   = h_composer_browse;
    pbuf.menu_rows     = FOOTER_ROWS(ps_global) - 1;
    pbuf.pine_anchor   = title;
    pbuf.pine_version  = pine_version;
    pbuf.pine_flags    = flags_for_pico(ps_global);
    if(ps_global->VAR_OPER_DIR){
	pbuf.oper_dir    = ps_global->VAR_OPER_DIR;
	pbuf.pine_flags |= P_TREE;
    }

    if(newmail)
      pbuf.newmail = new_mail_for_pico;

    rv = pico_file_browse(&pbuf, path, pathlen, file, filelen, NULL, flags);
    fix_windsize(ps_global);
    init_signals();		/* has it's own signal stuff */

    /* Restore display's titlebar and body */
    pop_titlebar_state();
    redraw_titlebar();
    if(ps_global->redrawer = redraw)
      (*ps_global->redrawer)();

    return(rv);
}


#ifdef	_WINDOWS


/*
 * windows callback to get/set header mode state
 */
int
header_mode_callback(set, args)
    int  set;
    long args;
{
    return(ps_global->full_header);
}


/*
 * windows callback to get/set zoom mode state
 */
int
zoom_mode_callback(set, args)
    int  set;
    long args;
{
    return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
}


/*
 * windows callback to get/set zoom mode state
 */
int
any_selected_callback(set, args)
    int  set;
    long args;
{
    return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
}


/*
 *
 */
int
flag_callback(set, flags)
    int  set;
    long flags;
{
    MESSAGECACHE *mc;
    int		  newflags = 0;
    long	  msgno;

    if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
	 && can_set_flag(ps_global, "flag")))
      return(0);

    if(ps_global->io_error_on_stream) {
	ps_global->io_error_on_stream = 0;
	mail_check(ps_global->mail_stream); /* forces write */
	return(0);
    }

    msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
    if((mc = mail_elt(ps_global->mail_stream, msgno)) && mc->valid){
	/*
	 * NOTE: code below is *VERY* sensitive to the order of
	 * the messages defined in resource.h for flag handling.
	 * Don't change it unless you know what you're doing.
	 */
	if(set){
	    char *flagstr;
	    long  ourflag, mflag;

	    switch(set){
	      case 1 :			/* Important */
		flagstr = "\\FLAGGED";
		mflag   = (mc->flagged) ? 0L : ST_SET;
		break;

	      case 2 :			/* New */
		flagstr = "\\SEEN";
		mflag   = (mc->seen) ? 0L : ST_SET;
		break;

	      case 3 :			/* Answered */
		flagstr = "\\ANSWERED";
		mflag   = (mc->answered) ? 0L : ST_SET;
		break;

	      case 4 :		/* Deleted */
		flagstr = "\\DELETED";
		mflag   = (mc->deleted) ? 0L : ST_SET;
		break;

	      default :			/* bogus */
		return(0);
	    }

	    mail_flag(ps_global->mail_stream, long2string(msgno),
		      flagstr, mflag);

	    if(ps_global->redrawer)
	      (*ps_global->redrawer)();
	}
	else{
	    /* Important */
	    if(mc->flagged)
	      newflags |= 0x0001;

	    /* New */
	    if(!mc->seen)
	      newflags |= 0x0002;

	    /* Answered */
	    if(mc->answered)
	      newflags |= 0x0004;

	    /* Deleted */
	    if(mc->deleted)
	      newflags |= 0x0008;
	}
    }

    return(newflags);
}



MPopup *
flag_submenu(mc)
    MESSAGECACHE *mc;
{
    static MPopup flag_submenu[] = {
	{tMessage, {"Important", lNormal}, {IDM_MI_FLAGIMPORTANT}},
	{tMessage, {"New", lNormal}, {IDM_MI_FLAGNEW}},
	{tMessage, {"Answered", lNormal}, {IDM_MI_FLAGANSWERED}},
	{tMessage , {"Deleted", lNormal}, {IDM_MI_FLAGDELETED}},
	{tTail}
    };

    /* Important */
    flag_submenu[0].label.style = (mc->flagged) ? lChecked : lNormal;

    /* New */
    flag_submenu[1].label.style = (mc->seen) ? lNormal : lChecked;

    /* Answered */
    flag_submenu[2].label.style = (mc->answered) ? lChecked : lNormal;

    /* Deleted */
    flag_submenu[3].label.style = (mc->deleted) ? lChecked : lNormal;

    return(flag_submenu);
}
#endif	/* _WINDOWS */
