/*
 * Program:	MS - Mail System Distributed Client
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	28 November 1988
 * Last Edited:	8 December 2005
 *
 * The IMAP toolkit provided in this Distribution is
 * Copyright 1988-2005 University of Washington.
 *
 * The full text of our legal notices is contained in the file called
 * CPYRIGHT, included with this Distribution.
 */

/* Version information */

static char *ccmd_copyright = "CCMD command interface is\n\
Copyright (c) 1986, 1990 by The Trustees of Columbia University in\n\
the City of New York.  Permission is granted to any individual or\n\
institution to use, copy, or redistribute this software so long as it\n\
is not sold for profit, provided this copyright notice is retained.\n";
static char *version = "2005.107";
static char *hostlist[] = {	/* SMTP server host list */
  "mailhost",
  "localhost",
  0
};
static char *newslist[] = {	/* News server host list */
  "news",
  "newshost",
  "localhost",
  0
};

/* Parameter files */

#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <signal.h>
#include "c-client.h"
/* stupid ccmd */
#define main ccmd_main
#define bcopy ccmd_bcopy
#define VOID void
#include "ccmd.h"
#undef main
#undef bcopy
#undef VOID


/* Size of temporary buffers */
#define TMPLEN 1024


/* CCMD buffer sizes */
#define CMDBFL 16384
#define MAXMAILBOXES 1000

/* Header sizes */
#define FROMLEN 20
#define SUBJECTLEN 25

/* Function prototypes */

int main (int argc,char *argv[]);
void ctrlc ();
short ms_init ();
void toplevel (int argc,char *argv[]);
void do_cmd (void (*f) (short help));
void do_help (void (*f) (short help));
void c_answer (short help);
void c_blank (short help);
void c_check (short help);
void c_copy (short help);
void c_create (short help);
void c_daytime (short help);
void c_debug (short help);
void c_delete (short help);
void c_echo (short help);
void c_exit (short help);
void c_expunge (short help);
void c_flag (short help);
void c_forward (short help);
void c_get (short help);
void c_headers (short help);
void c_help (short help);
void c_keyword (short help);
void c_kill (short help);
void c_literal_type (short help);
void c_mark (short help);
void c_move (short help);
void c_next (short help);
void c_post (short help);
void c_previous (short help);
void c_read (short help);
void c_remail (short help);
void c_remove (short help);
void c_rename (short help);
void c_quit (short help);
void c_send (short help);
void c_status (short help);
void c_subscribe (short help);
void c_take (short help);
int takelevel ();
void c_type (short help);
void c_unanswer (short help);
void c_undelete (short help);
void c_unflag (short help);
void c_unkeyword (short help);
void c_unmark (short help);
void c_unsubscribe (short help);
void c_version (short help);
void r_answer (short help);
void r_copy (short help);
void r_delete (short help);
void r_flag (short help);
void r_forward (short help);
void r_headers (short help);
void r_help (short help);
void r_keyword (short help);
void r_kill (short help);
void r_literal_type (short help);
void r_mark (short help);
void r_move (short help);
void r_next (short help);
void r_pipe (short help);
void r_previous (short help);
void r_quit (short help);
void r_remail (short help);
void r_status (short help);
void r_type (short help);
void r_unanswer (short help);
void r_undelete (short help);
void r_unflag (short help);
void r_unkeyword (short help);
void r_unmark (short help);
void send_level (ENVELOPE *msg,BODY *body);
void do_scmd (int (*f)(short help,ENVELOPE *msg,BODY *body),
	      ENVELOPE *msg,BODY *body);
void do_shelp (int (*f)(short help,ENVELOPE *msg,BODY *body),
	       ENVELOPE *msg,BODY *body);
void s_bcc (short help,ENVELOPE *msg,BODY *body);
void s_cc (short help,ENVELOPE *msg,BODY *body);
void s_display (short help,ENVELOPE *msg,BODY *body);
void s_erase (short help,ENVELOPE *msg,BODY *body);
void s_help (short help,ENVELOPE *msg,BODY *body);
void s_newsgroups (short help,ENVELOPE *msg,BODY *body);
void s_quit (short help,ENVELOPE *msg,BODY *body);
void s_remove (short help,ENVELOPE *msg,BODY *body);
void s_send (short help,ENVELOPE *msg,BODY *body);
void s_subject (short help,ENVELOPE *msg,BODY *body);
void s_to (short help,ENVELOPE *msg,BODY *body);
char do_sequence (char *def);
void more (void (*f)(FILE *pipe,long arg),long arg);
void do_display (FILE *pipe,long i);
void do_header (FILE *pipe,long i);
void do_get (char *mailbox);
void do_status (MAILSTREAM *stream);
void do_version ();
void answer_message (int msgno,int allflag);
void header_message (FILE *file,long msgno);
void literal_type_message (FILE *file,long msgno);
void remail_message (int msgno,ADDRESS *adr);
void type_message (FILE *file,long msgno);
void type_address (FILE *file,char *tag,ADDRESS *adr);
void type_string (FILE *file,int *pos,char *str1,char chr,char *str2);
ENVELOPE *send_init ();
ADDRESS *copy_adr (ADDRESS *adr,ADDRESS *ret);
ADDRESS *remove_adr (ADDRESS *adr,char *text);
BODY *send_text ();
void send_message (ENVELOPE *msg,BODY *body);
int onoff ();
void cmerr (char *string);

/* Global variables */

static char sequence[CMDBFL];	/* string representing sequence */
static long current = 0;	/* current message for NEXT, PREVIOUS, KILL */
static int cmdbuf[CMDBFL];	/* command buffer */
static char atmbuf[CMDBFL];	/* atom buffer */
static char wrkbuf[CMDBFL];	/* work buffer */
static keywrd mbx[MAXMAILBOXES];/* mailbox list */
static keytab mbxtab = {0,mbx};	/* pointer to mailbox list */
static int critical = NIL;	/* in critical code flag */
static int done = NIL;		/* exit flag */
static int quitted = NIL;	/* quitted from send flag */
static int debug = NIL;		/* debugging flag */
static char *curhst = NIL;	/* current host */
static char *curusr = NIL;	/* current login user */
static char *personal = NIL;	/* user's personal name */
static long nmsgs = 0;		/* local number of messages */
static MAILSTREAM *stream = NIL;/* current mail stream */
static char *fwdhdr = "\n ** Begin Forwarded Message(s) **\n\n";
static keywrd flags[NUSERFLAGS];/* user flag table */
static keytab flgtab = {0,flags};
static brktab mbxbrk = {
  {				/* 1st char break array */
    0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x01,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01
  },
  {				/* subsequent char break array */
    0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x01,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01
  }
};

/* Top level command table */

static keywrd cmds[] = {
  {"ANSWER",	NIL,	(keyval) c_answer},
  {"BLANK",	NIL,	(keyval) c_blank},
  {"CHECK",	NIL,	(keyval) c_check},
  {"COPY",	NIL,	(keyval) c_copy},
  {"CREATE",	NIL,	(keyval) c_create},
  {"D",		KEY_INV|KEY_ABR,(keyval) 8},
  {"DAYTIME",	NIL,	(keyval) c_daytime},
  {"DEBUG",	NIL,	(keyval) c_debug},
  {"DELETE",	NIL,	(keyval) c_delete},
  {"ECHO",	NIL,	(keyval) c_echo},
  {"EX",	KEY_INV|KEY_ABR,(keyval) 11},
  {"EXIT",	NIL,	(keyval) c_exit},
  {"EXPUNGE",	NIL,	(keyval) c_expunge},
  {"FLAG",	NIL,	(keyval) c_flag},
  {"FORWARD",	NIL,	(keyval) c_forward},
  {"GET",	NIL,	(keyval) c_get},
  {"H",		KEY_INV|KEY_ABR,(keyval) 17},
  {"HEADERS",	NIL,	(keyval) c_headers},
  {"HELP",	NIL,	(keyval) c_help},
  {"KEYWORD",	NIL,	(keyval) c_keyword},
  {"KILL",	NIL,	(keyval) c_kill},
  {"LITERAL-TYPE",NIL,	(keyval) c_literal_type},
  {"MARK",	NIL,	(keyval) c_mark},
  {"MOVE",	NIL,	(keyval) c_move},
  {"NEXT",	NIL,	(keyval) c_next},
  {"POST",	NIL,	(keyval) c_post},
  {"PREVIOUS",	NIL,	(keyval) c_previous},
  {"QUIT",	NIL,	(keyval) c_quit},
  {"R",		KEY_INV|KEY_ABR,(keyval) 29},
  {"READ",	NIL,	(keyval) c_read},
  {"REMAIL",	NIL,	(keyval) c_remail},
  {"REMOVE",	NIL,	(keyval) c_remove},
  {"RENAME",	NIL,	(keyval) c_rename},
  {"S",		KEY_INV|KEY_ABR,(keyval) 34},
  {"SEND",	NIL,	(keyval) c_send},
  {"SUBSCRIBE",	NIL,	(keyval) c_subscribe},
  {"STATUS",	NIL,	(keyval) c_status},
  {"T",		KEY_INV|KEY_ABR,(keyval) 39},
  {"TAKE",	NIL,	(keyval) c_take},
  {"TYPE",	NIL,	(keyval) c_type},
  {"UNANSWER",	NIL,	(keyval) c_unanswer},
  {"UNDELETE",	NIL,	(keyval) c_undelete},
  {"UNFLAG",	NIL,	(keyval) c_unflag},
  {"UNKEYWORD",	NIL,	(keyval) c_unkeyword},
  {"UNMARK",	NIL,	(keyval) c_unmark},
  {"UNSUBSCRIBE",NIL,	(keyval) c_unsubscribe},
  {"VERSION",	NIL,	(keyval) c_version},
};
static keytab cmdtab = {(sizeof (cmds)/sizeof (keywrd)),cmds};

struct display_msg {
  ENVELOPE *env;
  BODY *bdy;
};

/* Main program */

int main (int argc,char *argv[])
{
  signal (SIGINT,ctrlc);	/* set up CTRL/C handler */
  signal (SIGQUIT,SIG_IGN);	/* ignore quit and pipe signals */
  signal (SIGPIPE,SIG_IGN);
				/* initialize CCMD */
  cmbufs (cmdbuf,CMDBFL,atmbuf,CMDBFL,wrkbuf,CMDBFL);
  cmseti (stdin,stdout,stderr);
  if (ms_init () && argc <= 1) {/* initialize global state and if OK... */
    cmcls ();			/* blank the screen */
    do_version ();		/* display version */
    do_get ("INBOX");		/* get INBOX */
  }
  toplevel (argc,argv);		/* enter top level cmd parser */
  stream = mail_close (stream);	/* punt mail stream */
  critical = T;			/* don't allow CTRL/C any more */
  cmdone ();			/* restore the world */
  return 0;
}


/* Respond to CTRL/C
 */

void ctrlc ()
{
  cmxprintf ("^C\n");		/* make sure user gets confirmation */
  if (!critical) {		/* must not be critical */
    if (!done) done = T;	/* if global done not set, set it now */
    else {			/* else two in a row kill us */
      cmdone ();		/* restore the world */
      _exit (0);
    }
  }
}

/* Initialize MS global state
 * Returns: T if OK, NIL if error
 */

short ms_init ()
{
  char tmp[TMPLEN];
  char *name;
  char *suffix;
  struct passwd *pwd;
  struct hostent *host_name;
  if (curhst) fs_give ((void **) &curhst);
  if (curusr) fs_give ((void **) &curusr);
  gethostname (tmp,TMPLEN);	/* get local name */
				/* get it in full form */
  curhst = (host_name = gethostbyname (tmp)) ?
    cpystr (host_name->h_name) : cpystr (tmp);
				/* get user name and passwd entry */
  if (name = (char *) getlogin ()) pwd = getpwnam (name);
  else {
    pwd = getpwuid (getuid ());	/* get it this way if detached, etc */
    name = pwd->pw_name;
  }
  curusr = cpystr (name);	/* current user is this name */
  if (!personal) {		/* this is OK to do only once */
    strcpy (tmp,pwd->pw_gecos);	/* probably not necessay but be safe */
				/* dyke out the office and phone poop */
    if (suffix = (char *) strchr (tmp,',')) suffix[0] = '\0';
    personal = cpystr (tmp);	/* make a permanent copy of it */
  }
  flgtab._ktcnt = 0;		/* init keyword table */
#include "linkage.c"
  mail_parameters (NIL,SET_PREFETCH,NIL);
  mail_list (NIL,NIL,"%");	/* find local mailboxes */
  mail_lsub (NIL,NIL,"*");	/* find subscribed mailboxes */
  return T;
}

/* Top-level command parser
 * Accepts: argument count
 *	    argument vector
 */

void toplevel (int argc,char *argv[])
{
  int i;
  pval parseval;
  fdb *used;
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(cmdtab),"Command, ",NIL,NIL};
  static fdb numfdb = {_CMNUM,CM_SDH,NIL,(pdat) 10,"Message number",NIL,NIL};
  while (!done) {		/* until program wants to exit */
    cmseter ();			/* set error trap */
				/* exit on EOF */
    if (cmcsb._cmerr == CMxEOF) break;
				/* exit afterwards if command line argument */
    if (cmargs (argc,argv)) done = T;
    else prompt ("MS>");	/* prompt */
    cmsetrp ();			/* set reparse trap */
				/* allow message number if have a stream */
    cmdfdb._cmlst = stream ? (fdb *) &numfdb : NIL;
				/* parse command */
    parse (&cmdfdb,&parseval,&used);
    if (used == &numfdb) {	/* check for valid message number */
      if (parseval._pvint >= 1 && parseval._pvint <= nmsgs) {
	confirm ();		/* confirm single message */
				/* set up as current */
	current = parseval._pvint;
	for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
	mail_elt (stream,current)->spare = T;
	sprintf (sequence,"%ld",current);
				/* now output it */
	more (type_message,current);
      }
      else cmerr ("Invalid message number");
    }
				/* else do the command */
    else do_cmd ((void *) parseval._pvint);
  }
}

/* Execute command
 * Accepts: function
 */

void do_cmd (void (*f) (short help))
{
  (*f)((short) NIL);		/* call function with help flag off */
}


/* Execute help for command
 * Accepts: function
 */

void do_help (void (*f) (short help))
{
  (*f)((short) T);		/* call function with help flag on */
}

/* Top level commands */


/* ANSWER command
 * Accepts: help flag
 */

static keywrd anscmds[] = {
  {"ALL",	NIL,	(keyval) T},
  {"SENDER-ONLY",NIL,	(keyval) NIL},
};
static keytab anstab = {(sizeof (anscmds)/sizeof (keywrd)),anscmds};

void c_answer (short help)
{
  if (help) cmxprintf ("\
The ANSWER command composes and sends an answer to the specified messages.\n");
  else {
    char tmp[TMPLEN];
    int i;
    int msgno;
    pval parseval;
    fdb *used;
    static fdb ansfdb = {_CMKEY,NIL,NIL,(pdat) &(anstab),"Answer option, ",
			   "SENDER-ONLY",NIL};
    if (do_sequence (NIL)) for (msgno = 1; msgno <= nmsgs; ++msgno)
      if (mail_elt (stream,msgno)->spare) {
	current = msgno;	/* this is new current message */
	cmseter ();		/* set error trap */
	sprintf (tmp,"Send answer for message %d to: ",msgno);
	prompt (tmp);		/* get reply option */
	cmsetrp ();		/* set reparse trap */
	parse (&ansfdb,&parseval,&used);
	i = parseval._pvint;	/* save user's selection */
	confirm ();
	answer_message (msgno,i);
      }
  }
}

/* BLANK command
 * Accepts: help flag
 */

void c_blank (short help)
{
  if (help) cmxprintf ("Blanks the screen.\n");
  else {
    noise ("SCREEN");
    confirm ();
    cmcls ();			/* zap the screen */
  }
}
/* CHECK command
 * Accepts: help flag
 */

void c_check (short help)
{
  int lnmsgs;
  if (help) cmxprintf ("\
The CHECK command checks to see if any new messages have arrived in the\n\
current mailbox.\n");
  else {
    if (stream) {
      noise ("FOR NEW MESSAGES");
      confirm ();
      lnmsgs = nmsgs;		/* remember old value */
      mail_check (stream);	/* check for new messages */
				/* report no change */
      if (lnmsgs == nmsgs) cmxprintf ("There are no new messages\n");
    }
    else cmerr ("No mailbox is currently open");
  }
}

/* COPY command
 * Accepts: help flag
 */

void c_copy (short help)
{
  if (help) cmxprintf ("\
The COPY command copies the specified messages into the specified mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox on this server",
			   "INBOX",&mbxbrk};
    if (stream) {
      noise ("TO MAILBOX");
				/* parse the mailbox name */
      parse (&mbxfdb,&parseval,&used);
      strcpy (tmp,atmbuf);	/* note the mailbox name */
      if (do_sequence (NIL)) mail_copy (stream,sequence,tmp);
    }
    else cmerr ("No mailbox is currently open");
  }
}

/* CREATE command
 * Accepts: help flag
 */

void c_create (short help)
{
  if (help) cmxprintf ("Creates a new mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox","INBOX",&mbxbrk};
    noise ("NEW MAILBOX NAMED");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);	/* note the mailbox name */
    confirm ();
    mail_create (NIL,tmp);
  }
}


/* DAYTIME command
 * Accepts: help flag
 */

void c_daytime (short help)
{
  if (help) cmxprintf ("Types the current date and time.\n");
  else {
    char tmp[TMPLEN];
    confirm ();
    rfc822_date (tmp);
    cmxprintf (" %s\n",tmp);
  }
}

/* DEBUG command
 * Accepts: help flag
 */

void c_debug (short help)
{
  int state;
  if (help) cmxprintf ("\
The DEBUG command enables debugging information, e.g. protocol telemetry.\n");
  else {
    noise ("PROTOCOL");
    state = onoff ();
    confirm ();
    debug = state;		/* set debugging */
    if (stream) {		/* and frob the stream if one's there */
      if (debug) mail_debug (stream);
      else mail_nodebug (stream);
    }
  }
}


/* DELETE command
 * Accepts: help flag
 */

void c_delete (short help)
{
  if (help) cmxprintf ("\
The DELETE command makes the specified messages be deleted (marked for\n\
removal by a subsequent EXIT or EXPUNGE command).\n");
  else if (do_sequence (NIL)) mail_setflag (stream,sequence,"\\Deleted");
}


/* ECHO command
 * Accepts: help flag
 */

void c_echo (short help)
{
  if (help) cmxprintf ("\
The ECHO command takes a text line as an argument and outputs it to the\n\
terminal.  This is useful in TAKE files.\n");
  else {
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"text to echo back",NIL,NIL};
    parse (&linfdb,&parseval,&used);
    confirm ();
    cmxprintf ("%s\n",atmbuf);
  }
}

/* EXIT command
 * Accepts: help flag
 */

void c_exit (short help)
{
  if (help) cmxprintf ("\
The EXIT command expunges the current mailbox, closes the mailbox, and\n\
exits this program.\n");
  else {
    noise ("PROGRAM");
    confirm ();
				/* expunge if a stream is open */
    if (stream) mail_expunge (stream);
    done = T;			/* let top level know it's time to die */
  }
}


/* EXPUNGE command
 * Accepts: help flag
 */

void c_expunge (short help)
{
  int i;
  if (help) cmxprintf ("\
The EXPUNGE command expunges (permanently removes all deleted messages from)\n\
the current mailbox.\n");
  else {
    if (stream) {
      noise ("MAILBOX");
      confirm ();
      mail_expunge (stream);	/* smash the deleted messages */
				/* invalidate the current sequence */
      for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
      sequence[current = 0] = '\0';
    }
    else cmerr ("No mailbox is currently open");
  }
}

/* FLAG command
 * Accepts: help flag
 */

void c_flag (short help)
{
  if (help) cmxprintf ("\
The FLAG command makes the specified messages be flagged as urgent.\n");
  else if (do_sequence (NIL)) mail_setflag (stream,sequence,"\\Flagged");
}

/* FORWARD command
 * Accepts: help flag
 */

void c_forward (short help)
{
  if (help) cmxprintf ("\
Forwards the specified messages with optional comments to another mailbox.\n");
  else {
    char tmp[TMPLEN];
    unsigned long i,msgno;
    char *text;
    char *s;
    ENVELOPE *msg;
    BODY *body;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    ADDRESS *adr = NIL;
    linfdb._cmhlp = "list of forward recipients in RFC 822 format";
    if (do_sequence (NIL)) {
      while (!adr) {
	cmseter ();		/* set error trap */
	prompt ("To: ");	/* get recipient list */
	cmsetrp ();		/* set reparse trap */
	parse (&linfdb,&parseval,&used);
				/* parse recipient */
	rfc822_parse_adrlist (&adr,atmbuf,curhst);
	confirm ();
      }
      msg = send_init ();	/* get message block */
      msg->to = adr;		/* set to-list */
      if (body = send_text ()) {/* get initial text of comments */
	fs_resize ((void **) &body->contents.text.data,
		   body->contents.text.size + (i = strlen (fwdhdr)));
				/* append the forward header */
	memcpy (body->contents.text.data + body->contents.text.size,fwdhdr,i);
	body->contents.text.size += i;
	for (msgno = 1; msgno <= nmsgs; ++msgno)
	  if (mail_elt (stream,msgno)->spare) {
	    if (!msg->subject) {/* if no subject yet */
	      tmp[0] = '[';	/* build string */
				/* get short from sans trailing spaces */
	      mail_fetchfrom (tmp+1,stream,msgno,FROMLEN);
	      for (s = tmp+FROMLEN; *s == ' '; --s) *s = '\0';
	      *++s = ':'; *++s = ' ';
	      strcpy (++s,(mail_fetchstructure (stream,msgno,NIL))->subject);
	      msg->subject = cpystr (tmp);
	    }
	    mail_parameters (NIL,SET_PREFETCH,(void *) T);
				/* get header of message */
	    text = mail_fetchheader_full (stream,msgno,NIL,&i,NIL);
	    mail_parameters (NIL,SET_PREFETCH,NIL);
				/* resize the forward text */
	    fs_resize ((void **) &body->contents.text.data,
		       body->contents.text.size + i);
				/* append the forward message text */
	    memcpy(body->contents.text.data + body->contents.text.size,text,i);
	    body->contents.text.size += i;
				/* get text of message */
	    text = mail_fetchtext_full (stream,msgno,&i,NIL);
				/* resize the forward text */
	    fs_resize ((void **) &body->contents.text.data,
		       body->contents.text.size + i);
				/* append the forward message text */
	    memcpy(body->contents.text.data + body->contents.text.size,text,i);
	    body->contents.text.size += i;
	  }
	send_level (msg,body);	/* enter send-level */
	mail_free_body (&body);
      }
      mail_free_envelope (&msg);/* flush the message */
    }
  }
}

/* GET command
 * Accepts: help flag
 */

void c_get (short help)
{
  if (help) cmxprintf ("Establish connection to a mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mb2fdb = {_CMFLD,CM_SDH,NIL,NIL,"new mailbox name",NIL,&mbxbrk};
    static fdb mbxfdb = {_CMKEY,NIL,&mb2fdb,(pdat)& (mbxtab),"known mailbox, ",
			 NIL,&mbxbrk};
				/* default is current mailbox or INBOX */
    mbxfdb._cmdef = stream ? stream->mailbox : "INBOX";
    noise ("MAILBOX");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);
    confirm ();
    do_get (tmp);		/* get this mailbox */
  }
}

/* HEADERS command
 * Accepts: help flag
 */

void c_headers (short help)
{
  if (help) cmxprintf ("\
The HEADERS command displays one-line summaries of the specified messages.\n");
  else {
    mail_parameters (NIL,SET_PREFETCH,(void *) T);
				/* parse sequence */
    if (do_sequence (NIL)) more (do_header,NIL);
    mail_parameters (NIL,SET_PREFETCH,NIL);
  }
}


/* HELP command
 * Accepts: help flag
 */

void c_help (short help)
{
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(cmdtab),"Command, ",NIL,NIL};
  static fdb hlpfdb = {_CMCFM,NIL,&cmdfdb,NIL,NIL,NIL,NIL};
  pval parseval;
  fdb *used;
  if (help) cmxprintf ("\
The HELP command gives short descriptions of the MS commands.\n");
  else {
    noise ("WITH");
				/* parse a command */
    parse (&hlpfdb,&parseval,&used);
    if (used == &hlpfdb) cmxprintf ("\
MS is a distributed electronic mail client using the Interactive Mail\n\
Access Protocol described in RFC-1176.  Its user interface is for the most\n\
part a subset of MM.  Please refer to the MM documentation on your system\n\
for more information.\n");
    else {
      void *which = (void *) parseval._pvint;
      confirm ();
      do_help (which);		/* dispatch to appropriate command */
    }
  }
}

/* KEYWORD command
 * Accepts: help flag
 */

void c_keyword (short help)
{
  if (help) cmxprintf ("\
The KEYWORD command makes the specified messages have the specified\n\
keyword.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb flgfdb = {_CMKEY,NIL,NIL,(pdat) &(flgtab),"Keyword, ",NIL,NIL};
    if (stream) {
      noise ("NAME");
				/* parse the keyword */
      parse (&flgfdb,&parseval,&used);
      strcpy (tmp,(char *) parseval._pvint);
      if (do_sequence (NIL)) mail_setflag (stream,sequence,tmp);
    }
    else cmerr ("No mailbox is currently open");
  }
}


/* KILL command
 * Accepts: help flag
 */

void c_kill (short help)
{
  int i;
  if (help) cmxprintf ("\
The KILL command deletes the current message and does an implicit NEXT.\n");
  else {
    if (!stream) cmerr ("No mailbox is currently open");
    else {
      if (!current) cmerr ("No current message");
      else {
	char tmp[TMPLEN];
	noise ("MESSAGE");
	confirm ();
				/* delete the current message */
	sprintf (tmp,"%ld",current);
	mail_setflag (stream,tmp,"\\Deleted");
	if (current >= nmsgs) cmxprintf ("%%No next message\n");
	else {			/* invalidate the current sequence */
	  for (i=1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
				/* select next message and type it */
	  mail_elt (stream,++current)->spare = T;
	  sprintf (sequence,"%ld",current);
	  more (type_message,current);
	}
      }
    }
  }
}

/* LITERAL-TYPE command
 * Accepts: help flag
 */

void c_literal_type (short help)
{
  if (help) cmxprintf ("\
The LITERAL-TYPE command types the specified messages in original format.\n");
  else {
    int i;
    if (!do_sequence (NIL)) return;
				/* type messages */
    for (i = 1; i <= nmsgs; ++i) if (mail_elt (stream,i)->spare)
      more (literal_type_message,i);
  }
}


/* MARK command
 * Accepts: help flag
 */

void c_mark (short help)
{
  if (help) cmxprintf ("\
The MARK command makes the specified messages be marked as seen.\n");
  else if (do_sequence (NIL)) mail_setflag (stream,sequence,"\\Seen");
}

/* MOVE command
 * Accepts: help flag
 */

void c_move (short help)
{
  if (help) cmxprintf ("\
The MOVE command moves the specified messages into the specified mailbox\n\
and then deletes them from this mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox on this server",
			   "INBOX",&mbxbrk};
    if (stream) {
      noise ("TO MAILBOX");
				/* parse the mailbox name */
      parse (&mbxfdb,&parseval,&used);
      strcpy (tmp,atmbuf);	/* note the mailbox name */
      if (do_sequence (NIL)) mail_move (stream,sequence,tmp);
    }
    else cmerr ("No mailbox is currently open");
  }
}

/* NEXT command
 * Accepts: help flag
 */

void c_next (short help)
{
  int i;
  if (help) cmxprintf ("\
The NEXT command types the next message in the mailbox.\n");
  else {
    if (!stream) cmerr ("No mailbox is currently open");
    else {
      if (!current) cmerr ("No current message");
      else {
	noise ("MESSAGE");
	confirm ();
	if (current >= nmsgs)
	  cmxprintf (" Currently at end, message %d\n",current);
	else {
				/* invalidate the current sequence */
	  for (i=1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
				/* select next message */
	  mail_elt (stream,++current)->spare = T;
	  sprintf (sequence,"%ld",current);
	  more (type_message,current);
	}
      }
    }
  }
}

/* POST command
 * Accepts: help flag
 */

void c_post (short help)
{
  if (help) cmxprintf ("Compose and send a new news posting.\n");
  else {
    ENVELOPE *msg = NIL;
    BODY *body;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    static fdb optfdb = {_CMCFM,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    static para_data pd = {NIL,NIL};
    static fdb parafdb = {_CMPARA,NIL,NIL,NIL,NIL,NIL,NIL};
    parafdb._cmdat = (pdat) &pd;
    noise ("TO NEWSGROUP(S)");
    linfdb._cmhlp = "list of newsgroups";
    parse (&linfdb,&parseval,&used);
    if (atmbuf[0]) {		/* if specified recipient on command line */
      msg = send_init ();	/* get a message block and store to list */
      msg->newsgroups = cpystr (atmbuf);
    }
    confirm ();
				/* get a message block now if not one yet */
    if (!msg) msg = send_init ();
    while (!msg->newsgroups) {	/* loop here until he gives a newsgroup */
      prompt ("Newsgroup(s): ");/* prompt for and parse newsgroup-list */
      cmsetrp ();		/* set reparse trap */
      parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
      if (msg->newsgroups) fs_give ((void **) &msg->newsgroups);
      msg->newsgroups = cpystr (atmbuf);
      confirm ();
    }
				/* Subject is optional */
    linfdb._cmlst = (fdb *) &optfdb;
    prompt ("Subject: ");	/* prompt for and get Subject */
    cmsetrp ();			/* set reparse trap */
    linfdb._cmhlp = "single-line subject for this posting";
    parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
    if (msg->subject) fs_give ((void **) &msg->subject);
    if (atmbuf[0]) msg->subject = cpystr (atmbuf);
    confirm ();
    if (body = send_text ()) {	/* get text and send message */
      send_level (msg,body);
      mail_free_body (&body);
    }
    mail_free_envelope (&msg);	/* flush the message */
  }
}

/* PREVIOUS command
 * Accepts: help flag
 */

void c_previous (short help)
{
  int i;
  if (help) cmxprintf ("\
The PREVIOUS command types the previous message in the mailbox.\n");
  else {
    if (!stream) cmerr ("No mailbox is currently open");
    else {
      if (!current) cmerr ("No current message");
      else {
	noise ("MESSAGE");
	confirm ();
	if (current == 1)
	  cmxprintf (" Currently at beginning, message %d\n",current);
	else {
				/* invalidate the current sequence */
	  for (i=1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
				/* select previous message */
	  mail_elt (stream,--current)->spare = T;
	  sprintf (sequence,"%ld",current);
	  more (type_message,current);
	}
      }
    }
  }
}


/* QUIT command
 * Accepts: help flag
 */

void c_quit (short help)
{
  if (help) cmxprintf ("\
The QUIT command closes the mailbox and quits this program without\n\
expunging the mailbox.\n");
  else {
    noise ("PROGRAM");
    confirm ();
    done = T;			/* let top level know it's time to die */
  }
}

/* Read level command table */

static keywrd readcmds[] = {
  {"ANSWER",	KEY_INV,(keyval) r_answer},
  {"BLANK",	NIL,	(keyval) c_blank},
  {"COPY",	NIL,	(keyval) r_copy},
  {"D",		KEY_INV|KEY_ABR,(keyval) 6},
  {"DAYTIME",	NIL,	(keyval) c_daytime},
  {"DEBUG",	NIL,	(keyval) c_debug},
  {"DELETE",	NIL,	(keyval) r_delete},
  {"ECHO",	NIL,	(keyval) c_echo},
  {"FLAG",	NIL,	(keyval) r_flag},
  {"FORWARD",	NIL,	(keyval) r_forward},
  {"H",		KEY_INV|KEY_ABR,(keyval) 11},
  {"HEADER",	NIL,	(keyval) r_headers},
  {"HELP",	NIL,	(keyval) r_help},
  {"K",		KEY_INV|KEY_ABR,(keyval) 15},
  {"KEYWORD",	NIL,	(keyval) r_keyword},
  {"KILL",	NIL,	(keyval) r_kill},
  {"LITERAL-TYPE",NIL,	(keyval) r_literal_type},
  {"MARK",	NIL,	(keyval) r_mark},
  {"MOVE",	NIL,	(keyval) r_move},
  {"NEXT",	NIL,	(keyval) r_next},
  {"PIPE",	NIL,	(keyval) r_pipe},
  {"POST",	NIL,	(keyval) c_post},
  {"PREVIOUS",	NIL,	(keyval) r_previous},
  {"QUIT",	NIL,	(keyval) r_quit},
  {"R",		KEY_INV|KEY_ABR,(keyval) 26},
  {"REMAIL",	NIL,	(keyval) r_remail},
  {"REPLY",	NIL,	(keyval) r_answer},
  {"S",		KEY_INV|KEY_ABR,(keyval) 28},
  {"SEND",	NIL,	(keyval) c_send},
  {"STATUS",	NIL,	(keyval) r_status},
  {"TYPE",	NIL,	(keyval) r_type},
  {"UNANSWER",	NIL,	(keyval) r_unanswer},
  {"UNDELETE",	NIL,	(keyval) r_undelete},
  {"UNFLAG",	NIL,	(keyval) r_unflag},
  {"UNKEYWORD",	NIL,	(keyval) r_unkeyword},
  {"UNMARK",	NIL,	(keyval) r_unmark},
  {"VERSION",	NIL,	(keyval) c_version},
};
static keytab reatab = {(sizeof (readcmds)/sizeof (keywrd)),readcmds};

/* READ command
 * Accepts: help flag
 */

void c_read (short help)
{
  pval parseval;
  fdb *used;
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(reatab),"Command, ","NEXT",NIL};
  int olddone = done;
  if (help) cmxprintf ("\
The READ command enters read sub-mode on the specified messages.\n");
  else if (do_sequence ("NEW")) for (current=1; current <= nmsgs; ++current)
    if (mail_elt (stream,current)->spare) {
				/* make sure we have flags & envelope */
      mail_fetchstructure (stream,current,NIL);
      if (mail_elt (stream,current)->deleted)
	cmxprintf (" Message %d deleted\n",current);
      else {
	cmcls ();		/* zap the screen */
	more (type_message,current);
      }
      done = NIL;		/* not done yet */
      while (!done) {
	cmseter ();		/* set error trap */
				/* exit on EOF */
	if (cmcsb._cmerr == CMxEOF) break;
	prompt ("MS-Read>");	/* prompt */
	cmsetrp ();		/* set reparse trap */
				/* parse command */
	parse (&cmdfdb,&parseval,&used);
	do_cmd ((void *) parseval._pvint);
      }
      if (done > 0) break;	/* negative means NEXT command */
    }
  sequence[current = 0] = '\0';	/* invalidate sequence */
  done = olddone;		/* cancel done status */
}

/* REMAIL command
 * Accepts: help flag
 */

void c_remail (short help)
{
  if (help) cmxprintf ("Remail the specified messages to another mailbox.\n");
  else {
    int msgno;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    ADDRESS *adr = NIL;
    linfdb._cmhlp = "list of remail recipients in RFC 822 format";
    if (do_sequence (NIL)) {
      while (!adr) {
	cmseter ();		/* set error trap */
	prompt ("To: ");	/* get recipient list */
	cmsetrp ();		/* set reparse trap */
	parse (&linfdb,&parseval,&used);
				/* parse recipient */
	rfc822_parse_adrlist (&adr,atmbuf,curhst);
	confirm ();
      }
      for (msgno = 1; msgno <= nmsgs; ++msgno)
	if (mail_elt (stream,msgno)->spare)
	  remail_message ((current = msgno),adr);
      mail_free_address (&adr);	/* flush the address */
    }
  }
}

/* REMOVE command
 * Accepts: help flag
 */

void c_remove (short help)
{
  if (help) cmxprintf ("Removes an existing mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox",NIL,&mbxbrk};
    noise ("MAILBOX NAMED");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);	/* note the mailbox name */
    confirm ();
    mail_delete (NIL,tmp);
  }
}

/* RENAME command
 * Accepts: help flag
 */

void c_rename (short help)
{
  if (help) cmxprintf ("Renames an existing mailbox.\n");
  else {
    char tmp[TMPLEN],tmpx[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox",NIL,&mbxbrk};
    noise ("MAILBOX NAMED");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);	/* note the mailbox name */
    noise ("TO");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmpx,atmbuf);	/* note the mailbox name */
    confirm ();
    mail_rename (NIL,tmp,tmpx);
  }
}

/* SEND command
 * Accepts: help flag
 */

void c_send (short help)
{
  if (help) cmxprintf ("Compose and send a message.\n");
  else {
    ENVELOPE *msg = NIL;
    BODY *body;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    static fdb optfdb = {_CMCFM,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    static para_data pd = {NIL,NIL};
    static fdb parafdb = {_CMPARA,NIL,NIL,NIL,NIL,NIL,NIL};
    parafdb._cmdat = (pdat) &pd;
    noise ("MESSAGE TO");
    linfdb._cmhlp = "list of primary recipients in RFC 822 format";
    parse (&linfdb,&parseval,&used);
    if (atmbuf[0]) {		/* if specified recipient on command line */
      msg = send_init ();	/* get a message block and store to list */
      rfc822_parse_adrlist (&msg->to,atmbuf,curhst);
    }
    confirm ();
				/* get a message block now if not one yet */
    if (!msg) msg = send_init ();
    if (!msg->to) {		/* if no command line do hand-holding */
      while (!msg->to) {	/* loop here until he gives a To-list */
	prompt ("To: ");	/* prompt for and parse To-list */
	cmsetrp ();		/* set reparse trap */
	parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
	if (msg->to) mail_free_address (&msg->to);
	rfc822_parse_adrlist (&msg->to,atmbuf,curhst);
	confirm ();
      }
				/* cc and Subject are optional */
      linfdb._cmlst = (fdb *) &optfdb;
      prompt ("cc: ");		/* prompt for and parse cc-list */
      cmsetrp ();		/* set reparse trap */
      linfdb._cmhlp = "list of secondary recipients in RFC 822 format";
      parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
      if (msg->cc) mail_free_address (&msg->cc);
      if (atmbuf[0]) rfc822_parse_adrlist (&msg->cc,atmbuf,curhst);
      confirm ();
    }
    prompt ("Subject: ");	/* prompt for and get Subject */
    cmsetrp ();			/* set reparse trap */
    linfdb._cmhlp = "single-line subject for this message";
    parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
    if (msg->subject) fs_give ((void **) &msg->subject);
    if (atmbuf[0]) msg->subject = cpystr (atmbuf);
    confirm ();
    if (body = send_text ()) {	/* get text and send message */
      send_level (msg,body);
      mail_free_body (&body);
    }
    mail_free_envelope (&msg);	/* flush the message */
  }
}

/* STATUS command
 * Accepts: help flag
 */

void c_status (short help)
{
  if (help) cmxprintf ("Type status of current mailbox.\n");
  else {
    noise ("OF CURRENT MAILBOX");
    confirm ();
    if (stream) {
      do_status (stream);	/* output the status */
      if (sequence[0]) cmxprintf (" Current sequence: %s\n",sequence);
    }
    else cmxprintf ("%%No mailbox is currently open\n");
  }
}

/* SUBSCRIBE command
 * Accepts: help flag
 */

void c_subscribe (short help)
{
  if (help) cmxprintf ("Subscribe to a mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox",NIL,&mbxbrk};
    noise ("TO MAILBOX");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);	/* note the mailbox name */
    confirm ();
    mail_subscribe (NIL,tmp);
  }
}

/* TAKE command
 * Accepts: help flag
 */

void c_take (short help)
{
  if (help) cmxprintf ("Take commands from a file.\n");
  else cmtake (takelevel);
}


/* Routine called by TAKE to invoke top level */

int takelevel ()
{
  toplevel (NIL,NIL);		/* invoke top level */
  return 0;
}


/* TYPE command
 * Accepts: help flag
 */

void c_type (short help)
{
  if (help) cmxprintf ("The TYPE command types the specified messages.\n");
  else {
    int i;
    if (!do_sequence (NIL)) return;
    for (i = 1; i <= nmsgs; ++i) if (mail_elt (stream,i)->spare)
      more (type_message,i);
  }
}

/* UNANSWER command
 * Accepts: help flag
 */

void c_unanswer (short help)
{
  if (help) cmxprintf ("\
The UNANSWER command makes the specified messages not be answered.\n");
  else if (do_sequence (NIL)) mail_clearflag (stream,sequence,"\\Answered");
}


/* UNDELETE command
 * Accepts: help flag
 */

void c_undelete (short help)
{
  if (help) cmxprintf ("\
The UNDELETE command makes the specified messages not be deleted.\n");
  else if (do_sequence (NIL)) mail_clearflag (stream,sequence,"\\Deleted");
}


/* UNFLAG command
 * Accepts: help flag
 */

void c_unflag (short help)
{
  if (help) cmxprintf ("\
The UNFLAG command makes the specified messages not be flagged as urgent.\n");
  else if (do_sequence (NIL)) mail_clearflag (stream,sequence,"\\Flagged");
}

/* UNKEYWORD command
 * Accepts: help flag
 */

void c_unkeyword (short help)
{
  if (help) cmxprintf ("\
The UNKEYWORD command makes the specified messages not have the specified\n\
keyword.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb flgfdb = {_CMKEY,NIL,NIL,(pdat) &(flgtab),"Keyword, ",NIL,NIL};
    if (stream) {
      noise ("NAME");
				/* parse the keyword */
      parse (&flgfdb,&parseval,&used);
      strcpy (tmp,(char *) parseval._pvint);
      if (do_sequence (NIL)) mail_clearflag (stream,sequence,tmp);
    }
    else cmerr ("No mailbox is currently open");
  }
}


/* UNMARK command
 * Accepts: help flag
 */

void c_unmark (short help)
{
  if (help) cmxprintf ("\
The UNMARK command makes the specified messages not be marked as seen.\n");
  else if (do_sequence (NIL)) mail_clearflag (stream,sequence,"\\Seen");
}

/* UNSUBSCRIBE command
 * Accepts: help flag
 */

void c_unsubscribe (short help)
{
  if (help) cmxprintf ("Unsubscribe from a mailbox.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mb2fdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox name",NIL,&mbxbrk};
    static fdb mbxfdb = {_CMKEY,NIL,&mb2fdb,(pdat)& (mbxtab),"known mailbox, ",
			 NIL,&mbxbrk};
    noise ("FROM MAILBOX");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (tmp,atmbuf);	/* note the mailbox name */
    confirm ();
    mail_unsubscribe (NIL,tmp);
  }
}


/* VERSION command
 * Accepts: help flag
 */

extern char *UW_copyright;

void c_version (short help)
{
  if (help) cmxprintf ("Display the current version of this program.\n");
  else {
    noise ("OF MS");
    confirm ();
    do_version ();
    cmxprintf ("\n%s\n%s",UW_copyright,ccmd_copyright);
  }
}

/* Read command level */


/* ANSWER command
 * Accepts: help flag
 */

void r_answer (short help)
{
  if (help) cmxprintf ("\
The REPLY command composes and sends an answer to this message.\n");
  else {
    int i;
    pval parseval;
    fdb *used;
    static fdb ansfdb = {_CMKEY,NIL,NIL,(pdat) &(anstab),"Answer option, ",
			   "SENDER-ONLY",NIL};
    noise ("TO");		/* get reply option */
    parse (&ansfdb,&parseval,&used);
    i = parseval._pvint;	/* save user's selection */
    confirm ();
    answer_message (current,i);	/* do the answer and mark the message */
  }
}

/* COPY command
 * Accepts: help flag
 */

void r_copy (short help)
{
  if (help) cmxprintf ("\
The COPY command copies this message into the specified mailbox.\n");
  else {
    char tmp[TMPLEN];
    char copybox[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox on this server",
			   "INBOX",&mbxbrk};
    noise ("TO MAILBOX");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (copybox,atmbuf);	/* note the mailbox name */
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_copy (stream,tmp,copybox);
  }
}

/* DELETE command
 * Accepts: help flag
 */

void r_delete (short help)
{
  if (help) cmxprintf ("\
The DELETE command makes this message be deleted (marked for\n\
removal by a subsequent EXIT or EXPUNGE command).\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_setflag (stream,tmp,"\\Deleted");
  }
}


/* FLAG command
 * Accepts: help flag
 */

void r_flag (short help)
{
  if (help) cmxprintf ("\
The FLAG command makes this message be flagged as urgent.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_setflag (stream,tmp,"\\Flagged");
  }
}

/* FORWARD command
 * Accepts: help flag
 */

void r_forward (short help)
{
  if (help) cmxprintf ("\
Forwards this message with optional comments to another mailbox.\n");
  else {
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    char tmp[TMPLEN];
    long i;
    char *hdr,*text,*s;
    ENVELOPE *msg;
    BODY *body;
    ADDRESS *adr = NIL;
    noise ("MESSAGE TO");
    linfdb._cmhlp = "list of forward recipients in RFC 822 format";
    parse (&linfdb,&parseval,&used);
				/* parse recipient */
    rfc822_parse_adrlist (&adr,atmbuf,curhst);
    if (!adr) {
      cmerr ("No forward recipient specified");
      return;
    }
    confirm ();
    msg = send_init ();		/* get message block */
    msg->to = adr;		/* set to-list */
    tmp[0] = '[';		/* build string */
				/* get short from sans trailing spaces */
    mail_fetchfrom (tmp+1,stream,current,FROMLEN);
    for (s = tmp+FROMLEN; *s == ' '; --s) *s = '\0';
    *++s = ':'; *++s = ' ';
    strcpy (++s,(mail_fetchstructure (stream,current,NIL))->subject);
    msg->subject = cpystr (tmp);/* set up subject */
				/* get header of message */
    mail_parameters (NIL,SET_PREFETCH,(void *) T);
    hdr = cpystr (mail_fetchheader (stream,current));
    mail_parameters (NIL,SET_PREFETCH,NIL);
				/* get body of message */
    text = mail_fetchtext (stream,current);
    if (body = send_text ()) {	/* get initial text of comments */
				/* resize the forward text */
      fs_resize ((void **) &body->contents.text.data,
		 body->contents.text.size +
		 (i = strlen (fwdhdr) + strlen (hdr) + strlen (text)));
      sprintf (body->contents.text.data + body->contents.text.size,
	       "%s%s%s",fwdhdr,hdr,text);
      body->contents.text.size += i;
      send_level (msg,body);	/* enter send-level */
      mail_free_body (&body);
    }
    fs_give ((void **) &hdr);	/* free header */
    mail_free_envelope (&msg);	/* flush the message */
  }
}

/* HEADER command
 * Accepts: help flag
 */

void r_headers (short help)
{
  if (help) cmxprintf ("\
The HEADERS command displays one-line summaries of this message.\n");
  else {
    noise ("OF CURRENT MESSAGE");
    confirm ();
    header_message (stdout,current);
  }
}


/* HELP command
 * Accepts: help flag
 */

void r_help (short help)
{
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(reatab),"Command, ",NIL,NIL};
  static fdb hlpfdb = {_CMCFM,NIL,&cmdfdb,NIL,NIL,NIL,NIL};
  pval parseval;
  fdb *used;
  if (help) cmxprintf ("\
The HELP command gives short descriptions of the MS read level commands.\n");
  else {
    noise ("WITH");
				/* parse a command */
    parse (&hlpfdb,&parseval,&used);
    if (used == &hlpfdb) cmxprintf ("\
MS is at read level, in which commands apply only to the current message.\n");
    else {
      void *which = (void *) parseval._pvint;
      confirm ();
      do_help (which);		/* dispatch to appropriate command */
    }
  }
}

/* KEYWORD command
 * Accepts: help flag
 */

void r_keyword (short help)
{
  if (help) cmxprintf ("\
The KEYWORD command makes this message have the specified keyword.\n");
  else {
    char key[TMPLEN];
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb flgfdb = {_CMKEY,NIL,NIL,(pdat) &(flgtab),"Keyword, ",NIL,NIL};
    noise ("NAME");
				/* parse the keyword */
    parse (&flgfdb,&parseval,&used);
    strcpy (key,(char *) parseval._pvint);
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_setflag (stream,tmp,key);
  }
}


/* KILL command
 * Accepts: help flag
 */

void r_kill (short help)
{
  if (help) cmxprintf ("\
The KILL command deletes the current message and does an implicit NEXT.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);	/* delete the current message */
    mail_setflag (stream,tmp,"\\Deleted");
    done = -1;			/* exit this message */
  }
}

/* LITERAL-TYPE command
 * Accepts: help flag
 */

void r_literal_type (short help)
{
  if (help) cmxprintf ("\
The LITERAL-TYPE command types this message in original form.\n");
  else {
    noise ("MESSAGE");
    confirm ();
    if (stream) {
				/* type the message */
      if (current) more (literal_type_message,current);
      else cmxprintf ("%%No current message\n");
    }
    else cmxprintf ("%%No mailbox is currently open\n");
  }
}


/* MARK command
 * Accepts: help flag
 */

void r_mark (short help)
{
  if (help) cmxprintf ("\
The MARK command makes this message be marked as seen.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_setflag (stream,tmp,"\\Seen");
  }
}

/* MOVE command
 * Accepts: help flag
 */

void r_move (short help)
{
  if (help) cmxprintf ("\
The MOVE command moves this message into the specified mailbox\n\
and then deletes them from this mailbox.\n");
  else {
    char tmp[TMPLEN];
    char copybox[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb mbxfdb = {_CMFLD,CM_SDH,NIL,NIL,"mailbox on this server",
			   "INBOX",&mbxbrk};
    noise ("TO MAILBOX");
				/* parse the mailbox name */
    parse (&mbxfdb,&parseval,&used);
    strcpy (copybox,atmbuf);	/* note the mailbox name */
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_move (stream,tmp,copybox);
  }
}

/* NEXT command
 * Accepts: help flag
 */

void r_next (short help)
{
  if (help) cmxprintf ("\
The NEXT command goes to the next message in the sequence.\n");
  else {
    noise ("MESSAGE");
    confirm ();
    done = -1;			/* let read level know it's time to next */
  }
}

/* PIPE command
 * Accepts: help flag
 */

void r_pipe (short help)
{
  if (help) cmxprintf ("Pipe to a program.\n");
  else {
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    static para_data pd = {NIL,NIL};
    static fdb parafdb = {_CMPARA,NIL,NIL,NIL,NIL,NIL,NIL};
    parafdb._cmdat = (pdat) &pd;
    noise ("TO PROGRAM");
    linfdb._cmhlp = "program";
    linfdb._cmdef = "more";
    parse (&linfdb,&parseval,&used);
    strcpy (tmp,atmbuf);
    confirm ();
    if (stream) {		/* type the message */
      if (current) {
	FILE *pipe = popen (tmp,"w");
	if (pipe) {
	  fflush (stdout);	/* make sure regular output is done */
	  fflush (stderr);
	  critical = T;		/* ignore CTRL/C while running */
	  literal_type_message (pipe,current);
	  fflush (pipe);	/* wait for output to be done */
	  pclose (pipe);	/* close the pipe */
	  critical = NIL;	/* allow CTRL/C again */
	}
      }
      else cmxprintf ("%%No current message\n");
    }
    else cmxprintf ("%%No mailbox is currently open\n");
  }
}

/* PREVIOUS command
 * Accepts: help flag
 */

void r_previous (short help)
{
  if (help) cmxprintf ("\
The PREVIOUS command types the previous message in the mailbox.\n");
  else {
    int i;
    noise ("MESSAGE");
    confirm ();
				/* look for earlier current message */
    for (i = current-1; i >= 1; --i) if (mail_elt (stream,i)->spare) {
      current = i;		/* this is the new current message */
      if (mail_elt (stream,current)->deleted)
	cmxprintf (" Message %d deleted\n",current);
      else {
	cmcls ();		/* zap the screen */
	more (type_message,current);
      }
      return;			/* skip error message */
    }
    cmxprintf (" Currently at beginning, message %d\n",current);
  }
}

/* QUIT command
 * Accepts: help flag
 */

void r_quit (short help)
{
  if (help) cmxprintf ("\
The QUIT command exits read level, returning to top level.\n");
  else {
    noise ("READ LEVEL");
    confirm ();
    done = T;			/* let read level know it's time to die */
  }
}


/* REMAIL command
 * Accepts: help flag
 */

void r_remail (short help)
{
  if (help) cmxprintf ("Remail this message to another mailbox.\n");
  else {
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,NIL,NIL,NIL};
    ADDRESS *adr = NIL;
    noise ("MESSAGE TO");
    linfdb._cmhlp = "list of remail recipients in RFC 822 format";
    parse (&linfdb,&parseval,&used);
				/* parse recipient */
    rfc822_parse_adrlist (&adr,atmbuf,curhst);
    if (!adr) {
      cmerr ("No remail recipient specified");
      return;
    }
    confirm ();
    remail_message (current,adr);
    mail_free_address (&adr);	/* flush the address */
  }
}

/* STATUS command
 * Accepts: help flag
 */

void r_status (short help)
{
  if (help) cmxprintf ("Type status of current mailbox.\n");
  else {
    noise ("OF CURRENT MAILBOX");
    confirm ();
    if (stream) {
      do_status (stream);	/* output the status */
      if (current) cmxprintf (" Currently at message %d\n",current);
    }
    else cmxprintf ("%%No mailbox is currently open\n");
  }
}


/* TYPE command
 * Accepts: help flag
 */

void r_type (short help)
{
  if (help) cmxprintf ("The TYPE command types this message.\n");
  else {
    noise ("MESSAGE");
    confirm ();
    if (stream) {
				/* type the message */
      if (current) more (type_message,current);
      else cmxprintf ("%%No current message\n");
    }
    else cmxprintf ("%%No mailbox is currently open\n");
  }
}

/* UNANSWER command
 * Accepts: help flag
 */

void r_unanswer (short help)
{
  if (help) cmxprintf ("\
The UNANSWER command makes this message not be answered.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_clearflag (stream,tmp,"\\Answered");
  }
}


/* UNDELETE command
 * Accepts: help flag
 */

void r_undelete (short help)
{
  if (help) cmxprintf ("\
The UNDELETE command makes this message not be deleted.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_clearflag (stream,tmp,"\\Deleted");
  }
}


/* UNFLAG command
 * Accepts: help flag
 */

void r_unflag (short help)
{
  if (help) cmxprintf ("\
The UNFLAG command makes this message not be flagged as urgent.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_clearflag (stream,tmp,"\\Flagged");
  }
}

/* UNKEYWORD command
 * Accepts: help flag
 */

void r_unkeyword (short help)
{
  if (help) cmxprintf ("\
The UNKEYWORD command makes this message not have the specified keyword.\n");
  else {
    char key[TMPLEN];
    char tmp[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb flgfdb = {_CMKEY,NIL,NIL,(pdat) &(flgtab),"Keyword, ",NIL,NIL};
    noise ("NAME");
				/* parse the keyword */
    parse (&flgfdb,&parseval,&used);
    strcpy (key,(char *) parseval._pvint);
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_clearflag (stream,tmp,key);
  }
}


/* UNMARK command
 * Accepts: help flag
 */

void r_unmark (short help)
{
  if (help) cmxprintf ("\
The UNMARK command makes this message not be marked as seen.\n");
  else {
    char tmp[TMPLEN];
    noise ("MESSAGE");
    confirm ();
    sprintf (tmp,"%ld",current);
    mail_clearflag (stream,tmp,"\\Seen");
  }
}

/* Send command level */


/* Send level command table */

static keywrd sendcmds[] = {
  {"BCC",	NIL,	(keyval) s_bcc},
  {"BLANK",	NIL,	(keyval) c_blank},
  {"CC",	NIL,	(keyval) s_cc},
  {"D",		KEY_INV|KEY_ABR,(keyval) 6},
  {"DAYTIME",	NIL,	(keyval) c_daytime},
  {"DEBUG",	NIL,	(keyval) c_debug},
  {"DISPLAY",	NIL,	(keyval) s_display},
  {"ECHO",	NIL,	(keyval) c_echo},
  {"ERASE",	NIL,	(keyval) s_erase},
  {"HELP",	NIL,	(keyval) s_help},
  {"LITERAL-TYPE",NIL,	(keyval) r_literal_type},
  {"NEWSGROUPS",NIL,	(keyval) s_newsgroups},
  {"QUIT",	NIL,	(keyval) s_quit},
  {"REMOVE",	NIL,	(keyval) s_remove},
  {"SEND",	NIL,	(keyval) s_send},
  {"STATUS",	NIL,	(keyval) r_status},
  {"SUBJECT",	NIL,	(keyval) s_subject},
  {"TO",	NIL,	(keyval) s_to},
  {"TYPE",	NIL,	(keyval) r_type},
};
static keytab sndtab = {(sizeof (sendcmds)/sizeof (keywrd)),sendcmds};


/* Send command level
 * Accepts: message
 */

void send_level (ENVELOPE *msg,BODY *body)
{
  pval parseval;
  fdb *used;
  static fdb sndfdb = {_CMKEY,NIL,NIL,(pdat) &(sndtab),"Command, ","SEND",NIL};
  int olddone = done;		/* hold calling done */
  done = NIL;			/* not done in send yet */
  while (!done) {		/* loop until done */
    cmseter ();			/* set error trap */
				/* exit on EOF */
    if (cmcsb._cmerr == CMxEOF) break;
    prompt ("MS-Send>");	/* prompt */
    cmsetrp ();			/* set reparse trap */
				/* parse command */
    parse (&sndfdb,&parseval,&used);
				/* do the command */
    do_scmd ((void *) parseval._pvint,msg,body);
  }
  done = olddone;		/* so we don't bust out of top level */
}

/* Execute command
 * Accepts: function
 *	    message
 */

void do_scmd (int (*f)(short help,ENVELOPE *msg,BODY *body),
	      ENVELOPE *msg,BODY *body)
{
  (*f)((short) NIL,msg,body);	/* call function with help flag off */
}


/* Execute help for command
 * Accepts: function
 *	    message
 */

void do_shelp (int (*f)(short help,ENVELOPE *msg,BODY *body),
	       ENVELOPE *msg,BODY *body)
{
  (*f)((short) T,msg,body);	/* call function with help flag on */
}

/* Send command execution routines */


/* BCC command
 * Accepts: help flag
 *	    message
 */

void s_bcc (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The BCC command adds recipients to the blind carbon copy (bcc) list.\n");
  else {
    ADDRESS *adr = NIL;
    ADDRESS *lst;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"blind carbon copy list",
			   NIL,NIL};
    noise ("TO");
    parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
    if (adr) mail_free_address (&adr);
				/* parse the address list */
    rfc822_parse_adrlist (&adr,atmbuf,curhst);
    confirm ();
    if (lst = msg->bcc) {	/* if a bcc list already */
				/* run down the list until the end */
      while (lst->next) lst = lst->next;
      lst->next = adr;		/* and link at the end of the list */
    }
    else msg->bcc = adr;	/* else this is the bcc list */
  }
}

/* CC command
 * Accepts: help flag
 *	    message
 */

void s_cc (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The CC command adds recipients to the carbon copy (cc) list.\n");
  else {
    ADDRESS *adr = NIL;
    ADDRESS *lst;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"carbon copy list",
			   NIL,NIL};
    noise ("TO");
    parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
    if (adr) mail_free_address (&adr);
				/* parse the address list */
    rfc822_parse_adrlist (&adr,atmbuf,curhst);
    confirm ();
    if (lst = msg->cc) {	/* if a cc list already */
				/* run down the list until the end */
      while (lst->next) lst = lst->next;
      lst->next = adr;		/* and link at the end of the list */
    }
    else msg->cc = adr;		/* else this is the cc list */
  }
}


/* DISPLAY command
 * Accepts: help flag
 *	    message
 */

void s_display (short help,ENVELOPE *msg,BODY *body)
{
  struct display_msg message;
  if (help) cmxprintf ("\
The DISPLAY command displays the header and text of the message.\n");
  else {
    noise ("MESSAGE");
    confirm ();
    message.env = msg;
    message.bdy = body;
    more (do_display,(long) &message);
  }
}

/* ERASE command
 * Accepts: help flag
 *	    message
 */

#define ERBCC 0
#define ERCC 1
#define ERRNEWS 2
#define ERTO 3

static keywrd eracmds[] = {
  {"BCC",	NIL,	(keyval) ERBCC},
  {"CC",	NIL,	(keyval) ERCC},
  {"NEWSGROUPS",NIL,	(keyval) ERRNEWS},
  {"TO",	NIL,	(keyval) ERTO},
};
static keytab eratab = {(sizeof (eracmds)/sizeof (keywrd)),eracmds};


void s_erase (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The ERASE command erases the specified recipient list.\n");
  else {
    int i;
    pval parseval;
    fdb *used;
    static fdb erafdb = {_CMKEY,NIL,NIL,(pdat) &(eratab),"Address list, ",
			   NIL,NIL};
    noise ("LIST");
    parse (&erafdb,&parseval,&used);
    i = parseval._pvint;	/* save user's selection */
    confirm ();
    switch (i) {		/* now zap the appropriate list */
    case ERBCC:			/* bcc list */
      if (msg->bcc) mail_free_address (&msg->bcc);
      break;
    case ERCC:			/* cc list */
      if (msg->cc) mail_free_address (&msg->cc);
      break;
    case ERRNEWS:
      if (msg->newsgroups) fs_give ((void **) &msg->newsgroups);
      break;
    case ERTO:			/* to list */
    default:
      if (msg->to) mail_free_address (&msg->to);
      break;
    }
  }
}

/* HELP command
 * Accepts: help flag
 *	    message
 */

void s_help (short help,ENVELOPE *msg,BODY *body)
{
  static fdb sndfdb = {_CMKEY,NIL,NIL,(pdat) &(sndtab),"Command, ",NIL,NIL};
  static fdb hlpfdb = {_CMCFM,NIL,&sndfdb,NIL,NIL,NIL,NIL};
  pval parseval;
  fdb *used;
  if (help) cmxprintf ("\
The HELP command gives short descriptions of the send-level MS commands.\n");
  else {
    noise ("WITH");
				/* parse a command */
    parse (&hlpfdb,&parseval,&used);
    if (used == &hlpfdb) cmxprintf ("\
You are at MS send level, which allows you to change various parts of your\n\
message prior to sending it.\n");
    else {
      void *which = (void *) parseval._pvint;
      confirm ();
      do_shelp (which,msg,body);/* dispatch to appropriate command */
    }
  }
}


/* NEWSGROUPS command
 * Accepts: help flag
 *	    message
 */

void s_newsgroups (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The NEWSGROUPS command sets a new newsgroups list.\n");
  else {
    char newsgroups[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"list of newsgroups",NIL,NIL};
				/* get newsgroups */
    parse (&linfdb,&parseval,&used);
    strcpy (newsgroups,atmbuf);	/* copy newsgroups into temp buffer */
    confirm ();
				/* flush the old newsgroups */
    if (msg->newsgroups) fs_give ((void **) &msg->newsgroups);
				/* set new newsgroups */
    msg->newsgroups = newsgroups[0] ? cpystr (newsgroups) : NIL;
  }
}

/* QUIT command
 * Accepts: help flag
 *	    message
 */

void s_quit (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The QUIT command aborts the message being composed and returns to top level.\
\n");
  else {
    noise ("SEND LEVEL");
    confirm ();
    done = T;			/* let send level know it's time to die */
    quitted = T;		/* flag to zap answered */
  }
}

/* REMOVE command
 * Accepts: help flag
 *	    message
 */

void s_remove (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The REMOVE command removes the specified recipient.\n");
  else {
    char text[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"recipient mailbox",NIL,NIL};
    noise ("RECIPIENT");
				/* get subject */
    parse (&linfdb,&parseval,&used);
    strcpy (text,atmbuf);	/* copy text into temp buffer */
    confirm ();
				/* remove the recipient from all lists */
    msg->to = remove_adr (msg->to,text);
    msg->cc = remove_adr (msg->cc,text);
    msg->bcc = remove_adr (msg->bcc,text);
  }
}

/* SEND command
 * Accepts: help flag
 *	    message
 */

void s_send (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("Send this message.\n");
  else {
    long i,j;
    unsigned char *text = (unsigned char *) body->contents.text.data;
    long size = body->contents.text.size;
    unsigned char *src,*dst;
    noise ("MESSAGE");
    confirm ();
				/* count LF's in string */
    for (i = j = 0; j < size; j++) if (text[j] == '\012') i++;
				/* make buffer that will hold CRLF form */
    body->contents.text.data = (char *)
      fs_get (body->contents.text.size = 1 + i + size);
				/* copy string, inserting CR's */
    for (j = 0, src = text,dst = (unsigned char *) body->contents.text.data;
	 j < size; *dst++ = src[j++]) if (src[j] == '\012') *dst++ = '\015';
    *dst = '\0';		/* tie off destination */
    send_message (msg,body);	/* send the message */
				/* restore original */
    body->contents.text.data = (char *) text;
    body->contents.text.size = size;
    quitted = NIL;		/* flag to zap answered */
  }
}

/* SUBJECT command
 * Accepts: help flag
 *	    message
 */

void s_subject (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The SUBJECT command sets the subject of this message.\n");
  else {
    char subject[TMPLEN];
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"subject text",NIL,NIL};
				/* get subject */
    parse (&linfdb,&parseval,&used);
    strcpy (subject,atmbuf);	/* copy subject into temp buffer */
    confirm ();
				/* flush the old subject */
    if (msg->subject) fs_give ((void **) &msg->subject);
				/* set new subject */
    msg->subject = subject[0] ? cpystr (subject) : NIL;
  }
}

/* TO command
 * Accepts: help flag
 *	    message
 */

void s_to (short help,ENVELOPE *msg,BODY *body)
{
  if (help) cmxprintf ("\
The TO command adds recipients to the primary recipient (to) list.\n");
  else {
    ADDRESS *adr = NIL;
    ADDRESS *lst;
    pval parseval;
    fdb *used;
    static fdb linfdb = {_CMTXT,CM_SDH,NIL,NIL,"to list",
			   NIL,NIL};
    noise ("TO");
    parse (&linfdb,&parseval,&used);
				/* free old one in case reparse */
    if (adr) mail_free_address (&adr);
				/* parse the address list */
    rfc822_parse_adrlist (&adr,atmbuf,curhst);
    confirm ();
    if (lst = msg->to) {	/* if a to list already */
				/* run down the list until the end */
      while (lst->next) lst = lst->next;
      lst->next = adr;		/* and link at the end of the list */
    }
    else msg->to = adr;		/* else this is the to list */
  }
}

/* Sequence parser  */


#define s_date 1
#define s_flag 2
#define s_string 3

static keywrd seqcmds[] = {
  {"ALL",	NIL,	(keyval) NIL},
  {"ANSWERED",	NIL,	(keyval) NIL},
  {"BCC",	NIL,	(keyval) s_string},
  {"BEFORE",	NIL,	(keyval) s_date},
  {"BODY",	NIL,	(keyval) s_string},
  {"CC",	NIL,	(keyval) s_string},
  {"DELETED",	NIL,	(keyval) NIL},
  {"FLAGGED",	NIL,	(keyval) NIL},
  {"FROM",	NIL,	(keyval) s_string},
  {"KEYWORD",	NIL,	(keyval) s_flag},
  {"NEW",	NIL,	(keyval) NIL},
  {"OLD",	NIL,	(keyval) NIL},
  {"ON",	NIL,	(keyval) s_date},
  {"RECENT",	NIL,	(keyval) NIL},
  {"SEEN",	NIL,	(keyval) NIL},
  {"SINCE",	NIL,	(keyval) s_date},
  {"SUBJECT",	NIL,	(keyval) s_string},
  {"TEXT",	NIL,	(keyval) s_string},
  {"TO",	NIL,	(keyval) s_string},
  {"UNANSWERED",NIL,	(keyval) NIL},
  {"UNDELETED",	NIL,	(keyval) NIL},
  {"UNFLAGGED",	NIL,	(keyval) NIL},
  {"UNKEYWORD",	NIL,	(keyval) s_flag},
  {"UNSEEN",	NIL,	(keyval) NIL},
};
static keytab seqtab = {(sizeof (seqcmds)/sizeof (keywrd)),seqcmds};

static keywrd seq2cmds[] = {
  {"LAST",	NIL,	(keyval) T},
};
static keytab seq2tab = {(sizeof (seq2cmds)/sizeof (keywrd)),seq2cmds};

/* Sequence parser
 * Accepts: default string
 * Returns: first element of the sequence as a success flag
 */

char do_sequence (char *def)
{
  int i,j;
  int msgno;
  char tmp[TMPLEN];
  char tmpx[TMPLEN];
  char *seq;
  pval parseval;
  fdb *used;
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(seqtab),"Message sequence, ",
			 NIL,NIL};
  static fdb cm2fdb = {_CMKEY,NIL,NIL,(pdat) &(seq2tab),NIL,NIL,NIL};
  static fdb flgfdb = {_CMKEY,NIL,NIL,(pdat) &(flgtab),"Keyword, ",NIL,NIL};
  static fdb numfdb = {_CMNUM,NIL,NIL,(pdat) 10,NIL,NIL,NIL};
  static fdb cfmfdb = {_CMCFM,NIL,NIL,NIL,NIL,NIL,NIL};
  static fdb datfdb = {_CMTAD,DTP_NTI,NIL,NIL,NIL,NIL};
  static fdb qstfdb = {_CMQST,NIL,NIL,NIL,NIL,NIL,NIL};
  static fdb fldfdb = {_CMFLD,NIL,NIL,NIL,NIL,NIL,NIL};
  static fdb cmafdb = {_CMTOK,CM_SDH,NIL,",","comma for another number",NIL,
			 NIL};
  static fdb clnfdb = {_CMTOK,CM_SDH,NIL,":","colon for a range",NIL,NIL};
  if (!stream) {
    cmerr ("No mailbox is currently open");
    return (NIL);
  }
  qstfdb._cmlst = (fdb *) &fldfdb;
  cmdfdb._cmlst = (fdb *) &cm2fdb;
  cm2fdb._cmlst = (fdb *) &numfdb;
  cmdfdb._cmdef = def;		/* default is first whatever was in call */
				/* else default is previous sequence if any */
  numfdb._cmdef = sequence[0] ? sequence : NIL;
  clnfdb._cmlst = (fdb *) &cmafdb;
  cmafdb._cmlst = (fdb *) &cfmfdb;
  noise ("MESSAGES");
				/* get first sequence item */
  parse (&cmdfdb,&parseval,&used);
  if (used == &numfdb) {
				/* invalidate the current sequence */
    for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
    while (used != &cfmfdb) {
      i = parseval._pvint;	/* note message number */
      if (i < 1 || i > nmsgs) {
	cmerr ("Invalid message number");
	return (NIL);
      }
				/* get what's after number */
      parse (&clnfdb,&parseval,&used);
				/* select just this message if not range */
      if (used != &clnfdb) mail_elt (stream,i)->spare = T;
      else {			/* user wants a range */
	parse (&numfdb,&parseval,&used);
	if (parseval._pvint < 1 || parseval._pvint > nmsgs) {
	  cmerr ("Invalid message number");
	  return (NIL);
	}
				/* reverse range? */
	if (i > parseval._pvint) {
	  j = i;		/* yes, reverse it back again */
	  i = parseval._pvint;
	}
	else j = parseval._pvint;
				/* set range to T */
	do mail_elt (stream,i++)->spare = T;
	while (i <= j);
				/* get what's after range */
	parse (&cmafdb,&parseval,&used);
      }
				/* if got a comma, get another number */
      if (used == &cmafdb) parse (&numfdb,&parseval,&used);
    }
  }
  else {
    tmp[0] = '\0';		/* init search buffer */
				/* init selection */
    for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = T;
				/* numbers not allowed any more */
    cm2fdb._cmlst = (fdb *) &cfmfdb;
    do {
      if (used == &cmdfdb) {	/* command for mail_search? */
				/* prepend a space if not the first time */
	if (tmp[0] != '\0') strcat (tmp," ");
	/* This is a disgusting kludge.  I'm ashamed to admit having written
	   it, but not as much as the authors of CCMD should be for having
	   cmkey parsing return the data item instead of the entire keyword
	   block. */
	ucase (atmbuf);		/* strncmp is case-dependent */
				/* locate the keyword */
	for (i = 0; strncmp (seqcmds[i]._kwkwd,atmbuf,strlen (atmbuf)); i++);
				/* see what we need to do */
	switch (parseval._pvint) {
	case s_date:		/* slurp date */
	  parse (&datfdb,&parseval,&used);
	  sprintf (tmpx,"%s %d/%d/%d",seqcmds[i]._kwkwd,
		   (&parseval._pvtad)->_dtmon+1,(&parseval._pvtad)->_dtday+1,
		   (&parseval._pvtad)->_dtyr);
	  strcat (tmp,tmpx);	/* append string */
	  break;
	case s_flag:		/* slurp keyword */
	  parse (&flgfdb,&parseval,&used);
	  sprintf (tmpx,"%s %s",seqcmds[i]._kwkwd,(char *) parseval._pvint);
	  strcat (tmp,tmpx);	/* append string */
	  break;
	case s_string:		/* slurp quoted string or field */
	  parse (&qstfdb,&parseval,&used);
	  sprintf (tmpx,"%s \"%s\"",seqcmds[i]._kwkwd,atmbuf);
	  strcat (tmp,tmpx);	/* append string */
	  break;
	default:		/* command that doesn't take an argument */
	  strcat (tmp,seqcmds[i]._kwkwd);
	  break;
	}
      }
      else {			/* local command, LAST only one so far */
	parse (&numfdb,&parseval,&used);
	if (parseval._pvint < 1 || parseval._pvint > nmsgs) {
	  cmerr ("Invalid number of messages");
	  return (NIL);
	}
				/* reject specified number of messages */
	for (i = 1; i <= stream->nmsgs - parseval._pvint; i++)
	  mail_elt (stream,i)->spare = NIL;
      }
      parse (&cmdfdb,&parseval,&used);
    } while (used != &cfmfdb);	/* loop until confirm */
    sequence[current = 0] = '\0';
    if (tmp[0]) {		/* search needed? */
      mail_search (stream,tmp);
      for (i = 1; i <= nmsgs; ++i)
	mail_elt (stream,i)->spare &= mail_elt (stream,i)->searched;
    }
  }
				/* recompute sequence string */
  seq = sequence;		/* start sequence pointer */
  *seq = '\0';			/* destroy old sequence poop */
  i = NIL;			/* delimiter is null first time through */
  for (msgno = 1; msgno <= nmsgs; ++msgno)
    if (mail_elt (stream,msgno)->spare) {
				/* if at the last message or next not sel */
				/* output delimiter and the number */
      sprintf (seq,"%s%d",(i ? "," : ""),msgno);
      /* The following kludge is necessary because the damn VAX C library has
	 sprintf return a char * rather than an int!  The comment by it in
	 stdio.h is "too painful to do right".  The cretin responsible should
	 be drawn and quartered. */
      seq += (i = strlen (seq));/* update the pointer */
      current = msgno;		/* this is the new current */
				/* any consecutive entries? */
      if (msgno < nmsgs && mail_elt (stream,msgno+1)->spare) {
				/* yes, look for end of range */
	while (msgno < nmsgs && mail_elt (stream,msgno+1)->spare) msgno++;
	sprintf (seq,":%d",msgno);/* output delimiter and final of range */
	seq += (i = strlen (seq));/* (see above kludge note) */
	current = msgno;	/* this is the new current */
      }
    }
  return (sequence[0]);		/* flag if any sequence found */
}

/* Command subroutines */


/* Execute code under "more"
 * Accepts: function to be called
 *	    integer argument to function
 */

void more (void (*f)(FILE *pipe,long arg),long arg)
{
  FILE *pipe = popen ("more","w");
  if (pipe) {
    fflush (stdout);		/* make sure regular output is done */
    fflush (stderr);
    critical = T;		/* ignore CTRL/C while more is running */
    (*f)(pipe,arg);
    fflush (pipe);		/* wait for output to be done */
    pclose (pipe);		/* close the pipe */
    critical = NIL;		/* allow CTRL/C again */
  }
  else (*f)(stdout,arg);	/* do it this way if can't invoke "more" */
}


/* Display current message
 * Accepts: file to output to
 *	    integer of message pointer
 */

void do_display (FILE *pipe,long i)
{
  char header[32768];
  struct display_msg *msg = (struct display_msg *) i;
  rfc822_header (header,msg->env,msg->bdy);
  fprintf (pipe,"%s%s\n",header,(char *) msg->bdy->contents.text.data);
}


/* Do headers of selected messages
 * Accepts: file to output to
 *	    dummy
 */

void do_header (FILE *pipe,long i)
{
  for (i = 1; i <= nmsgs; ++i) if (mail_elt (stream,i)->spare)
    header_message (pipe,i);
}

/* Get a mailbox
 * Accepts: mailbox name
 */

void do_get (char *mailbox)
{
  register int i;
  char *s,tmp[TMPLEN],lsthst[TMPLEN];
  nmsgs = 0;			/* init number of messages */
  flgtab._ktcnt = 0;		/* re-init keyword table */
  if (stream && (s = (*stream->mailbox == '{') ? stream->mailbox : NIL)) {
    strcpy (lsthst,s);		/* copy last host */
    if (s = strchr (lsthst,'}')) s[1] = '\0';
  }
  else lsthst[0] = '\0';	/* no last host */
				/* open new connection */
  if (stream = mail_open (stream,mailbox,debug ? OP_DEBUG : NIL)) {
    for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->spare = NIL;
    sequence[current = 0] = '\0';
    nmsgs = stream->nmsgs;	/* in case mail_open "failed" */
    do_status (stream);		/* report status of mailbox */
				/* copy keywords to our table */
    for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i) {
				/* value and keyword are the same */
      flags[i]._kwval = (keyval) (flags[i]._kwkwd = stream->user_flags[i]);
      flags[i]._kwflg = NIL;	/* no special flags */
    }
    flgtab._ktcnt = i;		/* update keyword count */
    if (*stream->mailbox == '{') {
      strcpy (tmp,stream->mailbox);
      if (s = strchr (tmp,'}')) strcpy (s+1,"%");
      mail_list(stream,NIL,tmp);/* find local mailboxes */
      if (s) strcpy (s+1,"*");
      mail_lsub(stream,NIL,tmp);/* find subscribed mailboxes */
    }
  }
}


/* Report status of the current mailbox
 * Accepts: stream
 */

void do_status (MAILSTREAM *stream)
{
  char *s = stream->mailbox;
  if (s)			/* report status */
    cmxprintf (" %s: %s, %d messages, %d recent\n",
	       stream->rdonly ? "Read-only mailbox" : "Mailbox",
	       s,nmsgs,stream->recent);
}


/* Display version of this program */

void do_version ()
{
  char tmp[TMPLEN];
  char *local;
  struct hostent *host_name;
  gethostname (tmp,TMPLEN);	/* get local name */
				/* get it in full form */
  local = (host_name = gethostbyname (tmp)) ? host_name->h_name : tmp;
  cmxprintf (" %s",local);
  cmxprintf (" MS %s\n",version);
}

/* Message reading subroutines */


/* Answer a message
 * Accepts: message number
 *	    answer to all flag
 */

void answer_message (int msgno,int allflag)
{
  char tmp[TMPLEN];
  BODY *body;
  ENVELOPE *env = mail_fetchstructure (stream,msgno,NIL);
  ENVELOPE *msg;
  if (env && env->reply_to) {	/* if reply address */
    msg = send_init ();		/* get a message block */
				/* copy reply-to */
    msg->to = copy_adr (env->reply_to,NIL);
    if (allflag) {		/* user asked for ALL reply */
      msg->cc = copy_adr (env->to,NIL);
      copy_adr (env->cc,msg->cc);
      msg->bcc = copy_adr (env->bcc,NIL);
    }
    if (env->subject) {		/* use subject in reply */
      strncpy (tmp,env->subject,3);
      tmp[3] ='\0';		/* tie off copy of first 3 chars */
      ucase (tmp);		/* make the whole thing uppercase */
				/* a "re:" already there? */
      if (strcmp (tmp,"RE:")) sprintf (tmp,"re: %s",env->subject);
      else sprintf (tmp,"%s",env->subject);
    }
    else sprintf (tmp,"(response to message of %s)",env->date);
    msg->subject = cpystr (tmp);/* copy desired subject */
				/* in-reply-to is our message-id if exists */
    if (env->message_id) sprintf (tmp,"%s",env->message_id);
    else {			/* build one from other info */
      if (env->from->personal)
	sprintf (tmp,"Message of %s from %s",env->date,
		 env->from->personal);
      else sprintf (tmp,"Message of %s from %s@%s",env->date,
		    env->from->mailbox,env->from->host);
    }
				/* copy in-reply-to */
    msg->in_reply_to = cpystr (tmp);
    if (body = send_text ()) {	/* get text, send message */
      quitted = NIL;
      send_level (msg,body);
      mail_free_body (&body);
      if (!quitted) {		/* unless quitted */
	sprintf (tmp,"%d",msgno);
	mail_setflag (stream,tmp,"\\Answered");
      }
    }
    mail_free_envelope (&msg);	/* flush the message */
  }
}

/* Output header for message
 * Accepts: file to output to
 *	    message number
 */

void header_message (FILE *file,long msgno)
{
  unsigned long i;
  char tmp[TMPLEN],frm[FROMLEN+1];
  char flgs[5];
  char date[7];
  MESSAGECACHE *c = mail_elt (stream,msgno);
  flgs[4] = (date[6] = '\0');	/* tie off constant width strings */
				/* make sure it's in the cache */
  mail_fetchstructure (stream,msgno,NIL);
				/* get system flags */
				/* If recent, then either "recent" or "new"
				   otherwise, either "seen" or "unseen" */
  flgs[0] = c->recent ? (c->seen ? 'R' : 'N') : (c->seen ? ' ' : 'U');
  flgs[1] = c->flagged ? 'F' : ' ';
  flgs[2] = c->answered ? 'A' : ' ';
  flgs[3] = c->deleted ? 'D' : ' ';
				/* only use "dd-mmm" from date */
  if (c->day) sprintf (date,"%2d-%s",c->day,months[c->month - 1]);
  else strncpy (date,"dd-mmm",6);
  if (i = c->user_flags) {	/* first stash user flags into tmp */
    tmp[0] = '{';		/* open brace for keywords */
    tmp[1] = '\0';		/* tie off so strcat starts off right */
    while (i) {
				/* output this keyword */
      strcat (tmp,stream->user_flags[find_rightmost_bit (&i)]);
				/* followed by spaces until done */
      if (i) strcat (tmp," ");
    }
    strcat (tmp,"} ");		/* close brace and space before subject */
  }
  else tmp[0] = '\0';
  mail_fetchfrom (frm,stream,msgno,FROMLEN);
				/* now append the subject */
  mail_fetchsubject (tmp + strlen (tmp),stream,msgno,SUBJECTLEN);
  tmp[SUBJECTLEN] = '\0';	/* and trim it to the subject length */
				/* output what we got */
  fprintf (file,"%s%4lu) %s %s %s (%lu chars)\n",flgs,c->msgno,date,frm,
	   tmp,c->rfc822_size);
}

/* Literal type a message
 * Accepts: file to output to
 *	    message number
 */

void literal_type_message (FILE *file,long msgno)
{
  char c,*t,*hdr,*text;
  mail_parameters (NIL,SET_PREFETCH,(void *) T);
  hdr = cpystr (mail_fetchheader (stream,msgno));
  mail_parameters (NIL,SET_PREFETCH,NIL);
  text = mail_fetchtext (stream,msgno);
				/* output the poop */
  fprintf (file,"Message %ld of %ld (%lu chars)\n",msgno,nmsgs,
	   strlen (hdr) + strlen (text));
  for (t = hdr; c = *t++;) if (c != '\r') putc (c,file);
  while (c = *text++) if (c != '\r') putc (c,file);
  putc ('\n',file);
  fs_give ((void **) &hdr);
}

/* Remail a message
 * Accepts: message number
 *	    remail address list
 */

void remail_message (int msgno,ADDRESS *adr)
{
  ENVELOPE *msg = send_init ();
  BODY *body = mail_newbody ();
  msg->to = adr;		/* set to-list */
				/* get header */
  mail_parameters (NIL,SET_PREFETCH,(void *) T);
  msg->remail = cpystr (mail_fetchheader (stream,msgno));
  mail_parameters (NIL,SET_PREFETCH,NIL);
  body->contents.text.data =	/* get body of message */
    cpystr (mail_fetchtext_full (stream,msgno,&body->contents.text.size,NIL));
  send_message (msg,body);	/* send off the message */
				/* if lost, enter send-level */
  if (!done) send_level (msg,body);
  else done = NIL;		/* restore prior done state */
  msg->to = NIL;		/* be sure the address isn't nuked */
  mail_free_envelope (&msg);	/* flush the message */
  mail_free_body (&body);
}

/* Type a message
 * Accepts: file to output to
 *	    message number
 */

void type_message (FILE *file,long msgno)
{
  char *text;
  char c;
  ENVELOPE *env = mail_fetchstructure (stream,msgno,NIL);
				/* make sure we get some text */
  if (text = mail_fetchtext (stream,msgno)) {
				/* output the poop */
    fprintf (file,"Message %ld of %ld (%lu chars)\n",
	     msgno,nmsgs,strlen (text));
    if (env) {			/* make sure we have an envelope */
				/* output envelope */
      if (env->date) fprintf (file,"Date: %s\n",env->date);
      type_address (file,"From",env->from);
      if (env->subject) fprintf (file,"Subject: %s\n",env->subject);
      type_address (file,"To",env->to);
      type_address (file,"cc",env->cc);
      type_address (file,"bcc",env->bcc);
    }
    putc ('\n',file);		/* output message text */
    while (c = *text++) if (c != '\r') putc (c,file);
    putc ('\n',file);
  }
}

/* Type an address
 * Accepts: file to output to
 *	    tag string to start with
 *	    address to output
 */

void type_address (FILE *file,char *tag,ADDRESS *adr)
{
  char c,tmp[8196];
  char *s = tmp;
  *s = '\0';
  rfc822_address_line (&s,tag,NIL,adr);
  for (s = tmp; c = *s++;) if (c != '\r') putc (c,file);
}


/* Type a string or string del string
 * Accepts: file to output to
 *	    pointer current line position
 *	    first string to output
 *	    optional delimiter
 *	    second string to output
 */

#define MAXLINE 78
void type_string (FILE *file,int *pos,char *str1,char chr,char *str2)
{
  int i;
  i = strlen (str1) + 2;	/* count up space, length of string, comma */
  if (chr) i++;			/* bump if delimiter and second string */
  if (str2) i += strlen (str2);
  if ((*pos += i) > MAXLINE) {	/* line too long? */
    fprintf (file,"\n ");	/* yes, start new line */
    *pos = i + 2;		/* reset position */
  }
  fprintf (file," %s",str1);	/* output first string */
  if (chr) fputc (chr,file);	/* delimiter and second string */
  if (str2) fprintf (file,"%s",str2);
}

/* Message sending subroutines */


/* Create a message composition structure
 * Returns: message structure
 */

ENVELOPE *send_init ()
{
  char tmp[TMPLEN];
  ENVELOPE *msg = mail_newenvelope ();
  rfc822_date (tmp);
  msg->date = cpystr (tmp);
  msg->from = mail_newaddr ();
  if (personal) msg->from->personal = cpystr (personal);
  msg->from->mailbox = cpystr (curusr);
  msg->from->host = cpystr (curhst);
  msg->return_path = mail_newaddr ();
  msg->return_path->mailbox = cpystr (curusr);
  msg->return_path->host = cpystr (curhst);
  sprintf (tmp,"<MS-C.%lu.%lu.%s@%s>",(unsigned long) time (0),
	   (unsigned long) rand (),curusr,curhst);
  msg->message_id = cpystr (tmp);
  return (msg);
}

/* Copy address list
 * Accepts: MAP address list
 *	    optional MTP address list to append to
 * Returns: MTP address list
 */

ADDRESS *copy_adr (ADDRESS *adr,ADDRESS *ret)
{
  ADDRESS *dadr;		/* current destination */
  ADDRESS *prev = ret;		/* previous destination */
				/* run down previous list until the end */
  if (prev) while (prev->next) prev = prev->next;
  while (adr) {			/* loop while there's still an MAP adr */
    if (adr->host) {		/* ignore group stuff */
      dadr = mail_newaddr ();	/* instantiate a new address */
      if (!ret) ret = dadr;	/* note return */
				/* tie on to the end of any previous */
      if (prev) prev->next = dadr;
      dadr->personal = cpystr (adr->personal);
      dadr->adl = cpystr (adr->adl);
      dadr->mailbox = cpystr (adr->mailbox);
      dadr->host = cpystr (adr->host);
      prev = dadr;		/* this is now the previous */
    }
    adr = adr->next;		/* go to next address in list */
  }
  return (ret);			/* return the MTP address list */
}

/* Remove recipient from an address list
 * Accepts: list
 *	    text of recipient
 * Returns: list (possibly zapped to NIL)
 */

ADDRESS *remove_adr (ADDRESS *adr,char *text)
{
  ADDRESS *ret = adr;
  ADDRESS *prev = NIL;
  while (adr) {			/* run down the list looking for this guy */
				/* is this one we want to punt? */
    if (!strcmp (adr->mailbox,text)) {
				/* yes, unlink this from the list */
      if (prev) prev->next = adr->next;
      else ret = adr->next;
      adr->next = NIL;		/* unlink list from this */
      mail_free_address (&adr);	/* now flush it */
				/* get the next in line */
      adr = prev ? prev->next : ret;
    }
    else {
      prev = adr;		/* remember the previous */
      adr = adr->next;		/* try the next in line */
    }
  }
  return (ret);			/* all done */
}

/* Prompt for and get message text
 * Returns: message body
 */

BODY *send_text ()
{
  BODY *body;
  pval parseval;
  fdb *used;
  static para_data pd = {NIL,NIL};
  static fdb parafdb = {_CMPARA,NIL,NIL,NIL,NIL,NIL,NIL};
  parafdb._cmdat = (pdat) &pd;
  cmseter ();			/* set error trap */
				/* prompt for text */
  cmxprintf (" Message (CTRL/D to send,\n\
  Use CTRL/B to insert a file, CTRL/E to enter editor, CTRL/K to redisplay\n\
  message, CTRL/L to clear screen and redisplay, CTRL/N to abort.):\n");
  cmsetrp ();			/* set reparse trap */
				/* get the text */
  parse (&parafdb,&parseval,&used);
				/* copy and return text if got any */
  if (parseval._pvpara == NIL) {/* aborted? */
    cmxprintf ("?Aborted\n");	/* yes, punt */
    return NIL;
  }
  cmxprintf ("^D\n");		/* give confirmation of the end */
  body = mail_newbody ();	/* make a new body */
  body->contents.text.size = strlen (parseval._pvpara);
  body->contents.text.data =
    strcpy ((char *) fs_get (1 + body->contents.text.size),parseval._pvpara);
				/* ISO-2022 stuff inside? */
  if (strstr (body->contents.text.data,"\033$")) {
    body->parameter = mail_newbody_parameter ();
    body->parameter->attribute = cpystr ("charset");
    body->parameter->value = cpystr ("ISO-2022-JP");
  }
  return body;
}

/* Send the message off
 * Accepts: message
 */

void send_message (ENVELOPE *msg,BODY *body)
{
  char tmp[TMPLEN];
  SENDSTREAM *stream;
  done = T;			/* assume all is well */
  if (msg->to || msg->cc || msg->bcc) {
				/* get MTP connection */
    if (!(stream = smtp_open (hostlist,debug))) {
      cmerr ("Can't open connection to any server");
      done = NIL;
    }
    else {			/* deliver message */
      if (!smtp_mail (stream,"MAIL",msg,body)) {
	sprintf (tmp,"Mailing failed - %s",stream->reply);
	cmerr (tmp);
	done = NIL;
      }
      smtp_close (stream);	/* close SMTP */
    }
  }
  if (done && msg->newsgroups) {/* want posting? */
				/* get NNTP connection */
    if (!(stream = nntp_open (newslist,debug))) {
      cmerr ("Can't open connection to any news server");
      done = NIL;
    }
    else {			/* deliver message */
      if (!nntp_mail (stream,msg,body)) {
	sprintf (tmp,"Posting failed - %s",stream->reply);
	cmerr (tmp);
	done = NIL;
      }
      smtp_close (stream);	/* close NNTP */
    }
  }
}

/* Auxillary command parsing routines */


/* Get ON or OFF
 * Returns: NIL if OFF, T or NIL
 */

static keywrd onfcmds[] = {
  {"OFF",	NIL,	(keyval) NIL},
  {"ON",	NIL,	(keyval) T},
};
static keytab onftab = {(sizeof (onfcmds)/sizeof (keywrd)),onfcmds};

int onoff ()
{
  pval parseval;
  fdb *used;
  static fdb cmdfdb = {_CMKEY,NIL,NIL,(pdat) &(onftab),"Option state, ","ON",
			 NIL};
				/* parse command */
  parse (&cmdfdb,&parseval,&used);
  return (parseval._pvint);	/* return the state */
}


/* Report an error
 * Accepts: error
 */

void cmerr (char *string)
{
  cmflush (cmcsb._cmij);	/* flush waiting input */
				/* newline if necessary */
  if (cmcpos() != 0) cmnl (cmcsb._cmej);
  cmcsb._cmcol = 0;		/* make sure our counter agrees */
  cmputc ('?',cmcsb._cmej);	/* start with question mark */
  cmputs (string,cmcsb._cmej);	/* then the error string */
  cmnl (cmcsb._cmej);		/* tie off with newline */
}

/* Co-routines from c-client */


/* Message matches a search
 * Accepts: MAIL stream
 *	    message number
 */

void mm_searched (MAILSTREAM *s,unsigned long msgno)
{
				/* don't need to do anything special here */
}


/* Message exists (i.e. there are that many messages in the mailbox)
 * Accepts: MAIL stream
 *	    message number
 */

void mm_exists (MAILSTREAM *s,unsigned long number)
{
  int delta;
  if (s != stream) return;
  if ((delta = number-nmsgs) < 0)
    cmxprintf ("%%Mailbox shrunk from %d to %d!!",nmsgs,number);
  if (nmsgs) switch (delta) {	/* no output if first time */
  case 0:			/* no new messages */
    break;
  case 1:
    cmxprintf ("[There is 1 new message]\n");
    break;
  default:
    cmxprintf ("[There are %d new messages]\n",delta);
    break;
  }
  nmsgs = number;		/* update local copy of nmsgs */
}


/* Message expunged
 * Accepts: MAIL stream
 *	    message number
 */

void mm_expunged (MAILSTREAM *s,unsigned long number)
{
  if (s == stream) nmsgs--;	/* decrement number of messages */
}

/* Message flag status change
 * Accepts: MAIL stream
 *	    message number
 */

void mm_flags (MAILSTREAM *s,unsigned long number)
{
}


/* Mailbox found
 * Accepts: MAIL stream
 *	    hierarchy delimiter
 *	    mailbox name
 *	    mailbox attributes
 */

void mm_list (MAILSTREAM *stream,int delimiter,char *string,long attributes)
{
  int i,j;
  if (attributes & LATT_NOSELECT) return;
  for (i = 0;i < mbxtab._ktcnt && (j = strcmp (mbx[i]._kwkwd,string)) <= 0;i++)
    if (!j) return;		/* done if string already present */
  if (i == MAXMAILBOXES) cmxprintf ("%%Mailbox limit exceeded -- %s\n",string);
  else {			/* add new mailbox name */
    for (j = mbxtab._ktcnt++; i < j; j--) mbx[j] = mbx[j - 1];
    mbx[i]._kwkwd = cpystr (string);
    mbx[i]._kwflg = KEY_EMO;
    mbx[i]._kwval = NIL;
  }
}


/* Subscribed mailbox found
 * Accepts: MAIL stream
 *	    hierarchy delimiter
 *	    mailbox name
 *	    mailbox attributes
 */

void mm_lsub (MAILSTREAM *stream,int delimiter,char *string,long attributes)
{
  mm_list (stream,delimiter,string,attributes);
}


/* Mailbox status
 * Accepts: MAIL stream
 *	    mailbox name
 *	    mailbox status
 */

void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
{
}

/* Notification event
 * Accepts: MAIL stream
 *	    string to log
 *	    error flag
 */

void mm_notify (MAILSTREAM *s,char *string,long errflg)
{
  mm_log (string,errflg);	/* just do mm_log action */
}


/* Log an event for the user to see
 * Accepts: string to log
 *	    error flag
 */

void mm_log (char *string,long errflg)
{
  switch (errflg) {  
  case BYE:
  case NIL:			/* no error */
    cmxprintf ("[%s]\n",string);
    break;
  case TCPDEBUG:
    cmxprintf ("[TCP: %s]\n",string);
    break;
  case PARSE:			/* parsing problem */
  case WARN:			/* warning */
    cmxprintf ("%%%s\n",string);
    break;
  case ERROR:			/* error */
  default:
    cmxprintf ("?%s\n",string);
    break;
  }
}


/* Log an event to debugging telemetry
 * Accepts: string to log
 */

void mm_dlog (char *string)
{
  fprintf (stderr,"%s\n",string);
}

/* Get user name and password for this host
 * Accepts: parse of network mailbox name
 *	    where to return user name
 *	    where to return password
 *	    trial count
 */

void mm_login (NETMBX *mb,char *username,char *password,long trial)
{
  char tmp[TMPLEN];
  pval parseval;
  fdb *used;
  static brktab usrbrk = {
    {				/* 1st char break array */
      0xff,0xff,0xff,0xff,0xff,0xfa,0x00,0x15,
      0x80,0x00,0x00,0x1f,0x80,0x00,0x00,0x1b
    },
    {				/* subsequent char break array */
				/* same as above, plus dots */
      0xff,0xff,0xff,0xff,0xff,0xf8,0x00,0x15,
      0x80,0x00,0x00,0x1f,0x80,0x00,0x00,0x1f
    }
  };
  static brktab pswbrk = {
    {				/* 1st char break array */
      0x00,0xa4,0x25,0x10,0x00,0x00,0x00,0x00,
      0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01
    },
    {				/* subsequent char break array */
      0x00,0xa4,0x25,0x10,0x00,0x00,0x00,0x00,
      0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01
    }
  };
  static fdb namfdb = {_CMFLD,CM_SDH,NIL,NIL,"remote user name",NIL,&usrbrk};
  static fdb pasfdb = {_CMFLD,CM_SDH,NIL,NIL,"remote password",NIL,&pswbrk};
  namfdb._cmdef = curusr;
  if (curhst) fs_give ((void **) &curhst);
  curhst = cpystr (mb->host);
  if (*mb->user) {
    strcpy (username,mb->user);
    sprintf (tmp,"{%s/%s/user=%s} password: ",mb->host,mb->service,mb->user);
  }
  else {
    sprintf (tmp,"{%s/%s} username: ",mb->host,mb->service);
    cmseter ();			/* set error trap */
    prompt (tmp);		/* prompt for and get user name */
    cmsetrp ();			/* set reparse trap */
    parse (&namfdb,&parseval,&used);
    strcpy (username,atmbuf);
    confirm ();
    strcpy (tmp,"Password: ");
  }
  if (curusr) fs_give ((void **) &curusr);
  curusr = cpystr (username);
  cmseter ();			/* set error trap */
  prompt (tmp);
  cmsetrp ();			/* set reparse trap */
  cmecho (NIL);			/* nuke echoing */
  parse (&pasfdb,&parseval,&used);
  strcpy (password,atmbuf);
  confirm ();
  cmxputc ('\n');		/* echo newline */
  cmecho (T);			/* re-enable echoing */
}

/* About to enter critical code
 * Accepts: stream
 */

void mm_critical (MAILSTREAM *s)
{
  critical = T;			/* note in critical code */
}


/* About to exit critical code
 * Accepts: stream
 */

void mm_nocritical (MAILSTREAM *s)
{
  critical = NIL;		/* note not in critical code */
}


/* Disk error found
 * Accepts: stream
 *	    system error code
 *	    flag indicating that mailbox may be clobbered
 * Returns: T if user wants to abort
 */

long mm_diskerror (MAILSTREAM *s,long errcode,long serious)
{
  if (serious) cmxprintf ("[Warning: mailbox on disk is probably damaged.]\n");
  cmxprintf ("Will retry if you continue MS.\n");
  kill (getpid (),SIGSTOP);
  cmxprintf ("Retrying...\n");
  return NIL;
}


/* Log a fatal error event
 * Accepts: string to log
 */

void mm_fatal (char *string)
{
  mm_log (string,ERROR);	/* shouldn't happen normally */
}
