!C99Shell v. 2.0 [PHP 7 Update] [25.02.2019]!

Software: Apache. PHP/7.3.33 

uname -a: Linux web25.us.cloudlogin.co 5.10.237-xeon-hst #1 SMP Mon May 5 15:10:04 UTC 2025 x86_64 

uid=233359(alpastrology) gid=888(tty) groups=888(tty),33(tape) 

Safe-mode: OFF (not secure)

/usr/local/imap-2007e/src/imapd/   drwxr-xr-x
Free 6182 GB of 6263.2 GB (98.7%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Feedback    Self remove    Logout    


Viewing file:     imapd.c (140.78 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* ========================================================================
 * Copyright 1988-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 
 * ========================================================================
 */

/*
 * Program:    IMAP4rev1 server
 *
 * Author:    Mark Crispin
 *        UW Technology
 *        University of Washington
 *        Seattle, WA  98195
 *        Internet: MRC@Washington.EDU
 *
 * Date:    5 November 1990
 * Last Edited:    3 March 2008
 */

/* Parameter files */

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
extern int errno;        /* just in case */
#include <signal.h>
#include <setjmp.h>
#include <time.h>
#include "c-client.h"
#include "newsrc.h"
#include <sys/stat.h>


#define CRLF PSOUT ("\015\012")    /* primary output terpri */


/* Timeouts and timers */

#define MINUTES *60

#define LOGINTIMEOUT 3 MINUTES    /* not logged in autologout timer */
#define TIMEOUT 30 MINUTES    /* RFC 3501 minimum autologout timer */
#define INPUTTIMEOUT 5 MINUTES    /* timer for additional command input */
#define ALERTTIMER 1 MINUTES    /* alert check timer */
#define SHUTDOWNTIMER 1 MINUTES    /* shutdown dally timer */
#define IDLETIMER 1 MINUTES    /* IDLE command poll timer */
#define CHECKTIMER 15 MINUTES    /* IDLE command last checkpoint timer */


#define LITSTKLEN 20        /* length of literal stack */
#define MAXCLIENTLIT 10000    /* maximum non-APPEND client literal size
                 * must be smaller than 4294967295
                 */
#define MAXAPPENDTXT 0x40000000    /* maximum APPEND literal size
                 * must be smaller than 4294967295
                 */
#define CMDLEN 65536        /* size of command buffer */


/* Server states */

#define LOGIN 0
#define SELECT 1
#define OPEN 2
#define LOGOUT 3

/* Body text fetching */

typedef struct text_args {
  char *section;        /* body section */
  STRINGLIST *lines;        /* header lines */
  unsigned long first;        /* first octet to fetch */
  unsigned long last;        /* number of octets to fetch */
  long flags;            /* fetch flags */
  long binary;            /* binary flags */
} TEXTARGS;

#define FTB_BINARY 0x1        /* fetch as binary */
#define FTB_SIZE 0x2        /* fetch size only */


/* Append data */

typedef struct append_data {
  unsigned char *arg;        /* append argument pointer */
  char *flags;            /* message flags */
  char *date;            /* message date */
  char *msg;            /* message text */
  STRING *message;        /* message stringstruct */
} APPENDDATA;


/* Message pointer */

typedef struct msg_data {
  MAILSTREAM *stream;        /* stream */
  unsigned long msgno;        /* message number */
  char *flags;            /* current flags */
  char *date;            /* current date */
  STRING *message;        /* stringstruct of message */
} MSGDATA;

/* Function prototypes */

int main (int argc,char *argv[]);
void ping_mailbox (unsigned long uid);
time_t palert (char *file,time_t oldtime);
void msg_string_init (STRING *s,void *data,unsigned long size);
char msg_string_next (STRING *s);
void msg_string_setpos (STRING *s,unsigned long i);
void new_flags (MAILSTREAM *stream);
void settimeout (unsigned int i);
void clkint (void);
void kodint (void);
void hupint (void);
void trmint (void);
void staint (void);
char *sout (char *s,char *t);
char *nout (char *s,unsigned long n,unsigned long base);
void slurp (char *s,int n,unsigned long timeout);
void inliteral (char *s,unsigned long n);
unsigned char *flush (void);
void ioerror (FILE *f,char *reason);
unsigned char *parse_astring (unsigned char **arg,unsigned long *i,
                  unsigned char *del);
unsigned char *snarf (unsigned char **arg);
unsigned char *snarf_base64 (unsigned char **arg);
unsigned char *snarf_list (unsigned char **arg);
STRINGLIST *parse_stringlist (unsigned char **s,int *list);
unsigned long uidmax (MAILSTREAM *stream);
long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
             unsigned long maxuid,unsigned long depth);
long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long msgmsg,
              unsigned long maxuid,unsigned long depth);
long crit_date (unsigned short *date,unsigned char **arg);
long crit_date_work (unsigned short *date,unsigned char **arg);
long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima);
long crit_number (unsigned long *number,unsigned char **arg);
long crit_string (STRINGLIST **string,unsigned char **arg);

void fetch (char *t,unsigned long uid);
typedef void (*fetchfn_t) (unsigned long i,void *args);
void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[]);
void fetch_bodystructure (unsigned long i,void *args);
void fetch_body (unsigned long i,void *args);
void fetch_body_part_mime (unsigned long i,void *args);
void fetch_body_part_contents (unsigned long i,void *args);
void fetch_body_part_binary (unsigned long i,void *args);
void fetch_body_part_header (unsigned long i,void *args);
void fetch_body_part_text (unsigned long i,void *args);
void remember (unsigned long uid,char *id,SIZEDTEXT *st);
void fetch_envelope (unsigned long i,void *args);
void fetch_encoding (unsigned long i,void *args);
void changed_flags (unsigned long i,int f);
void fetch_flags (unsigned long i,void *args);
void put_flag (int *c,char *s);
void fetch_internaldate (unsigned long i,void *args);
void fetch_uid (unsigned long i,void *args);
void fetch_rfc822 (unsigned long i,void *args);
void fetch_rfc822_header (unsigned long i,void *args);
void fetch_rfc822_size (unsigned long i,void *args);
void fetch_rfc822_text (unsigned long i,void *args);
void penv (ENVELOPE *env);
void pbodystructure (BODY *body);
void pbody (BODY *body);
void pparam (PARAMETER *param);
void paddr (ADDRESS *a);
void pset (SEARCHSET **set);
void pnum (unsigned long i);
void pstring (char *s);
void pnstring (char *s);
void pastring (char *s);
void psizedquoted (SIZEDTEXT *s);
void psizedliteral (SIZEDTEXT *s,STRING *st);
void psizedstring (SIZEDTEXT *s,STRING *st);
void psizedastring (SIZEDTEXT *s);
void pastringlist (STRINGLIST *s);
void pnstringorlist (STRINGLIST *s);
void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs,
              TEXTARGS *ta);
void ptext (SIZEDTEXT *s,STRING *st);
void pthread (THREADNODE *thr);
void pcapability (long flag);
long nameok (char *ref,char *name);
char *bboardname (char *cmd,char *name);
long isnewsproxy (char *name);
long newsproxypattern (char *ref,char *pat,char *pattern,long flag);
char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen);
long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date,
           STRING **message);
long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date,
         STRING **message);
void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity,
          SEARCHSET *sourceset,SEARCHSET *destset);
void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set);
char *referral (MAILSTREAM *stream,char *url,long code);
void mm_list_work (char *what,int delimiter,char *name,long attributes);
char *lasterror (void);

/* Global storage */

char *version = "404";        /* edit number of this server */
char *logout = "Logout";    /* syslogreason for logout */
char *goodbye = NIL;        /* bye reason */
time_t alerttime = 0;        /* time of last alert */
time_t sysalerttime = 0;    /* time of last system alert */
time_t useralerttime = 0;    /* time of last user alert */
time_t lastcheck = 0;        /* time of last checkpoint */
time_t shutdowntime = 0;    /* time of last shutdown */
int state = LOGIN;        /* server state */
int cancelled = NIL;        /* authenticate cancelled */
int trycreate = 0;        /* saw a trycreate */
int finding = NIL;        /* doing old FIND command */
int anonymous = 0;        /* non-zero if anonymous */
int critical = NIL;        /* non-zero if in critical code */
int quell_events = NIL;        /* non-zero if in FETCH response */
int existsquelled = NIL;    /* non-zero if an EXISTS was quelled */
int proxylist = NIL;        /* doing a proxy LIST */
MAILSTREAM *stream = NIL;    /* mailbox stream */
DRIVER *curdriver = NIL;    /* note current driver */
MAILSTREAM *tstream = NIL;    /* temporary mailbox stream */
unsigned int nflags = 0;    /* current number of keywords */
unsigned long nmsgs =0xffffffff;/* last reported # of messages and recent */
unsigned long recent = 0xffffffff;
char *nntpproxy = NIL;        /* NNTP proxy name */
unsigned char *user = NIL;    /* user name */
unsigned char *pass = NIL;    /* password */
unsigned char *initial = NIL;    /* initial response */
unsigned char cmdbuf[CMDLEN];    /* command buffer */
char *status = "starting up";    /* server status */
char *tag;            /* tag portion of command */
unsigned char *cmd;        /* command portion of command */
unsigned char *arg;        /* pointer to current argument of command */
char *lstwrn = NIL;        /* last warning message from c-client */
char *lsterr = NIL;        /* last error message from c-client */
char *lstref = NIL;        /* last referral from c-client */
char *response = NIL;        /* command response */
struct {
  unsigned long size;        /* size of current LITERAL+ */
  unsigned int ok : 1;        /* LITERAL+ in effect */
} litplus;
int litsp = 0;            /* literal stack pointer */
char *litstk[LITSTKLEN];    /* stack to hold literals */
unsigned long uidvalidity = 0;    /* last reported UID validity */
unsigned long lastuid = 0;    /* last fetched uid */
char *lastid = NIL;        /* last fetched body id for this message */
char *lastsel = NIL;        /* last selected mailbox name */
SIZEDTEXT lastst = {NIL,0};    /* last sizedtext */
unsigned long cauidvalidity = 0;/* UIDVALIDITY for COPYUID/APPENDUID */
SEARCHSET *csset = NIL;        /* COPYUID source set */
SEARCHSET *caset = NIL;        /* COPYUID/APPENDUID destination set */
jmp_buf jmpenv;            /* stack context for setjmp */


/* Response texts which appear in multiple places */

char *win = "%.80s OK ";
char *rowin = "%.80s OK [READ-ONLY] %.80s completed\015\012";
char *rwwin = "%.80s OK [READ-WRITE] %.80s completed\015\012";
char *lose = "%.80s NO ";
char *logwin = "%.80s OK [";
char *losetry = "%.80s NO [TRYCREATE] %.80s failed: %.900s\015\012";
char *loseunknowncte = "%.80s NO [UNKNOWN-CTE] %.80s failed: %.900s\015\012";
char *badcmd = "%.80s BAD Command unrecognized: %.80s\015\012";
char *misarg = "%.80s BAD Missing or invalid argument to %.80s\015\012";
char *badarg = "%.80s BAD Argument given to %.80s when none expected\015\012";
char *badseq = "%.80s BAD Bogus sequence in %.80s: %.80s\015\012";
char *badatt = "%.80s BAD Bogus attribute list in %.80s\015\012";
char *badbin = "%.80s BAD Syntax error in binary specifier\015\012";

/* Message string driver for message stringstructs */

STRINGDRIVER msg_string = {
  msg_string_init,        /* initialize string structure */
  msg_string_next,        /* get next byte in string structure */
  msg_string_setpos        /* set position in string structure */
};

/* Main program */

int main (int argc,char *argv[])
{
  unsigned long i,uid;
  long f;
  unsigned char *s,*t,*u,*v,tmp[MAILTMPLEN];
  struct stat sbuf;
  logouthook_t lgoh;
  int ret = 0;
  time_t autologouttime = 0;
  char *pgmname;
                /* if case we get borked immediately */
  if (setjmp (jmpenv)) _exit (1);
  pgmname = (argc && argv[0]) ?
    (((s = strrchr (argv[0],'/')) || (s = strrchr (argv[0],'\\'))) ?
     (char *) s+1 : argv[0]) : "imapd";
                /* set service name before linkage */
  mail_parameters (NIL,SET_SERVICENAME,(void *) "imap");
#include "linkage.c"
  rfc822_date (tmp);        /* get date/time at startup */
                /* initialize server */
  server_init (pgmname,"imap","imaps",clkint,kodint,hupint,trmint,staint);
                /* forbid automatic untagged expunge */
  mail_parameters (NIL,SET_EXPUNGEATPING,NIL);
                /* arm proxy copy callback */
  mail_parameters (NIL,SET_MAILPROXYCOPY,(void *) proxycopy);
                /* arm referral callback */
  mail_parameters (NIL,SET_IMAPREFERRAL,(void *) referral);
                /* arm COPYUID callback */
  mail_parameters (NIL,SET_COPYUID,(void *) copyuid);
                /* arm APPENDUID callback */
  mail_parameters (NIL,SET_APPENDUID,(void *) appenduid);

  if (stat (SHUTDOWNFILE,&sbuf)) {
    char proxy[MAILTMPLEN];
    FILE *nntp = fopen (NNTPFILE,"r");
    if (nntp) {            /* desire NNTP proxy? */
      if (fgets (proxy,MAILTMPLEN,nntp)) {
                /* remove newline and set NNTP proxy */
    if (s = strchr (proxy,'\n')) *s = '\0';
    nntpproxy = cpystr (proxy);
                /* disable the news driver */
    mail_parameters (NIL,DISABLE_DRIVER,"news");
      }
      fclose (nntp);        /* done reading proxy name */
    }
    s = myusername_full (&i);    /* get user name and flags */
    switch (i) {
    case MU_NOTLOGGEDIN:
      PSOUT ("* OK [");        /* not logged in, ordinary startup */
      pcapability (-1);
      break;
    case MU_ANONYMOUS:
      anonymous = T;        /* anonymous user, fall into default */
      s = "ANONYMOUS";
    case MU_LOGGEDIN:
      PSOUT ("* PREAUTH [");    /* already logged in, pre-authorized */
      pcapability (1);
      user = cpystr (s);    /* copy user name */
      pass = cpystr ("*");    /* set fake password */
      state = SELECT;        /* enter select state */
      break;
    default:
      fatal ("Unknown state from myusername_full()");
    }
    PSOUT ("] ");
    if (user) {            /* preauthenticated as someone? */
      PSOUT ("Pre-authenticated user ");
      PSOUT (user);
      PBOUT (' ');
    }
  }
  else {            /* login disabled */
    PSOUT ("* BYE Service not available ");
    state = LOGOUT;
  }
  PSOUT (tcp_serverhost ());
  PSOUT (" IMAP4rev1 ");
  PSOUT (CCLIENTVERSION);
  PBOUT ('.');
  PSOUT (version);
  PSOUT (" at ");
  PSOUT (tmp);
  CRLF;
  PFLUSH ();            /* dump output buffer */
  switch (state) {        /* do this after the banner */
  case LOGIN:
    autologouttime = time (0) + LOGINTIMEOUT;
    break;
  case SELECT:
    syslog (LOG_INFO,"Preauthenticated user=%.80s host=%.80s",
        user,tcp_clienthost ());
    break;
  }

  if (setjmp (jmpenv)) {    /* die if a signal handler say so */
                /* in case we get borked now */
    if (setjmp (jmpenv)) _exit (1);
                /* need to close stream gracefully? */
    if (stream && !stream->lock && (stream->dtb->flags & DR_XPOINT))
      stream = mail_close (stream);
    ret = 1;            /* set exit status */
  }
  else while (state != LOGOUT) {/* command processing loop */
    slurp (cmdbuf,CMDLEN,TIMEOUT);
                /* no more last error or literal */
    if (lstwrn) fs_give ((void **) &lstwrn);
    if (lsterr) fs_give ((void **) &lsterr);
    if (lstref) fs_give ((void **) &lstref);
    while (litsp) fs_give ((void **) &litstk[--litsp]);
                /* find end of line */
    if (!strchr (cmdbuf,'\012')) {
      if (t = strchr (cmdbuf,' ')) *t = '\0';
      if ((t - cmdbuf) > 100) t = NIL;
      flush ();            /* flush excess */
      if (state == LOGIN)    /* error if NLI */
    syslog (LOG_INFO,"Line too long before authentication host=%.80s",
        tcp_clienthost ());
      sprintf (tmp,response,t ? (char *) cmdbuf : "*");
      PSOUT (tmp);
    }
    else if (!(tag = strtok (cmdbuf," \015\012"))) {
      if (state == LOGIN)    /* error if NLI */
    syslog (LOG_INFO,"Null command before authentication host=%.80s",
        tcp_clienthost ());
      PSOUT ("* BAD Null command\015\012");
    }
    else if (strlen (tag) > 50) PSOUT ("* BAD Excessively long tag\015\012");
    else if (!(s = strtok (NIL," \015\012"))) {
      if (state == LOGIN)    /* error if NLI */
    syslog (LOG_INFO,"Missing command before authentication host=%.80s",
        tcp_clienthost ());
      PSOUT (tag);
      PSOUT (" BAD Missing command\015\012");
    }
    else {            /* parse command */
      response = win;        /* set default response */
      finding = NIL;        /* no longer FINDing */
      ucase (s);        /* canonicalize command case */
                /* UID command? */
      if (!strcmp (s,"UID") && strtok (NIL," \015\012")) {
    uid = T;        /* a UID command */
    s[3] = ' ';        /* restore the space delimiter */
    ucase (s);        /* make sure command all uppercase */
      }
      else uid = NIL;        /* not a UID command */
                /* flush previous saved command */
      if (cmd) fs_give ((void **) &cmd);
      cmd = cpystr (s);        /* save current command */
                /* snarf argument, see if possible litplus */
      if ((arg = strtok (NIL,"\015\012")) && ((i = strlen (arg)) > 3) &&
      (arg[i - 1] == '}') && (arg[i - 2] == '+') && isdigit (arg[i - 3])) {
                /* back over possible count */
    for (i -= 4; i && isdigit (arg[i]); i--);
    if (arg[i] == '{') {    /* found a literal? */
      litplus.ok = T;    /* yes, note LITERAL+ in effect, set size */
      litplus.size = strtoul (arg + i + 1,NIL,10);
    }
      }

                /* these commands always valid */
      if (!strcmp (cmd,"NOOP")) {
    if (arg) response = badarg;
    else if (stream)    /* allow untagged EXPUNGE */
      mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
      }
      else if (!strcmp (cmd,"LOGOUT")) {
    if (arg) response = badarg;
    else {            /* time to say farewell */
      server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
      if (lastsel) fs_give ((void **) &lastsel);
      if (state == OPEN) stream = mail_close (stream);
      state = LOGOUT;
      PSOUT ("* BYE ");
      PSOUT (mylocalhost ());
      PSOUT (" IMAP4rev1 server terminating connection\015\012");
    }
      }
      else if (!strcmp (cmd,"CAPABILITY")) {
    if (arg) response = badarg;
    else {
      PSOUT ("* ");
      pcapability (0);    /* print capabilities */
      CRLF;
    }
    if (stream)        /* allow untagged EXPUNGE */
      mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
      }
#ifdef NETSCAPE_BRAIN_DAMAGE
      else if (!strcmp (cmd,"NETSCAPE")) {
    PSOUT ("* OK [NETSCAPE]\015\012* VERSION 1.0 UNIX\015\012* ACCOUNT-URL \"");
    PSOUT (NETSCAPE_BRAIN_DAMAGE);
    PBOUT ('"');
    CRLF;
      }
#endif

      else switch (state) {    /* dispatch depending upon state */
      case LOGIN:        /* waiting to get logged in */
                /* new style authentication */
    if (!strcmp (cmd,"AUTHENTICATE")) {
      if (user) fs_give ((void **) &user);
      if (pass) fs_give ((void **) &pass);
      initial = NIL;    /* no initial argument */
      cancelled = NIL;    /* not cancelled */
                /* mandatory first argument */
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg && !(initial = snarf_base64 (&arg)))
        response = misarg;    /* optional second argument */
      else if (arg) response = badarg;
      else if (!strcmp (ucase (s),"ANONYMOUS") && !stat (ANOFILE,&sbuf)) {
        if (!(s = imap_responder ("",0,NIL)))
          response ="%.80s BAD AUTHENTICATE ANONYMOUS cancelled\015\012";
        else if (anonymous_login (argc,argv)) {
          anonymous = T;    /* note we are anonymous */
          user = cpystr ("ANONYMOUS");
          pass = cpystr ("*");
          state = SELECT;    /* make select */
          alerttime = 0;    /* force alert */
          response = logwin;/* return logged-in capabilities */
          syslog (LOG_INFO,"Authenticated anonymous=%.80s host=%.80s",s,
              tcp_clienthost ());
          fs_give ((void **) &s);
        }
        else response ="%.80s NO AUTHENTICATE ANONYMOUS failed\015\012";
      }
      else if (user = cpystr (mail_auth (s,imap_responder,argc,argv))) {
        pass = cpystr ("*");
        state = SELECT;    /* make select */
        alerttime = 0;    /* force alert */
        response = logwin;    /* return logged-in capabilities */
        syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s mech=%.80s",
            user,tcp_clienthost (),s);
      }

      else {
        AUTHENTICATOR *auth = mail_lookup_auth (1);
        char *msg = (char *) fs_get (strlen (cmd) + strlen (s) + 2);
        sprintf (msg,"%s %s",cmd,s);
        fs_give ((void **) &cmd);
        cmd = msg;
        for (i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL);
         auth && compare_cstring (s,auth->name); auth = auth->next);
        /* Failed authentication when hidden looks like invalid command.
         * This is intentional but confused me when I was debugging.
         */
        if (auth && auth->server && !(auth->flags & AU_DISABLE) &&
        !(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) {
          response = lose;
          if (cancelled) {
        if (lsterr) fs_give ((void **) &lsterr);
        lsterr = cpystr ("cancelled by user");
          }
          if (!lsterr)    /* catch-all */
        lsterr = cpystr ("Invalid authentication credentials");
          syslog (LOG_INFO,"AUTHENTICATE %.80s failure host=%.80s",s,
              tcp_clienthost ());
        }
        else {
          response = badcmd;
          syslog (LOG_INFO,"AUTHENTICATE %.80s invalid host=%.80s",s,
              tcp_clienthost ());
        }
      }
    }

                /* plaintext login with password */
    else if (!strcmp (cmd,"LOGIN")) {
      if (user) fs_give ((void **) &user);
      if (pass) fs_give ((void **) &pass);
                /* two arguments */
      if (!((user = cpystr (snarf (&arg))) &&
        (pass = cpystr (snarf (&arg))))) response = misarg;
      else if (arg) response = badarg;
                /* see if we allow anonymous */
      else if (!compare_cstring (user,"ANONYMOUS") &&
           !stat (ANOFILE,&sbuf) && anonymous_login (argc,argv)) {
        anonymous = T;    /* note we are anonymous */
        ucase (user);    /* make all uppercase for consistency */
        state = SELECT;    /* make select */
        alerttime = 0;    /* force alert */
        response = logwin;    /* return logged-in capabilities */
        syslog (LOG_INFO,"Login anonymous=%.80s host=%.80s",pass,
            tcp_clienthost ());
      }
      else {        /* delimit user from possible admin */
        if (s = strchr (user,'*')) *s++ ='\0';
                /* see if username and password are OK */
        if (server_login (user,pass,s,argc,argv)) {
          state = SELECT;    /* make select */
          alerttime = 0;    /* force alert */
          response = logwin;/* return logged-in capabilities */
          syslog (LOG_INFO,"Login user=%.80s host=%.80s",user,
              tcp_clienthost ());
        }
        else {
          response = lose;
          if (!lsterr) lsterr = cpystr ("Invalid login credentials");
        }
      }
    }
                /* start TLS security */
    else if (!strcmp (cmd,"STARTTLS")) {
      if (arg) response = badarg;
      else if (lsterr = ssl_start_tls (pgmname)) response = lose;
    }
    else response = badcmd;
    break;

      case OPEN:        /* valid only when mailbox open */
                /* fetch mailbox attributes */
    if (!strcmp (cmd,"FETCH") || !strcmp (cmd,"UID FETCH")) {
      if (!(arg && (s = strtok (arg," ")) && (t = strtok(NIL,"\015\012"))))
        response = misarg;
      else if (uid ? mail_uid_sequence (stream,s) :
           mail_sequence (stream,s)) fetch (t,uid);
      else response = badseq;
    }
                /* store mailbox attributes */
    else if (!strcmp (cmd,"STORE") || !strcmp (cmd,"UID STORE")) {
                /* must have three arguments */
      if (!(arg && (s = strtok (arg," ")) && (v = strtok (NIL," ")) &&
        (t = strtok (NIL,"\015\012")))) response = misarg;
      else if (!(uid ? mail_uid_sequence (stream,s) :
             mail_sequence (stream,s))) response = badseq;
      else {
        f = ST_SET | (uid ? ST_UID : NIL)|((v[5]&&v[6]) ? ST_SILENT : NIL);
        if (!strcmp (ucase (v),"FLAGS") || !strcmp (v,"FLAGS.SILENT")) {
          strcpy (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen");
          for (i = 0, u = tmp;
           (i < NUSERFLAGS) && (v = stream->user_flags[i]); i++)
            if (strlen (v) <
            ((size_t) (MAILTMPLEN - ((u += strlen (u)) + 2 - tmp)))) {
          *u++ = ' ';    /* write next flag */
          strcpy (u,v);
        }
          mail_flag (stream,s,tmp,f & ~ST_SET);
        }
        else if (!strcmp (v,"-FLAGS") || !strcmp (v,"-FLAGS.SILENT"))
          f &= ~ST_SET;    /* clear flags */
        else if (strcmp (v,"+FLAGS") && strcmp (v,"+FLAGS.SILENT")) {
          response = badatt;
          break;
        }
                /* find last keyword */
        for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; i++);
        mail_flag (stream,s,t,f);
                /* any new keywords appeared? */
        if (i < NUSERFLAGS && stream->user_flags[i]) new_flags (stream);
                /* return flags if silence not wanted */
        if (uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s))
          for (i = 1; i <= nmsgs; i++) if (mail_elt(stream,i)->sequence)
        mail_elt (stream,i)->spare2 = (f & ST_SILENT) ? NIL : T;
      }
    }

                /* check for new mail */
    else if (!strcmp (cmd,"CHECK")) {
                /* no arguments */
      if (arg) response = badarg;
      else if (!anonymous) {
        mail_check (stream);
                /* remember last check time */
        lastcheck = time (0);
      }
    }
                /* expunge deleted messages */
    else if (!(anonymous || (strcmp (cmd,"EXPUNGE") &&
                 strcmp (cmd,"UID EXPUNGE")))) {
      if (uid && !arg) response = misarg;
      else if (!uid && arg) response = badarg;
      else {        /* expunge deleted or specified UIDs */
        mail_expunge_full (stream,arg,arg ? EX_UID : NIL);
                /* remember last checkpoint */
        lastcheck = time (0);
      }
    }
                /* close mailbox */
    else if (!strcmp (cmd,"CLOSE") || !strcmp (cmd,"UNSELECT")) {
                /* no arguments */
      if (arg) response = badarg;
      else {
                /* no last uid */
        uidvalidity = lastuid = 0;
        if (lastsel) fs_give ((void **) &lastsel);
        if (lastid) fs_give ((void **) &lastid);
        if (lastst.data) fs_give ((void **) &lastst.data);
        stream = mail_close_full (stream,((*cmd == 'C') && !anonymous) ?
                      CL_EXPUNGE : NIL);
        state = SELECT;    /* no longer opened */
        lastcheck = 0;    /* no last checkpoint */
      }
    }
    else if (!anonymous &&    /* copy message(s) */
         (!strcmp (cmd,"COPY") || !strcmp (cmd,"UID COPY"))) {
      trycreate = NIL;    /* no trycreate status */
      if (!(arg && (s = strtok (arg," ")) && (arg = strtok(NIL,"\015\012"))
        && (t = snarf (&arg)))) response = misarg;
      else if (arg) response = badarg;
      else if (!nmsgs) {
        response = lose;
        if (!lsterr) lsterr = cpystr ("Mailbox is empty");
      }
      else if (!(uid ? mail_uid_sequence (stream,s) :
             mail_sequence (stream,s))) response = badseq;
                /* try copy */
      else if (!mail_copy_full (stream,s,t,uid ? CP_UID : NIL)) {
        response = trycreate ? losetry : lose;
        if (!lsterr) lsterr = cpystr ("No such destination mailbox");
      }
    }

                /* sort mailbox */
    else if (!strcmp (cmd,"SORT") || !strcmp (cmd,"UID SORT")) {
                /* must have four arguments */
      if (!(arg && (*arg == '(') && (t = strchr (s = arg + 1,')')) &&
        (t[1] == ' ') && (*(arg = t + 2)))) response = misarg;
      else {        /* read criteria */
        SEARCHPGM *spg = NIL;
        char *cs = NIL;
        SORTPGM *pgm = NIL,*pg = NIL;
        unsigned long *slst,*sl;
        *t = NIL;        /* tie off criteria list */
        if (!(s = strtok (ucase (s)," "))) response = badatt;
        else {
          do {        /* parse sort attributes */
        if (pg) pg = pg->next = mail_newsortpgm ();
        else pgm = pg = mail_newsortpgm ();
        if (!strcmp (s,"REVERSE")) {
          pg->reverse = T;
          if (!(s = strtok (NIL," "))) {
            s = "";    /* end of attributes */
            break;
          }
        }
        if (!strcmp (s,"DATE")) pg->function = SORTDATE;
        else if (!strcmp (s,"ARRIVAL")) pg->function = SORTARRIVAL;
        else if (!strcmp (s,"FROM")) pg->function = SORTFROM;
        else if (!strcmp (s,"SUBJECT")) pg->function = SORTSUBJECT;
        else if (!strcmp (s,"TO")) pg->function = SORTTO;
        else if (!strcmp (s,"CC")) pg->function = SORTCC;
        else if (!strcmp (s,"SIZE")) pg->function = SORTSIZE;
        else break;
          } while (s = strtok (NIL," "));
                /* bad SORT attribute */
          if (s) response = badatt;
                /* get charset and search criteria */
          else if (!((t = snarf (&arg)) && (cs = cpystr (t)) && arg &&
             *arg)) response = misarg;
                /* parse search criteria  */
          else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
                    uidmax (stream),0)) response = badatt;
          else if (arg && *arg) response = badarg;
          else if (slst = mail_sort (stream,cs,spg,pgm,uid ? SE_UID:NIL)) {
        PSOUT ("* SORT");
        for (sl = slst; *sl; sl++) {
          PBOUT (' ');
          pnum (*sl);
        }
        CRLF;
        fs_give ((void **) &slst);
          }
        }
        if (pgm) mail_free_sortpgm (&pgm);
        if (spg) mail_free_searchpgm (&spg);
        if (cs) fs_give ((void **) &cs);
      }
    }

                /* thread mailbox */
    else if (!strcmp (cmd,"THREAD") || !strcmp (cmd,"UID THREAD")) {
      THREADNODE *thr;
      SEARCHPGM *spg = NIL;
      char *cs = NIL;
                /* must have four arguments */
      if (!(arg && (s = strtok (arg," ")) && (cs = strtok (NIL," ")) &&
        (cs = cpystr (cs)) && (arg = strtok (NIL,"\015\012"))))
        response = misarg;
      else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
                    uidmax (stream),0)) response = badatt;
      else if (arg && *arg) response = badarg;
      else {
        if (thr = mail_thread (stream,s,cs,spg,uid ? SE_UID : NIL)) {
          PSOUT ("* THREAD ");
          pthread (thr);
          mail_free_threadnode (&thr);
        }
        else PSOUT ("* THREAD");
        CRLF;
      }
      if (spg) mail_free_searchpgm (&spg);
      if (cs) fs_give ((void **) &cs);
    }

                /* search mailbox */
        else if (!strcmp (cmd,"SEARCH") || !strcmp (cmd,"UID SEARCH")) {
      int retval = NIL;
      char *charset = NIL;
      SEARCHPGM *pgm;
      response = misarg;    /* assume failure */
      if (!arg) break;    /* one or more arguments required */
      if (((arg[0] == 'R') || (arg[0] == 'r')) &&
          ((arg[1] == 'E') || (arg[1] == 'e')) &&
          ((arg[2] == 'T') || (arg[2] == 't')) &&
          ((arg[3] == 'U') || (arg[3] == 'u')) &&
          ((arg[4] == 'R') || (arg[4] == 'r')) &&
          ((arg[5] == 'N') || (arg[5] == 'n')) &&
          (arg[6] == ' ') && (arg[7] == '(')) {
        retval = 0x4000;    /* return is specified */
        for (arg += 8; *arg && (*arg != ')'); ) {
          if (((arg[0] == 'M') || (arg[0] == 'm')) &&
          ((arg[1] == 'I') || (arg[1] == 'i')) &&
          ((arg[2] == 'N') || (arg[2] == 'n')) &&
          ((arg[3] == ' ') || (arg[3] == ')'))) {
        retval |= 0x1;
        arg += 3;
          }
          else if (((arg[0] == 'M') || (arg[0] == 'm')) &&
               ((arg[1] == 'A') || (arg[1] == 'a')) &&
               ((arg[2] == 'X') || (arg[2] == 'x')) &&
               ((arg[3] == ' ') || (arg[3] == ')'))) {
        retval |= 0x2;
        arg += 3;
          }
          else if (((arg[0] == 'A') || (arg[0] == 'a')) &&
               ((arg[1] == 'L') || (arg[1] == 'l')) &&
               ((arg[2] == 'L') || (arg[2] == 'l')) &&
               ((arg[3] == ' ') || (arg[3] == ')'))) {
        retval |= 0x4;
        arg += 3;
          }
          else if (((arg[0] == 'C') || (arg[0] == 'c')) &&
               ((arg[1] == 'O') || (arg[1] == 'o')) &&
               ((arg[2] == 'U') || (arg[2] == 'u')) &&
               ((arg[3] == 'N') || (arg[3] == 'n')) &&
               ((arg[4] == 'T') || (arg[4] == 't')) &&
               ((arg[5] == ' ') || (arg[5] == ')'))) {
        retval |= 0x10;
        arg += 5;
          }
          else break;    /* unknown return value */
                /* more return values to come */
          if ((*arg == ' ') && (arg[1] != ')')) ++arg;
        }
                /* RETURN list must be properly terminated */
        if ((*arg++ != ')') || (*arg++ != ' ')) break;
                /* default return value is ALL */
        if (!(retval &= 0x3fff)) retval = 0x4;
      }

                /* character set specified? */
      if (((arg[0] == 'C') || (arg[0] == 'c')) &&
          ((arg[1] == 'H') || (arg[1] == 'h')) &&
          ((arg[2] == 'A') || (arg[2] == 'a')) &&
          ((arg[3] == 'R') || (arg[3] == 'r')) &&
          ((arg[4] == 'S') || (arg[4] == 's')) &&
          ((arg[5] == 'E') || (arg[5] == 'e')) &&
          ((arg[6] == 'T') || (arg[6] == 't')) &&
          (arg[7] == ' ')) {
        arg += 8;        /* yes, skip over CHARSET token */
        if (s = snarf (&arg)) charset = cpystr (s);
        else break;        /* missing character set */
      }
                /* must have arguments here */
      if (!(arg && *arg)) break;
      if (parse_criteria (pgm = mail_newsearchpgm (),&arg,nmsgs,
                  uidmax (stream),0) && !*arg) {
        response = win;    /* looks good, try the search */
        mail_search_full (stream,charset,pgm,SE_FREE);
                /* output search results if success */
        if (response == win) {
          if (retval) {    /* ESEARCH desired */
        PSOUT ("* ESEARCH (TAG ");
        pstring (tag);
        PBOUT (')');
        if (uid) PSOUT (" UID");
                /* wants MIN */
        if (retval & 0x1) {
          for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched;
               ++i);
          if (i <= nmsgs) {
            PSOUT (" MIN ");
            pnum (uid ? mail_uid (stream,i) : i);
          }
        }
                /* wants MAX */
        if (retval & 0x2) {
          for (i = nmsgs; i && !mail_elt (stream,i)->searched; --i);
          if (i) {
            PSOUT (" MAX ");
            pnum (uid ? mail_uid (stream,i) : i);
          }
        }

                /* wants ALL */
        if (retval & 0x4) {
          unsigned long j;
                /* find first match */
          for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched;
               ++i);
          if (i <= nmsgs) {
            PSOUT (" ALL ");
            pnum (uid ? mail_uid (stream,i) : i);
            j = i;    /* last message output */
          }
          while (++i <= nmsgs) {
            if (mail_elt (stream,i)->searched) {
              while ((++i <= nmsgs) && mail_elt (stream,i)->searched);
                /* previous message is end of range */
              if (j != --i) {
            PBOUT (':');
            pnum (uid ? mail_uid (stream,i) : i);
              }
            }
                /* search for next match */
            while ((++i <= nmsgs) && !mail_elt (stream,i)->searched);
            if (i <= nmsgs) {
              PBOUT (',');
              pnum (uid ? mail_uid (stream,i) : i);
              j = i;    /* last message output */
            }
          }
        }
                /* wants COUNT */
        if (retval & 0x10) {
          unsigned long j;
          for (i = 1, j = 0; i <= nmsgs; ++i)
            if (mail_elt (stream,i)->searched) ++j;
          PSOUT (" COUNT ");
          pnum (j);
        }
          }
          else {        /* standard search */
        PSOUT ("* SEARCH");
        for (i = 1; i <= nmsgs; ++i)
          if (mail_elt (stream,i)->searched) {
            PBOUT (' ');
            pnum (uid ? mail_uid (stream,i) : i);
          }
          }
          CRLF;
        }
      }
      else mail_free_searchpgm (&pgm);
      if (charset) fs_give ((void **) &charset);
    }

    else            /* fall into select case */
      case SELECT:        /* valid whenever logged in */
                /* select new mailbox */
      if (!(strcmp (cmd,"SELECT") && strcmp (cmd,"EXAMINE") &&
        strcmp (cmd,"BBOARD"))) {
                /* single argument */
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg) response = badarg;
      else if (nameok (NIL,s = bboardname (cmd,s))) {
        DRIVER *factory = mail_valid (NIL,s,NIL);
        f = (anonymous ? OP_ANONYMOUS + OP_READONLY : NIL) |
          ((*cmd == 'S') ? NIL : OP_READONLY);
        curdriver = NIL;    /* no drivers known */
                /* no last uid */
        uidvalidity = lastuid = 0;
        if (lastid) fs_give ((void **) &lastid);
        if (lastst.data) fs_give ((void **) &lastst.data);
        nflags = 0;        /* force update */
        nmsgs = recent = 0xffffffff;
        if (factory && !strcmp (factory->name,"phile") &&
        (stream = mail_open (stream,s,f | OP_SILENT)) &&
        (response == win)) {
          BODY *b;
                /* see if proxy open */
          if ((mail_elt (stream,1)->rfc822_size < 400) &&
          mail_fetchstructure (stream,1,&b) && (b->type == TYPETEXT) &&
          (t = mail_fetch_text (stream,1,NIL,&i,NIL)) &&
          (i < MAILTMPLEN) && (t[0] == '{')) {
                /* copy and tie off */
        strncpy (tmp,t,i)[i] = '\0';
                /* nuke any trailing newline */
        if (t = strpbrk (tmp,"\r\n")) *t = '\0';
                /* try to open proxy */
        if ((tstream = mail_open (NIL,tmp,f | OP_SILENT)) &&
            (response == win) && tstream->nmsgs) {
          s = tmp;    /* got it, close the link */
          mail_close (stream);
          stream = tstream;
          tstream = NIL;
        }
          }
                /* now give the exists event */
          stream->silent = NIL;
          mm_exists (stream,stream->nmsgs);
        }
        else if (!factory && isnewsproxy (s)) {
          sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6);
          stream = mail_open (stream,tmp,f);
        }
                /* open stream normally then */
        else stream = mail_open (stream,s,f);

        if (stream && (response == win)) {
          state = OPEN;    /* note state open */
          if (lastsel) fs_give ((void **) &lastsel);
                /* canonicalize INBOX */
          if (!compare_cstring (s,"#MHINBOX"))
        lastsel = cpystr ("#MHINBOX");
          else lastsel = cpystr (compare_cstring (s,"INBOX") ?
                     (char *) s : "INBOX");
                /* note readonly/readwrite */
          response = stream->rdonly ? rowin : rwwin;
          if (anonymous)
        syslog (LOG_INFO,"Anonymous select of %.80s host=%.80s",
            stream->mailbox,tcp_clienthost ());
          lastcheck = 0;    /* no last check */
        }
        else {        /* failed, nuke old selection */
          if (stream) stream = mail_close (stream);
          state = SELECT;    /* no mailbox open now */
          if (lastsel) fs_give ((void **) &lastsel);
          response = lose;    /* open failed */
        }
      }
    }

                /* APPEND message to mailbox */
    else if (!(anonymous || strcmp (cmd,"APPEND"))) {
                /* parse mailbox name */
      if ((s = snarf (&arg)) && arg) {
        STRING st;        /* message stringstruct */
        APPENDDATA ad;
        ad.arg = arg;    /* command arguments */
                /* no message yet */
        ad.flags = ad.date = ad.msg = NIL;
        ad.message = &st;    /* pointer to stringstruct to use */
        trycreate = NIL;    /* no trycreate status */
        if (!mail_append_multiple (NIL,s,append_msg,(void *) &ad)) {
          if (response == win) response = trycreate ? losetry : lose;
                /* this can happen with #driver. hack */
          if (!lsterr) lsterr = cpystr ("No such destination mailbox");
        }
                /* clean up any message text left behind */
        if (ad.flags) fs_give ((void **) &ad.flags);
        if (ad.date) fs_give ((void **) &ad.date);
        if (ad.msg) fs_give ((void **) &ad.msg);
      }
      else response = misarg;
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* list mailboxes */
    else if (!strcmp (cmd,"LIST") || !strcmp (cmd,"RLIST")) {
                /* get reference and mailbox argument */
      if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
        response = misarg;
      else if (arg) response = badarg;
                /* make sure anonymous can't do bad things */
      else if (nameok (s,t)) {
        if (newsproxypattern (s,t,tmp,LONGT)) {
          proxylist = T;
          mail_list (NIL,"",tmp);
          proxylist = NIL;
        }
        else mail_list (NIL,s,t);
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* scan mailboxes */
    else if (!strcmp (cmd,"SCAN")) {
                /* get arguments */
      if (!((s = snarf (&arg)) && (t = snarf_list (&arg)) &&
        (u = snarf (&arg)))) response = misarg;
      else if (arg) response = badarg;
                /* make sure anonymous can't do bad things */
      else if (nameok (s,t)) {
        if (newsproxypattern (s,t,tmp,NIL))
          mm_log ("SCAN not permitted for news",ERROR);
        else mail_scan (NIL,s,t,u);
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* list subscribed mailboxes */
    else if (!strcmp (cmd,"LSUB") || !strcmp (cmd,"RLSUB")) {
                /* get reference and mailbox argument */
      if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
        response = misarg;
      else if (arg) response = badarg;
                /* make sure anonymous can't do bad things */
      else if (nameok (s,t)) {
        if (newsproxypattern (s,t,tmp,NIL)) newsrc_lsub (NIL,tmp);
        else mail_lsub (NIL,s,t);
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

                /* find mailboxes */
    else if (!strcmp (cmd,"FIND")) {
                /* get subcommand and true argument */
      if (!(arg && (s = strtok (arg," \015\012")) && (s == cmd + 5) &&
        (cmd[4] = ' ') && ucase (s) &&
        (arg = strtok (NIL,"\015\012")) && (s = snarf_list (&arg))))
        response = misarg;    /* missing required argument */
      else if (arg) response = badarg;
                /* punt on single-char wildcards */
      else if (strpbrk (s,"%?")) response =
        "%.80s NO IMAP2 ? and %% wildcards not supported: %.80s\015\012";
      else if (nameok (NIL,s)) {
        finding = T;    /* note that we are FINDing */
                /* dispatch based on type */
        if (!strcmp (cmd,"FIND MAILBOXES") && !anonymous)
          mail_lsub (NIL,NIL,s);
        else if (!strcmp (cmd,"FIND ALL.MAILBOXES")) {
                /* convert * to % for compatible behavior */
          for (t = s; *t; t++) if (*t == '*') *t = '%';
          mail_list (NIL,NIL,s);
        }
        else response = badcmd;
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

                /* status of mailbox */
    else if (!strcmp (cmd,"STATUS")) {
      if (!((s = snarf (&arg)) && arg && (*arg++ == '(') &&
        (t = strchr (arg,')')) && (t - arg) && !t[1]))
        response = misarg;
      else {
        f = NIL;        /* initially no flags */
        *t = '\0';        /* tie off flag string */
                /* read flags */
        t = strtok (ucase (arg)," ");
        do {        /* parse each one; unknown generate warning */
          if (!strcmp (t,"MESSAGES")) f |= SA_MESSAGES;
          else if (!strcmp (t,"RECENT")) f |= SA_RECENT;
          else if (!strcmp (t,"UNSEEN")) f |= SA_UNSEEN;
          else if (!strcmp (t,"UIDNEXT")) f |= SA_UIDNEXT;
          else if (!strcmp (t,"UIDVALIDITY")) f |= SA_UIDVALIDITY;
          else {
        PSOUT ("* NO Unknown status flag ");
        PSOUT (t);
        CRLF;
          }
        } while (t = strtok (NIL," "));
        ping_mailbox (uid);    /* in case the fool did STATUS on open mbx */
        PFLUSH ();        /* make sure stdout is dumped in case slave */
        if (!compare_cstring (s,"INBOX")) s = "INBOX";
        else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX";
        if (state == LOGOUT) response = lose;
                /* get mailbox status */
        else if (lastsel && (!strcmp (s,lastsel) ||
                 (stream && !strcmp (s,stream->mailbox)))) {
          unsigned long unseen;
                /* snarl at cretins which do this */
          PSOUT ("* NO CLIENT BUG DETECTED: STATUS on selected mailbox: ");
          PSOUT (s);
          CRLF;
          tmp[0] = ' '; tmp[1] = '\0';
          if (f & SA_MESSAGES)
        sprintf (tmp + strlen (tmp)," MESSAGES %lu",stream->nmsgs);
          if (f & SA_RECENT)
        sprintf (tmp + strlen (tmp)," RECENT %lu",stream->recent);
          if (f & SA_UNSEEN) {
        for (i = 1,unseen = 0; i <= stream->nmsgs; i++)
          if (!mail_elt (stream,i)->seen) unseen++;
        sprintf (tmp + strlen (tmp)," UNSEEN %lu",unseen);
          }
          if (f & SA_UIDNEXT)
        sprintf (tmp + strlen (tmp)," UIDNEXT %lu",stream->uid_last+1);
          if (f & SA_UIDVALIDITY)
        sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",
             stream->uid_validity);
          tmp[1] = '(';
          strcat (tmp,")\015\012");
          PSOUT ("* STATUS ");
          pastring (s);
          PSOUT (tmp);
        }
        else if (isnewsproxy (s)) {
          sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6);
          if (!mail_status (NIL,tmp,f)) response = lose;
        }
        else if (!mail_status (NIL,s,f)) response = lose;
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

                /* subscribe to mailbox */
    else if (!(anonymous || strcmp (cmd,"SUBSCRIBE"))) {
                /* get <mailbox> or MAILBOX <mailbox> */
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg) {    /* IMAP2bis form */
        if (compare_cstring (s,"MAILBOX")) response = badarg;
        else if (!(s = snarf (&arg))) response = misarg;
        else if (arg) response = badarg;
        else mail_subscribe (NIL,s);
      }
      else if (isnewsproxy (s)) newsrc_update (NIL,s+6,':');
      else mail_subscribe (NIL,s);
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* unsubscribe to mailbox */
    else if (!(anonymous || strcmp (cmd,"UNSUBSCRIBE"))) {
                /* get <mailbox> or MAILBOX <mailbox> */
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg) {    /* IMAP2bis form */
        if (compare_cstring (s,"MAILBOX")) response = badarg;
        else if (!(s = snarf (&arg))) response = misarg;
        else if (arg) response = badarg;
        else if (isnewsproxy (s)) newsrc_update (NIL,s+6,'!');
        else mail_unsubscribe (NIL,s);
      }
      else mail_unsubscribe (NIL,s);
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

    else if (!strcmp (cmd,"NAMESPACE")) {
      if (arg) response = badarg;
      else {
        NAMESPACE **ns = (NAMESPACE **) mail_parameters(NIL,GET_NAMESPACE,
                                 NIL);
        NAMESPACE *n;
        PARAMETER *p;
        PSOUT ("* NAMESPACE");
        if (ns) for (i = 0; i < 3; i++) {
          if (n = ns[i]) {
        PSOUT (" (");
        do {
          PBOUT ('(');
          pstring (n->name);
          switch (n->delimiter) {
          case '\\':    /* quoted delimiter */
          case '"':
            PSOUT (" \"\\\\\"");
            break;
          case '\0':    /* no delimiter */
            PSOUT (" NIL");
            break;
          default:    /* unquoted delimiter */
            PSOUT (" \"");
            PBOUT (n->delimiter);
            PBOUT ('"');
            break;
          }
                /* NAMESPACE extensions are hairy */
          if (p = n->param) do {
            PBOUT (' ');
            pstring (p->attribute);
            PSOUT (" (");
            do pstring (p->value);
            while (p->next && !p->next->attribute && (p = p->next));
            PBOUT (')');
          } while (p = p->next);
          PBOUT (')');
        } while (n = n->next);
        PBOUT (')');
          }
          else PSOUT (" NIL");
        }
        else PSOUT (" NIL NIL NIL");
        CRLF;
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

                /* create mailbox */
    else if (!(anonymous || strcmp (cmd,"CREATE"))) {
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg) response = badarg;
      else mail_create (NIL,s);
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* delete mailbox */
    else if (!(anonymous || strcmp (cmd,"DELETE"))) {
      if (!(s = snarf (&arg))) response = misarg;
      else if (arg) response = badarg;
      else {        /* make sure not selected */
        if (lastsel && (!strcmp (s,lastsel) ||
                (stream && !strcmp (s,stream->mailbox))))
          mm_log ("Can not DELETE the selected mailbox",ERROR);
        else mail_delete (NIL,s);
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }
                /* rename mailbox */
    else if (!(anonymous || strcmp (cmd,"RENAME"))) {
      if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg;
      else if (arg) response = badarg;
      else {        /* make sure not selected */
        if (!compare_cstring (s,"INBOX")) s = "INBOX";
        else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX";
        if (lastsel && (!strcmp (s,lastsel) ||
                (stream && !strcmp (s,stream->mailbox))))
          mm_log ("Can not RENAME the selected mailbox",ERROR);
        else mail_rename (NIL,s,t);
      }
      if (stream)        /* allow untagged EXPUNGE */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
    }

                /* idle mode */
    else if (!strcmp (cmd,"IDLE")) {
                /* no arguments */
      if (arg) response = badarg;
      else {        /* tell client ready for argument */
        unsigned long donefake = 0;
        PSOUT ("+ Waiting for DONE\015\012");
        PFLUSH ();        /* dump output buffer */
                /* inactivity countdown */
        i = ((TIMEOUT) / (IDLETIMER)) + 1;
        do {        /* main idle loop */
          if (!donefake) {    /* don't ping mailbox if faking */
        mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,
                 (void *) stream);
        ping_mailbox (uid);
                /* maybe do a checkpoint if not anonymous */
        if (!anonymous && stream && (time (0) > lastcheck + CHECKTIMER)) {
          mail_check (stream);
                /* cancel likely altwin from mail_check() */
          if (lsterr) fs_give ((void **) &lsterr);
          if (lstwrn) fs_give ((void **) &lstwrn);
                /* remember last checkpoint */
          lastcheck = time (0);
        }
          }
          if (lstwrn) {    /* have a warning? */
        PSOUT ("* NO ");
        PSOUT (lstwrn);
        CRLF;
        fs_give ((void **) &lstwrn);
          }
          if (!(i % 2)) {    /* prevent NAT timeouts */
        sprintf (tmp,"* OK Timeout in %lu minutes\015\012",
             (i * IDLETIMER) / 60);
        PSOUT (tmp);
          }
                /* two minutes before the end... */
          if ((state == OPEN) && (i <= 2)) {
        sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012",
             donefake = nmsgs + 1,recent + 1);
        PSOUT (tmp);    /* prod client to wake up */
          }
          PFLUSH ();    /* dump output buffer */
        } while ((state != LOGOUT) && !INWAIT (IDLETIMER) && --i);

                /* time to exit idle loop */
        if (state != LOGOUT) {
          if (i) {        /* still have time left? */
                /* yes, read expected DONE */
        slurp (tmp,MAILTMPLEN,INPUTTIMEOUT);
        if (((tmp[0] != 'D') && (tmp[0] != 'd')) ||
            ((tmp[1] != 'O') && (tmp[1] != 'o')) ||
            ((tmp[2] != 'N') && (tmp[2] != 'n')) ||
            ((tmp[3] != 'E') && (tmp[3] != 'e')) ||
            (((tmp[4] != '\015') || (tmp[5] != '\012')) &&
             (tmp[4] != '\012')))
          response = "%.80s BAD Bogus IDLE continuation\015\012";
        if (donefake) {    /* if faking at the end */
                /* send EXPUNGE (should be just 1) */
          while (donefake > nmsgs) {
            sprintf (tmp,"* %lu EXPUNGE\015\012",donefake--);
            PSOUT (tmp);
          }
          sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012",
               nmsgs,recent);
          PSOUT (tmp);
        }
          }
          else clkint ();    /* otherwise do autologout action */
        }
      }
    }
    else response = badcmd;
    break;
      default:
        response = "%.80s BAD Unknown state for %.80s command\015\012";
    break;
      }

      while (litplus.ok) {    /* any unread LITERAL+? */
    litplus.ok = NIL;    /* yes, cancel it now */
    clearerr (stdin);    /* clear stdin errors */
    status = "discarding unread literal";
                /* read literal and discard it */
    while (i = (litplus.size > MAILTMPLEN) ? MAILTMPLEN : litplus.size) {
      if (state == LOGOUT) litplus.size = 0;
      else {
        settimeout (INPUTTIMEOUT);
        if (PSINR (tmp,i)) litplus.size -= i;
        else {
          ioerror (stdin,status);
          litplus.size = 0;    /* in case it continues */
        }
      }
    }
    settimeout (0);        /* stop timeout */
                /* get new command tail */
    slurp (tmp,MAILTMPLEN,INPUTTIMEOUT);
                /* locate end of line */
    if (t = strchr (tmp,'\012')) {
                /* back over CR */
      if ((t > tmp) && (t[-1] == '\015')) --t;
      *t = NIL;        /* tie off CRLF */
                /* possible LITERAL+? */
      if (((i = strlen (tmp)) > 3) && (tmp[i - 1] == '}') &&
          (tmp[i - 2] == '+') && isdigit (tmp[i - 3])) {
                /* back over possible count */
        for (i -= 4; i && isdigit (tmp[i]); i--);
        if (tmp[i] == '{') {    /* found a literal? */
          litplus.ok = T;    /* yes, note LITERAL+ in effect, set size */
          litplus.size = strtoul (tmp + i + 1,NIL,10);
        }
      }
    }
    else flush ();        /* overlong line after LITERAL+, punt */
      }
      ping_mailbox (uid);    /* update mailbox status before response */
      if (lstwrn && lsterr) {    /* output most recent warning */
    PSOUT ("* NO ");
    PSOUT (lstwrn);
    CRLF;
    fs_give ((void **) &lstwrn);
      }

      if (response == logwin) {    /* authentication win message */
    sprintf (tmp,response,lstref ? "*" : tag);
    PSOUT (tmp);        /* start response */
    pcapability (1);    /* print logged-in capabilities */
    PSOUT ("] User ");
    PSOUT (user);
    PSOUT (" authenticated\015\012");
    if (lstref) {
      sprintf (tmp,response,tag);
      PSOUT (tmp);        /* start response */
      PSOUT ("[REFERRAL ");
      PSOUT (lstref);
      PSOUT ("] ");
      PSOUT (lasterror ());
      CRLF;
    }
      }
      else if ((response == win) || (response == lose)) {
    sprintf (tmp,response,tag);
    PSOUT (tmp);
    if (cauidvalidity) {    /* COPYUID/APPENDUID response? */
      sprintf (tmp,"[%.80sUID %lu ",(char *)
           ((s = strchr (cmd,' ')) ? s+1 : cmd),cauidvalidity);
      PSOUT (tmp);
      cauidvalidity = 0;    /* cancel response for future */
      if (csset) {
        pset (&csset);
        PBOUT (' ');
      }
      pset (&caset);
      PSOUT ("] ");
    }
    else if (lstref) {    /* have a referral? */
      PSOUT ("[REFERRAL ");
      PSOUT (lstref);
      PSOUT ("] ");
    }
    if (lsterr || lstwrn) PSOUT (lasterror ());
    else {
      PSOUT (cmd);
      PSOUT ((response == win) ? " completed" : "failed");
    }
    CRLF;
      }
      else {            /* normal response */
    if ((response == rowin) || (response == rwwin)) {
      if (lstwrn) {        /* output most recent warning */
        PSOUT ("* NO ");
        PSOUT (lstwrn);
        CRLF;
        fs_give ((void **) &lstwrn);
      }
    }
    sprintf (tmp,response,tag,cmd,lasterror ());
    PSOUT (tmp);        /* output response */
      }
    }
    PFLUSH ();            /* make sure output blatted */

    if (autologouttime) {    /* have an autologout in effect? */
                /* cancel if no longer waiting for login */
      if (state != LOGIN) autologouttime = 0;
                /* took too long to login */
      else if (autologouttime < time (0)) {
    logout = goodbye = "Autologout";
    stream = NIL;
    state = LOGOUT;        /* sayonara */
      }
    }
  }
  if (goodbye && !quell_events){/* have a goodbye message? */
    PSOUT ("* BYE ");        /* utter it */
    PSOUT (goodbye);
    CRLF;
    PFLUSH ();            /* make sure blatted */
  }
  syslog (LOG_INFO,"%s user=%.80s host=%.80s",logout,
      user ? (char *) user : "???",tcp_clienthost ());
                /* do logout hook if needed */
  if (lgoh = (logouthook_t) mail_parameters (NIL,GET_LOGOUTHOOK,NIL))
    (*lgoh) (mail_parameters (NIL,GET_LOGOUTDATA,NIL));
  _exit (ret);            /* all done */
  return ret;            /* stupid compilers */
}

/* Ping mailbox during each cycle.  Also check alerts
 * Accepts: last command was UID flag
 */

void ping_mailbox (unsigned long uid)
{
  unsigned long i;
  char tmp[MAILTMPLEN];
  if (state == OPEN) {
    if (!mail_ping (stream)) {    /* make sure stream still alive */
      PSOUT ("* BYE ");
      PSOUT (mylocalhost ());
      PSOUT (" Fatal mailbox error: ");
      PSOUT (lasterror ());
      CRLF;
      stream = NIL;        /* don't try to clean up stream */
      state = LOGOUT;        /* go away */
      syslog (LOG_INFO,
          "Fatal mailbox error user=%.80s host=%.80s mbx=%.80s: %.80s",
          user ? (char *) user : "???",tcp_clienthost (),
          (stream && stream->mailbox) ? stream->mailbox : "???",
          lasterror ());
      return;
    }
                /* change in number of messages? */
    if (existsquelled || (nmsgs != stream->nmsgs)) {
      PSOUT ("* ");
      pnum (nmsgs = stream->nmsgs);
      PSOUT (" EXISTS\015\012");
    }
                /* change in recent messages? */
    if (existsquelled || (recent != stream->recent)) {
      PSOUT ("* ");
      pnum (recent = stream->recent);
      PSOUT (" RECENT\015\012");
    }
    existsquelled = NIL;    /* don't do this until asked again */
    if (stream->uid_validity && (stream->uid_validity != uidvalidity)) {
      PSOUT ("* OK [UIDVALIDITY ");
      pnum (stream->uid_validity);
      PSOUT ("] UID validity status\015\012* OK [UIDNEXT ");
      pnum (stream->uid_last + 1);
      PSOUT ("] Predicted next UID\015\012");
      if (stream->uid_nosticky) {
    PSOUT ("* NO [UIDNOTSTICKY] Non-permanent unique identifiers: ");
    PSOUT (stream->mailbox);
    CRLF;
      }
      uidvalidity = stream->uid_validity;
    }

                /* don't bother if driver changed */
    if (curdriver == stream->dtb) {
                /* first report any new flags */
      if ((nflags < NUSERFLAGS) && stream->user_flags[nflags])
    new_flags (stream);
      for (i = 1; i <= nmsgs; i++) if (mail_elt (stream,i)->spare2) {
    PSOUT ("* ");
    pnum (i);
    PSOUT (" FETCH (");
    fetch_flags (i,NIL);    /* output changed flags */
    if (uid) {        /* need to include UIDs in response? */
      PBOUT (' ');
      fetch_uid (i,NIL);
    }
    PSOUT (")\015\012");
      }
    }
    else {            /* driver changed */
      new_flags (stream);    /* send mailbox flags */
      if (curdriver) {        /* note readonly/write if possible change */
    PSOUT ("* OK [READ-");
    PSOUT (stream->rdonly ? "ONLY" : "WRITE");
    PSOUT ("] Mailbox status\015\012");
      }
      curdriver = stream->dtb;
      if (nmsgs) {        /* get flags for all messages */
    sprintf (tmp,"1:%lu",nmsgs);
    mail_fetch_flags (stream,tmp,NIL);
                /* don't do this if newsrc already did */
    if (!(curdriver->flags & DR_NEWS)) {
                /* find first unseen message */
      for (i = 1; i <= nmsgs && mail_elt (stream,i)->seen; i++);
      if (i <= nmsgs) {
        PSOUT ("* OK [UNSEEN ");
        pnum (i);
        PSOUT ("] first unseen message in ");
        PSOUT (stream->mailbox);
        CRLF;
      }
    }
      }
    }
  }
  if (shutdowntime && (time (0) > shutdowntime + SHUTDOWNTIMER)) {
    PSOUT ("* BYE Server shutting down\015\012");
    state = LOGOUT;
  }
                /* don't do these stat()s every cycle */
  else if (time (0) > alerttime + ALERTTIMER) { 
    struct stat sbuf;
                /* have a shutdown file? */
    if (!stat (SHUTDOWNFILE,&sbuf)) {
      PSOUT ("* OK [ALERT] Server shutting down shortly\015\012");
      shutdowntime = time (0);
    }
    alerttime = time (0);    /* output any new alerts */
    sysalerttime = palert (ALERTFILE,sysalerttime);
    if (state != LOGIN)        /* do user alert if logged in */
      useralerttime = palert (mailboxfile (tmp,USERALERTFILE),useralerttime);
  }
}

/* Print an alert file
 * Accepts: path of alert file
 *        time of last printed alert file
 * Returns: updated time of last printed alert file
 */

time_t palert (char *file,time_t oldtime)
{
  FILE *alf;
  struct stat sbuf;
  int c,lc = '\012';
                /* have a new alert file? */
  if (stat (file,&sbuf) || (sbuf.st_mtime <= oldtime) ||
      !(alf = fopen (file,"r"))) return oldtime;
                /* yes, display it */
  while ((c = getc (alf)) != EOF) {
    if (lc == '\012') PSOUT ("* OK [ALERT] ");
    switch (c) {        /* output character */
    case '\012':        /* newline means do CRLF */
      CRLF;
    case '\015':        /* flush CRs */
    case '\0':            /* flush nulls */
      break;
    default:
      PBOUT (c);        /* output all other characters */
      break;
    }
    lc = c;            /* note previous character */
  }
  fclose (alf);
  if (lc != '\012') CRLF;    /* final terminating CRLF */
  return sbuf.st_mtime;        /* return updated last alert time */
}

/* Initialize file string structure for file stringstruct
 * Accepts: string structure
 *        pointer to message data structure
 *        size of string
 */

void msg_string_init (STRING *s,void *data,unsigned long size)
{
  MSGDATA *md = (MSGDATA *) data;
  s->data = data;        /* note stream/msgno and header length */
#if 0
  s->size = size;        /* message size */
  s->curpos = s->chunk =    /* load header */
    mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,
               FT_PREFETCHTEXT | FT_PEEK);
#else    /* This kludge is necessary because of broken mail stores */
  mail_fetchtext_full (md->stream,md->msgno,&s->size,FT_PEEK);
  s->curpos = s->chunk =    /* load header */
    mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,FT_PEEK);
  s->size += s->data1;        /* header + body size */
#endif
  s->cursize = s->chunksize = s->data1;
  s->offset = 0;        /* offset is start of message */
}


/* Get next character from file stringstruct
 * Accepts: string structure
 * Returns: character, string structure chunk refreshed
 */

char msg_string_next (STRING *s)
{
  char c = *s->curpos++;    /* get next byte */
  SETPOS (s,GETPOS (s));    /* move to next chunk */
  return c;            /* return the byte */
}


/* Set string pointer position for file stringstruct
 * Accepts: string structure
 *        new position
 */

void msg_string_setpos (STRING *s,unsigned long i)
{
  MSGDATA *md = (MSGDATA *) s->data;
  if (i < s->data1) {        /* want header? */
    s->chunk = mail_fetchheader_full (md->stream,md->msgno,NIL,NIL,FT_PEEK);
    s->chunksize = s->data1;    /* header length */
    s->offset = 0;        /* offset is start of message */
  }
  else if (i < s->size) {    /* want body */
    s->chunk = mail_fetchtext_full (md->stream,md->msgno,NIL,FT_PEEK);
    s->chunksize = s->size - s->data1;
    s->offset = s->data1;    /* offset is end of header */
  }
  else {            /* off end of message */
    s->chunk = NIL;        /* make sure that we crack on this then */
    s->chunksize = 1;        /* make sure SNX cracks the right way... */
    s->offset = i;
  }
                /* initial position and size */
  s->curpos = s->chunk + (i -= s->offset);
  s->cursize = s->chunksize - i;
}

/* Send flags for stream
 * Accepts: MAIL stream
 *        scratch buffer
 */

void new_flags (MAILSTREAM *stream)
{
  int i,c;
  PSOUT ("* FLAGS (");
  for (i = 0; i < NUSERFLAGS; i++) if (stream->user_flags[i]) {
    PSOUT (stream->user_flags[i]);
    PBOUT (' ');
    nflags = i + 1;
  }
  PSOUT ("\\Answered \\Flagged \\Deleted \\Draft \\Seen)\015\012* OK [PERMANENTFLAGS (");
  for (i = c = 0; i < NUSERFLAGS; i++)
    if ((stream->perm_user_flags & (1 << i)) && stream->user_flags[i])
      put_flag (&c,stream->user_flags[i]);
  if (stream->kwd_create) put_flag (&c,"\\*");
  if (stream->perm_answered) put_flag (&c,"\\Answered");
  if (stream->perm_flagged) put_flag (&c,"\\Flagged");
  if (stream->perm_deleted) put_flag (&c,"\\Deleted");
  if (stream->perm_draft) put_flag (&c,"\\Draft");
  if (stream->perm_seen) put_flag (&c,"\\Seen");
  PSOUT (")] Permanent flags\015\012");
}

/* Set timeout
 * Accepts: desired interval
 */

void settimeout (unsigned int i)
{
                /* limit if not logged in */
  if (i) alarm ((state == LOGIN) ? LOGINTIMEOUT : i);
  else alarm (0);
}


/* Clock interrupt
 * Returns only if critical code in progress
 */

void clkint (void)
{
  settimeout (0);        /* disable all interrupts */
  server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  logout = "Autologout";
  goodbye = "Autologout (idle for too long)";
  if (critical) {        /* must defer if in critical code(?) */
    close (0);            /* kill stdin */
    state = LOGOUT;        /* die as soon as we can */
  }
  else longjmp (jmpenv,1);    /* die now */
}


/* Kiss Of Death interrupt
 * Returns only if critical code in progress
 */

void kodint (void)
{
  settimeout (0);        /* disable all interrupts */
  server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  logout = goodbye = "Killed (lost mailbox lock)";
  if (critical) {        /* must defer if in critical code */
    close (0);            /* kill stdin */
    state = LOGOUT;        /* die as soon as we can */
  }
  else longjmp (jmpenv,1);    /* die now */
}

/* Hangup interrupt
 * Returns only if critical code in progress
 */

void hupint (void)
{
  settimeout (0);        /* disable all interrupts */
  server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  logout = "Hangup";
  goodbye = NIL;        /* other end is already gone */
  if (critical) {        /* must defer if in critical code */
    close (0);            /* kill stdin */
    close (1);            /* and stdout */
    state = LOGOUT;        /* die as soon as we can */
  }
  else longjmp (jmpenv,1);    /* die now */
}


/* Termination interrupt
 * Returns only if critical code in progress
 */

void trmint (void)
{
  settimeout (0);        /* disable all interrupts */
  server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  logout = goodbye = "Killed (terminated)";
  /* Make no attempt at graceful closure since a shutdown may be in
   * progress, and we won't have any time to do mail_close() actions
   */
  stream = NIL;
  if (critical) {        /* must defer if in critical code */
    close (0);            /* kill stdin */
    close (1);            /* and stdout */
    state = LOGOUT;        /* die as soon as we can */
  }
  else longjmp (jmpenv,1);    /* die now */
}

/* The routines on this and the next page eschew the use of non-syscall libc
 * routines (especially stdio) for a reason.  Also, these hideous #if
 * condtionals need to be replaced.
 */

#ifndef unix
#define unix 0
#endif


/* Status request interrupt
 * Always returns
 */

void staint (void)
{
#if unix
  int fd;
  char *s,buf[8*MAILTMPLEN];
  unsigned long pid = getpid ();
                /* build file name */
  s = nout (sout (buf,"/tmp/imapd-status."),pid,10);
  if (user) s = sout (sout (s,"."),user);
  *s = '\0';            /* tie off file name */
  if ((fd = open (buf,O_WRONLY | O_CREAT | O_TRUNC,0666)) >= 0) {
    fchmod (fd,0666);
    s = nout (sout (buf,"PID="),pid,10);
    if (user) s = sout (sout (s,", user="),user);
    switch (state) {
    case LOGIN:
      s = sout (s,", not logged in");
      break;
    case SELECT:
      s = sout (s,", logged in");
      break;
    case OPEN:
      s = sout (s,", mailbox open");
      break;
    case LOGOUT:
      s = sout (s,", logging out");
      break;
    }
    if (stream && stream->mailbox)
      s = sout (sout (s,"\nmailbox="),stream->mailbox);
    *s++ = '\n';
    if (status) {
      s = sout (s,status);
      if (cmd) s = sout (sout (s,", last command="),cmd);
    }
    else s = sout (sout (s,cmd)," in progress");
    *s++ = '\n';
    write (fd,buf,s-buf);
    close (fd);
  }
#endif
}

/* Write string
 * Accepts: destination string pointer
 *        string
 * Returns: updated string pointer
 */

char *sout (char *s,char *t)
{
  while (*t) *s++ = *t++;
  return s;
}


/* Write number
 * Accepts: destination string pointer
 *        number
 *        base
 * Returns: updated string pointer
 */

char *nout (char *s,unsigned long n,unsigned long base)
{
  char stack[256];
  char *t = stack;
                /* push PID digits on stack */
  do *t++ = (char) (n % base) + '0';
  while (n /= base);
                /* pop digits from stack */
  while (t > stack) *s++ = *--t;
  return s;
}

/* Slurp a command line
 * Accepts: buffer pointer
 *        buffer size
 *        input timeout
 */

void slurp (char *s,int n,unsigned long timeout)
{
  memset (s,'\0',n);        /* zap buffer */
  if (state != LOGOUT) {    /* get a command under timeout */
    settimeout (timeout);
    clearerr (stdin);        /* clear stdin errors */
    status = "reading line";
    if (!PSIN (s,n-1)) ioerror (stdin,status);
    settimeout (0);        /* make sure timeout disabled */
    status = NIL;
  }
}


/* Read a literal
 * Accepts: destination buffer (must be size+1 for trailing NUL)
 *        size of buffer (must be less than 4294967295)
 */

void inliteral (char *s,unsigned long n)
{
  unsigned long i;
  if (litplus.ok) {        /* no more LITERAL+ to worry about */
    litplus.ok = NIL;
    litplus.size = 0;
  }
  else {            /* otherwise tell client ready for argument */
    PSOUT ("+ Ready for argument\015\012");
    PFLUSH ();            /* dump output buffer */
  }
  clearerr (stdin);        /* clear stdin errors */
  memset (s,'\0',n+1);        /* zap buffer */
  status = "reading literal";
  while (n) {            /* get data under timeout */
    if (state == LOGOUT) n = 0;
    else {
      settimeout (INPUTTIMEOUT);
      i = min (n,8192);        /* must read at least 8K within timeout */
      if (PSINR (s,i)) {
    s += i;
    n -= i;
      }
      else {
    ioerror (stdin,status);
    n = 0;            /* in case it continues */
      }
      settimeout (0);        /* stop timeout */
    }
  }
}

/* Flush until newline seen
 * Returns: NIL, always
 */

unsigned char *flush (void)
{
  int c;
  if (state != LOGOUT) {
    settimeout (INPUTTIMEOUT);
    clearerr (stdin);        /* clear stdin errors */
    status = "flushing line";
    while ((c = PBIN ()) != '\012') if (c == EOF) ioerror (stdin,status);
    settimeout (0);        /* make sure timeout disabled */
  }
  response = "%.80s BAD Command line too long\015\012";
  status = NIL;
  return NIL;
}


/* Report command stream error and die
 * Accepts: stdin or stdout (whichever got the error)
 *        reason (what caller was doing)
 */

void ioerror (FILE *f,char *reason)
{
  static char msg[MAILTMPLEN];
  char *s,*t;
  if (logout) {            /* say nothing if already dying */
    settimeout (0);        /* disable all interrupts */
    server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
                /* write error string */
    for (s = ferror (f) ? strerror (errno) : "Unexpected client disconnect",
       t = logout = msg; *s; *t++ = *s++);
    for (s = ", while "; *s; *t++ = *s++);
    for (s = reason; *s; *t++ = *s++);
    if (critical) {        /* must defer if in critical code */
      close (0);        /* kill stdin */
      close (1);        /* and stdout */
      state = LOGOUT;        /* die as soon as we can */
    }
    else longjmp (jmpenv,1);    /* die now */
  }
}

/* Parse an IMAP astring
 * Accepts: pointer to argument text pointer
 *        pointer to returned size
 *        pointer to returned delimiter
 * Returns: argument
 */

unsigned char *parse_astring (unsigned char **arg,unsigned long *size,
                  unsigned char *del)
{
  unsigned long i;
  unsigned char c,*s,*t,*v;
  if (!*arg) return NIL;    /* better be an argument */
  switch (**arg) {        /* see what the argument is */
  default:            /* atom */
    for (s = t = *arg, i = 0;
     (*t > ' ') && (*t < 0x7f) && (*t != '(') && (*t != ')') &&
     (*t != '{') && (*t != '%') && (*t != '*') && (*t != '"') &&
     (*t != '\\'); ++t,++i);
    if (*size = i) break;    /* got atom if non-empty */
  case ')': case '%': case '*': case '\\': case '\0': case ' ':
   return NIL;            /* empty atom is a bogon */
  case '"':            /* hunt for trailing quote */
    for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) {
                /* quote next character */
      if (c == '\\') switch (c = *t++) {
      case '"': case '\\': break;
      default: return NIL;    /* invalid quote-next */
      }
                /* else must be a CHAR */
      if (!c || (c & 0x80)) return NIL;
    }
    *v = '\0';            /* tie off string */
    *size = v - s;        /* return size */
    break;

  case '{':            /* literal string */
    s = *arg + 1;        /* get size */
    if (!isdigit (*s)) return NIL;
    if ((*size = i = strtoul (s,(char **) &t,10)) > MAXCLIENTLIT) {
      mm_notify (NIL,"Absurdly long client literal",ERROR);
      syslog (LOG_INFO,"Overlong (%lu) client literal user=%.80s host=%.80s",
          i,user ? (char *) user : "???",tcp_clienthost ());
      return NIL;
    }
    switch (*t) {        /* validate end of literal */
    case '+':            /* non-blocking literal */
      if (*++t != '}') return NIL;
    case '}':
      if (!t[1]) break;        /* OK if end of line */
    default:
      return NIL;        /* bad literal */
    }
    if (litsp >= LITSTKLEN) {    /* make sure don't overflow stack */
      mm_notify (NIL,"Too many literals in command",ERROR);
      return NIL;
    }
                /* get a literal buffer */
    inliteral (s = litstk[litsp++] = (char *) fs_get (i+1),i);
                    /* get new command tail */
    slurp (*arg = t,CMDLEN - (t - cmdbuf),INPUTTIMEOUT);
    if (!strchr (t,'\012')) return flush ();
                /* reset strtok mechanism, tie off if done */
    if (!strtok (t,"\015\012")) *t = '\0';
                /* possible LITERAL+? */
    if (((i = strlen (t)) > 3) && (t[i - 1] == '}') &&
    (t[i - 2] == '+') && isdigit (t[i - 3])) {
                /* back over possible count */
      for (i -= 4; i && isdigit (t[i]); i--);
      if (t[i] == '{') {    /* found a literal? */
    litplus.ok = T;        /* yes, note LITERAL+ in effect, set size */
    litplus.size = strtoul (t + i + 1,NIL,10);
      }
    }
    break;
  }
  if (*del = *t) {        /* have a delimiter? */
    *t++ = '\0';        /* yes, stomp on it */
    *arg = t;            /* update argument pointer */
  }
  else *arg = NIL;        /* no more arguments */
  return s;
}

/* Snarf a command argument (simple jacket into parse_astring())
 * Accepts: pointer to argument text pointer
 * Returns: argument
 */

unsigned char *snarf (unsigned char **arg)
{
  unsigned long i;
  unsigned char c;
  unsigned char *s = parse_astring (arg,&i,&c);
  return ((c == ' ') || !c) ? s : NIL;
}


/* Snarf a BASE64 argument for SASL-IR
 * Accepts: pointer to argument text pointer
 * Returns: argument
 */

unsigned char *snarf_base64 (unsigned char **arg)
{
  unsigned char *ret = *arg;
  unsigned char *s = ret + 1;
  static char base64mask[256] = {
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
   0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
   0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  };
  if (*(ret = *arg) == '=');    /* easy case if zero-length argument */
                /* must be at least one BASE64 char */
  else if (!base64mask[*ret]) return NIL;
  else {            /* quick and dirty */
    while (base64mask[*s++]);    /* scan until end of BASE64 */
    if (*s == '=') ++s;        /* allow up to two padding chars */
    if (*s == '=') ++s;
  }
  switch (*s) {            /* anything following the argument? */
  case ' ':            /* another argument */
    *s++ = '\0';        /* tie off previous argument */
    *arg = s;            /* and update argument pointer */
    break;
  case '\0':            /* end of command */
    *arg = NIL;
    break;
  default:            /* syntax error */
    return NIL;
  }
  return ret;            /* return BASE64 string */
}

/* Snarf a list command argument (simple jacket into parse_astring())
 * Accepts: pointer to argument text pointer
 * Returns: argument
 */

unsigned char *snarf_list (unsigned char **arg)
{
  unsigned long i;
  unsigned char c,*s,*t;
  if (!*arg) return NIL;    /* better be an argument */
  switch (**arg) {
  default:            /* atom and/or wildcard chars */
    for (s = t = *arg, i = 0;
     (*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') &&
     (*t != '"') && (*t != '\\'); ++t,++i);
    if (c = *t) {        /* have a delimiter? */
      *t++ = '\0';        /* stomp on it */
      *arg = t;            /* update argument pointer */
    }
    else *arg = NIL;
    break;
  case ')': case '\\': case '\0': case ' ':
    return NIL;            /* empty name is bogus */
  case '"':            /* quoted string? */
  case '{':            /* or literal? */
    s = parse_astring (arg,&i,&c);
    break;
  }
  return ((c == ' ') || !c) ? s : NIL;
}

/* Get a list of header lines
 * Accepts: pointer to string pointer
 *        pointer to list flag
 * Returns: string list
 */

STRINGLIST *parse_stringlist (unsigned char **s,int *list)
{
  char c = ' ',*t;
  unsigned long i;
  STRINGLIST *ret = NIL,*cur = NIL;
  if (*s && **s == '(') {    /* proper list? */
    ++*s;            /* for each item in list */
    while ((c == ' ') && (t = parse_astring (s,&i,&c))) {
                /* get new block */
      if (cur) cur = cur->next = mail_newstringlist ();
      else cur = ret = mail_newstringlist ();
                /* note text */
      cur->text.data = (unsigned char *) fs_get (i + 1);
      memcpy (cur->text.data,t,i);
      cur->text.size = i;        /* and size */
    }
                /* must be end of list */
    if (c != ')') mail_free_stringlist (&ret);
  }
  if (t = *s) {            /* need to reload strtok() state? */
                /* end of a list? */
    if (*list && (*t == ')') && !t[1]) *list = NIL;
    else {
      *--t = ' ';        /* patch a space back in */
      *--t = 'x';        /* and a hokey character before that */
      t = strtok (t," ");    /* reset to *s */
    }
  }
  return ret;
}

/* Get value of UID * for criteria parsing
 * Accepts: stream
 * Returns: maximum UID
 */

unsigned long uidmax (MAILSTREAM *stream)
{
  return stream->nmsgs ? mail_uid (stream,stream->nmsgs) : 0xffffffff;
}


/* Parse search criteria
 * Accepts: search program to write criteria into
 *        pointer to argument text pointer
 *        maximum message number
 *        maximum UID
 *        logical nesting depth
 * Returns: T if success, NIL if error
 */

long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
             unsigned long maxuid,unsigned long depth)
{
  if (arg && *arg) {        /* must be an argument */
                /* parse criteria */
    do if (!parse_criterion (pgm,arg,maxmsg,maxuid,depth)) return NIL;
                /* as long as a space delimiter */
    while (**arg == ' ' && (*arg)++);
                /* failed if not end of criteria */
    if (**arg && **arg != ')') return NIL;
  }
  return T;            /* success */
}

/* Parse a search criterion
 * Accepts: search program to write criterion into
 *        pointer to argument text pointer
 *        maximum message number
 *        maximum UID
 *        logical nesting depth
 * Returns: T if success, NIL if error
 */

long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
              unsigned long maxuid,unsigned long depth)
{
  unsigned long i;
  unsigned char c = NIL,*s,*t,*v,*tail,*del;
  SEARCHSET **set;
  SEARCHPGMLIST **not;
  SEARCHOR **or;
  SEARCHHEADER **hdr;
  long ret = NIL;
                /* better be an argument */
  if ((depth > 500) || !(arg && *arg));
  else if (**arg == '(') {    /* list of criteria? */
    (*arg)++;            /* yes, parse the criteria */
    if (parse_criteria (pgm,arg,maxmsg,maxuid,depth+1) && **arg == ')') {
      (*arg)++;            /* skip closing paren */
      ret = T;            /* successful parse of list */
    }
  }
  else {            /* find end of criterion */
    if (!(tail = strpbrk ((s = *arg)," )"))) tail = *arg + strlen (*arg);
    c = *(del = tail);        /* remember the delimiter */
    *del = '\0';        /* tie off criterion */
    switch (*ucase (s)) {    /* dispatch based on character */
    case '*':            /* sequence */
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      if (*(set = &pgm->msgno)){/* already a sequence? */
                /* silly, but not as silly as the client! */
    for (not = &pgm->not; *not; not = &(*not)->next);
    *not = mail_newsearchpgmlist ();
    set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->msgno;
      }
      ret = crit_set (set,&s,maxmsg) && (tail == s);
      break;
    case 'A':            /* possible ALL, ANSWERED */
      if (!strcmp (s+1,"LL")) ret = T;
      else if (!strcmp (s+1,"NSWERED")) ret = pgm->answered = T;
      break;

    case 'B':            /* possible BCC, BEFORE, BODY */
      if (!strcmp (s+1,"CC") && c == ' ' && *++tail)
    ret = crit_string (&pgm->bcc,&tail);
      else if (!strcmp (s+1,"EFORE") && c == ' ' && *++tail)
    ret = crit_date (&pgm->before,&tail);
      else if (!strcmp (s+1,"ODY") && c == ' ' && *++tail)
    ret = crit_string (&pgm->body,&tail);
      break;
    case 'C':            /* possible CC */
      if (!strcmp (s+1,"C") && c == ' ' && *++tail)
    ret = crit_string (&pgm->cc,&tail);
      break;
    case 'D':            /* possible DELETED */
      if (!strcmp (s+1,"ELETED")) ret = pgm->deleted = T;
      if (!strcmp (s+1,"RAFT")) ret = pgm->draft = T;
      break;
    case 'F':            /* possible FLAGGED, FROM */
      if (!strcmp (s+1,"LAGGED")) ret = pgm->flagged = T;
      else if (!strcmp (s+1,"ROM") && c == ' ' && *++tail)
    ret = crit_string (&pgm->from,&tail);
      break;
    case 'H':            /* possible HEADER */
      if (!strcmp (s+1,"EADER") && c == ' ' && *(v = tail + 1) &&
      (s = parse_astring (&v,&i,&c)) && i && c == ' ' &&
      (t = parse_astring (&v,&i,&c))) {
    for (hdr = &pgm->header; *hdr; hdr = &(*hdr)->next);
    *hdr = mail_newsearchheader (s,t);
                /* update tail, restore delimiter */
    *(tail = v ? v - 1 : t + i) = c;
    ret = T;        /* success */
      }
      break;
    case 'K':            /* possible KEYWORD */
      if (!strcmp (s+1,"EYWORD") && c == ' ' && *++tail)
    ret = crit_string (&pgm->keyword,&tail);
      break;
    case 'L':
      if (!strcmp (s+1,"ARGER") && c == ' ' && *++tail)
    ret = crit_number (&pgm->larger,&tail);
      break;
    case 'N':            /* possible NEW, NOT */
      if (!strcmp (s+1,"EW")) ret = pgm->recent = pgm->unseen = T;
      else if (!strcmp (s+1,"OT") && c == ' ' && *++tail) {
    for (not = &pgm->not; *not; not = &(*not)->next);
    *not = mail_newsearchpgmlist ();
    ret = parse_criterion ((*not)->pgm,&tail,maxmsg,maxuid,depth+1);
      }
      break;

    case 'O':            /* possible OLD, ON */
      if (!strcmp (s+1,"LD")) ret = pgm->old = T;
      else if (!strcmp (s+1,"N") && c == ' ' && *++tail)
    ret = crit_date (&pgm->on,&tail);
      else if (!strcmp (s+1,"R") && c == ' ') {
    for (or = &pgm->or; *or; or = &(*or)->next);
    *or = mail_newsearchor ();
    ret = *++tail && parse_criterion((*or)->first,&tail,maxmsg,maxuid,
                     depth+1) &&
      (*tail == ' ') && *++tail &&
      parse_criterion ((*or)->second,&tail,maxmsg,maxuid,depth+1);
      }
      else if (!strcmp (s+1,"LDER") && c == ' ' && *++tail)
    ret = crit_number (&pgm->older,&tail);
      break;
    case 'R':            /* possible RECENT */
      if (!strcmp (s+1,"ECENT")) ret = pgm->recent = T;
      break;
    case 'S':            /* possible SEEN, SINCE, SUBJECT */
      if (!strcmp (s+1,"EEN")) ret = pgm->seen = T;
      else if (!strcmp (s+1,"ENTBEFORE") && c == ' ' && *++tail)
    ret = crit_date (&pgm->sentbefore,&tail);
      else if (!strcmp (s+1,"ENTON") && c == ' ' && *++tail)
    ret = crit_date (&pgm->senton,&tail);
      else if (!strcmp (s+1,"ENTSINCE") && c == ' ' && *++tail)
    ret = crit_date (&pgm->sentsince,&tail);
      else if (!strcmp (s+1,"INCE") && c == ' ' && *++tail)
    ret = crit_date (&pgm->since,&tail);
      else if (!strcmp (s+1,"MALLER") && c == ' ' && *++tail)
    ret = crit_number (&pgm->smaller,&tail);
      else if (!strcmp (s+1,"UBJECT") && c == ' ' && *++tail)
    ret = crit_string (&pgm->subject,&tail);
      break;
    case 'T':            /* possible TEXT, TO */
      if (!strcmp (s+1,"EXT") && c == ' ' && *++tail)
    ret = crit_string (&pgm->text,&tail);
      else if (!strcmp (s+1,"O") && c == ' ' && *++tail)
    ret = crit_string (&pgm->to,&tail);
      break;

    case 'U':            /* possible UID, UN* */
      if (!strcmp (s+1,"ID") && c== ' ' && *++tail) {
    if (*(set = &pgm->uid)){/* already a sequence? */
                /* silly, but not as silly as the client! */
      for (not = &pgm->not; *not; not = &(*not)->next);
      *not = mail_newsearchpgmlist ();
      set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->uid;
    }
    ret = crit_set (set,&tail,maxuid);
      }
      else if (!strcmp (s+1,"NANSWERED")) ret = pgm->unanswered = T;
      else if (!strcmp (s+1,"NDELETED")) ret = pgm->undeleted = T;
      else if (!strcmp (s+1,"NDRAFT")) ret = pgm->undraft = T;
      else if (!strcmp (s+1,"NFLAGGED")) ret = pgm->unflagged = T;
      else if (!strcmp (s+1,"NKEYWORD") && c == ' ' && *++tail)
    ret = crit_string (&pgm->unkeyword,&tail);
      else if (!strcmp (s+1,"NSEEN")) ret = pgm->unseen = T;
      break;
    case 'Y':            /* possible YOUNGER */
      if (!strcmp (s+1,"OUNGER") && c == ' ' && *++tail)
    ret = crit_number (&pgm->younger,&tail);
      break;
    default:            /* oh dear */
      break;
    }
    if (ret) {            /* only bother if success */
      *del = c;            /* restore delimiter */
      *arg = tail;        /* update argument pointer */
    }
  }
  return ret;            /* return more to come */
}

/* Parse a search date criterion
 * Accepts: date to write into
 *        pointer to argument text pointer
 * Returns: T if success, NIL if error
 */

long crit_date (unsigned short *date,unsigned char **arg)
{
  if (*date) return NIL;    /* can't double this value */
                /* handle quoted form */
  if (**arg != '"') return crit_date_work (date,arg);
  (*arg)++;            /* skip past opening quote */
  if (!(crit_date_work (date,arg) && (**arg == '"'))) return NIL;
  (*arg)++;            /* skip closing quote */
  return T;
}

/* Worker routine to parse a search date criterion
 * Accepts: date to write into
 *        pointer to argument text pointer
 * Returns: T if success, NIL if error
 */

long crit_date_work (unsigned short *date,unsigned char **arg)
{
  int d,m,y;
                /* day */
  if (isdigit (d = *(*arg)++) || ((d == ' ') && isdigit (**arg))) {
    if (d == ' ') d = 0;    /* leading space */
    else d -= '0';        /* first digit */
    if (isdigit (**arg)) {    /* if a second digit */
      d *= 10;            /* slide over first digit */
      d += *(*arg)++ - '0';    /* second digit */
    }
    if ((**arg == '-') && (y = *++(*arg))) {
      m = (y >= 'a' ? y - 'a' : y - 'A') * 1024;
      if ((y = *++(*arg))) {
    m += (y >= 'a' ? y - 'a' : y - 'A') * 32;
    if ((y = *++(*arg))) {
      m += (y >= 'a' ? y - 'a' : y - 'A');
      switch (m) {        /* determine the month */
      case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
      case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
      case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
      case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
      case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
      case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
      case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
      case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
      case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
      case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10;break;
      case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11;break;
      case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12;break;
      default: return NIL;
      }
      if ((*++(*arg) == '-') && isdigit (*++(*arg))) {
        y = 0;        /* init year */
        do {
          y *= 10;        /* add this number */
          y += *(*arg)++ - '0';
        }
        while (isdigit (**arg));
                /* minimal validity check of date */
        if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0) return NIL; 
                /* time began on UNIX in 1970 */
        if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000;
                /* return value */
        *date = mail_shortdate (y - BASEYEAR,m,d);
        return T;        /* success */
      }
    }
      }
    }
  }
  return NIL;            /* else error */
}

/* Parse a search set criterion
 * Accepts: set to write into
 *        pointer to argument text pointer
 *        maximum value permitted
 * Returns: T if success, NIL if error
 */

long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima)
{
  unsigned long i = 0;
  if (*set) return NIL;        /* can't double this value */
  *set = mail_newsearchset ();    /* instantiate a new search set */
  if (**arg == '*') {        /* maxnum? */
    (*arg)++;            /* skip past that number */
    (*set)->first = maxima;
  }
  else if (crit_number (&i,arg) && i) (*set)->first = i;
  else return NIL;        /* bogon */
  switch (**arg) {        /* decide based on delimiter */
  case ':':            /* sequence range */
    i = 0;            /* reset for crit_number() */
    if (*++(*arg) == '*') {    /* maxnum? */
      (*arg)++;            /* skip past that number */
      (*set)->last = maxima;
    }
    else if (crit_number (&i,arg) && i) {
      if (i < (*set)->first) {    /* backwards range */
    (*set)->last = (*set)->first;
    (*set)->first = i;
      }
      else (*set)->last = i;    /* set last number */
    }
    else return NIL;        /* bogon */
    if (**arg != ',') break;    /* drop into comma case if comma seen */
  case ',':
    (*arg)++;            /* skip past delimiter */
    return crit_set (&(*set)->next,arg,maxima);
  default:
    break;
  }
  return T;            /* return success */
}

/* Parse a search number criterion
 * Accepts: number to write into
 *        pointer to argument text pointer
 * Returns: T if success, NIL if error
 */

long crit_number (unsigned long *number,unsigned char **arg)
{
                /* can't double this value */
  if (*number || !isdigit (**arg)) return NIL;
  *number = 0;
  while (isdigit (**arg)) {    /* found a digit? */
    *number *= 10;        /* add a decade */
    *number += *(*arg)++ - '0';    /* add number */
  }
  return T;
}


/* Parse a search string criterion
 * Accepts: date to write into
 *        pointer to argument text pointer
 * Returns: T if success, NIL if error
 */

long crit_string (STRINGLIST **string,unsigned char **arg)
{
  unsigned long i;
  char c;
  char *s = parse_astring (arg,&i,&c);
  if (!s) return NIL;
                /* find tail of list */
  while (*string) string = &(*string)->next;
  *string = mail_newstringlist ();
  (*string)->text.data = (unsigned char *) fs_get (i + 1);
  memcpy ((*string)->text.data,s,i);
  (*string)->text.data[i] = '\0';
  (*string)->text.size = i;
                /* if end of arguments, wrap it up here */
  if (!*arg) *arg = (char *) (*string)->text.data + i;
  else (*--(*arg) = c);        /* back up pointer, restore delimiter */
  return T;
}

/* Fetch message data
 * Accepts: string of data items to be fetched (must be writeable)
 *        UID fetch flag
 */

#define MAXFETCH 100

void fetch (char *t,unsigned long uid)
{
  fetchfn_t f[MAXFETCH +2];
  void *fa[MAXFETCH + 2];
  int k;
  memset ((void *) f,NIL,sizeof (f));
  memset ((void *) fa,NIL,sizeof (fa));
  fetch_work (t,uid,f,fa);    /* do the work */
                /* clean up arguments */
  for (k = 1; f[k]; k++) if (fa[k]) (*f[k]) (0,fa[k]);
}


/* Fetch message data worker routine
 * Accepts: string of data items to be fetched (must be writeable)
 *        UID fetch flag
 *        function dispatch vector
 *        function argument vector
 */

void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[])
{
  unsigned char *s,*v;
  unsigned long i;
  unsigned long k = 0;
  BODY *b;
  int list = NIL;
  int parse_envs = NIL;
  int parse_bodies = NIL;
  if (uid) {            /* need to fetch UIDs? */
    fa[k] = NIL;        /* no argument */
    f[k++] = fetch_uid;        /* push a UID fetch on the stack */
  }

                /* process macros */
  if (!strcmp (ucase (t),"ALL"))
    strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)");
  else if (!strcmp (t,"FULL"))
    strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)");
  else if (!strcmp (t,"FAST")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE)");
  if (list = (*t == '(')) t++;    /* skip open paren */
  if (s = strtok (t," ")) do {    /* parse attribute list */
    if (list && (i = strlen (s)) && (s[i-1] == ')')) {
      list = NIL;        /* done with list */
      s[i-1] = '\0';        /* tie off last item */
    }
    fa[k] = NIL;        /* default to no argument */
    if (!strcmp (s,"UID")) {    /* no-op if implicit */
      if (!uid) f[k++] = fetch_uid;
    }
    else if (!strcmp (s,"FLAGS")) f[k++] = fetch_flags;
    else if (!strcmp (s,"INTERNALDATE")) f[k++] = fetch_internaldate;
    else if (!strcmp (s,"RFC822.SIZE")) f[k++] = fetch_rfc822_size;
    else if (!strcmp (s,"ENVELOPE")) {
      parse_envs = T;        /* we will need to parse envelopes */
      f[k++] = fetch_envelope;
    }
    else if (!strcmp (s,"BODY")) {
      parse_envs = parse_bodies = T;
      f[k++] = fetch_body;
    }
    else if (!strcmp (s,"BODYSTRUCTURE")) {
      parse_envs = parse_bodies = T;
      f[k++] = fetch_bodystructure;
    }
    else if (!strcmp (s,"RFC822")) {
      fa[k] = s[6] ? (void *) FT_PEEK : NIL;
      f[k++] = fetch_rfc822;
    }
    else if (!strcmp (s,"RFC822.HEADER")) f[k++] = fetch_rfc822_header;
    else if (!strcmp (s,"RFC822.TEXT")) {
      fa[k] = s[11] ? (void *) FT_PEEK : NIL;
      f[k++] = fetch_rfc822_text;
    }

    else if (!strncmp (s,"BODY[",5) || !strncmp (s,"BODY.PEEK[",10) ||
         !strncmp (s,"BINARY[",7) || !strncmp (s,"BINARY.PEEK[",12) ||
         !strncmp (s,"BINARY.SIZE[",12)) {
      TEXTARGS *ta = (TEXTARGS *)
    memset (fs_get (sizeof (TEXTARGS)),0,sizeof (TEXTARGS));
      if (s[1] == 'I') {    /* body or binary? */
    ta->binary = FTB_BINARY;/* binary */
    f[k] = fetch_body_part_binary;
    if (s[6] == '.') {    /* wanted peek or size? */
      if (s[7] == 'P') ta->flags = FT_PEEK;
      else ta->binary |= FTB_SIZE;
      s += 12;        /* skip to section specifier */
    }
    else s += 7;        /* skip to section specifier */
    if (!isdigit (*s)) {    /* make sure top-level digit */
      fs_give ((void **) &ta);
      response = badbin;
      return;
    }
      }
      else {            /* body */
    f[k] = fetch_body_part_contents;
    if (s[4] == '.') {    /* wanted peek? */
      ta->flags = FT_PEEK;
      s += 10;        /* skip to section specifier */
    }
    else s += 5;        /* skip to section specifier */
      }
      if (*(v = s) != ']') {    /* non-empty section specifier? */
    if (isdigit (*v)) {    /* have section specifier? */
                /* need envelopes and bodies */
      parse_envs = parse_bodies = T;
      while (isdigit (*v))    /* scan to end of section specifier */
        if ((*++v == '.') && isdigit (v[1])) v++;
                /* any IMAP4rev1 stuff following? */
      if ((*v == '.') && isalpha (v[1])) {
        if (ta->binary) {    /* not if binary you don't */
          fs_give ((void **) &ta);
          response = badbin;
          return;
        }
        *v++ = '\0';    /* yes, tie off section specifier */
        if (!strncmp (v,"MIME",4)) {
          v += 4;        /* found <section>.MIME */
          f[k] = fetch_body_part_mime;
        }
      }
      else if (*v != ']') {    /* better be the end if no IMAP4rev1 stuff */
        fs_give ((void **) &ta);/* clean up */
        response = "%.80s BAD Syntax error in section specifier\015\012";
        return;
      }
    }

    if (*v != ']') {    /* IMAP4rev1 stuff here? */
      if (!strncmp (v,"HEADER",6)) {
        *v = '\0';        /* tie off in case top level */
        v += 6;        /* found [<section>.]HEADER */
        f[k] = fetch_body_part_header;
                /* partial headers wanted? */
        if (!strncmp (v,".FIELDS",7)) {
          v += 7;        /* yes */
          if (!strncmp (v,".NOT",4)) {
        v += 4;        /* want to exclude named headers */
        ta->flags |= FT_NOT;
          }
          if (*v || !(v = strtok (NIL,"\015\012")) ||
          !(ta->lines = parse_stringlist (&v,&list))) {
        fs_give ((void **) &ta);/* clean up */
        response = "%.80s BAD Syntax error in header fields\015\012";
        return;
          }
        }
      }
      else if (!strncmp (v,"TEXT",4)) {
        *v = '\0';        /* tie off in case top level */
        v += 4;        /* found [<section>.]TEXT */
        f[k] = fetch_body_part_text;
      }
      else {
        fs_give ((void **) &ta);/* clean up */
        response = "%.80s BAD Unknown section text specifier\015\012";
        return;
      }
    }
      }
                /* tie off section */
      if (*v == ']') *v++ = '\0';
      else {            /* bogon */
    if (ta->lines) mail_free_stringlist (&ta->lines);
    fs_give ((void **) &ta);/* clean up */
    response = "%.80s BAD Section specifier not terminated\015\012";
    return;
      }

      if ((*v == '<') &&    /* partial specifier? */
      ((ta->binary & FTB_SIZE) ||
       !(isdigit (v[1]) && ((ta->first = strtoul (v+1,(char **) &v,10)) ||
                v) &&
         (*v++ == '.') && (ta->last = strtoul (v,(char **) &v,10)) &&
         (*v++ == '>')))) {
    if (ta->lines) mail_free_stringlist (&ta->lines);
    fs_give ((void **) &ta);
    response ="%.80s BAD Syntax error in partial text specifier\015\012";
    return;
      }
      switch (*v) {        /* what's there now? */
      case ' ':            /* more follows */
    *--v = ' ';        /* patch a space back in */
    *--v = 'x';        /* and a hokey character before that */
    strtok (v," ");        /* reset strtok mechanism */
    break;
      case '\0':        /* none */
    break;
      case ')':            /* end of list */
    if (list && !v[1]) {    /* make sure of that */
      list = NIL;
      strtok (v," ");    /* reset strtok mechanism */
      break;        /* all done */
    }
                /* otherwise it's a bogon, drop in */
      default:            /* bogon */
    if (ta->lines) mail_free_stringlist (&ta->lines);
    fs_give ((void **) &ta);
    response = "%.80s BAD Syntax error after section specifier\015\012";
    return;
      }
                /* make copy of section specifier */
      if (s && *s) ta->section = cpystr (s);
      fa[k++] = (void *) ta;    /* set argument */
    }
    else {            /* unknown attribute */
      response = badatt;
      return;
    }
  } while ((s = strtok (NIL," ")) && (k < MAXFETCH) && list);
  else {
    response = misarg;        /* missing attribute list */
    return;
  }

  if (s) {            /* too many attributes? */
    response = "%.80s BAD Excessively complex FETCH attribute list\015\012";
    return;
  }
  if (list) {            /* too many attributes? */
    response = "%.80s BAD Unterminated FETCH attribute list\015\012";
    return;
  }
  f[k] = NIL;            /* tie off attribute list */
                /* c-client clobbers sequence, use spare */
  for (i = 1; i <= nmsgs; i++)
    mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence;
                /* for each requested message */
  for (i = 1; (i <= nmsgs) && (response != loseunknowncte); i++) {
                /* kill if dying */
    if (state == LOGOUT) longjmp (jmpenv,1);
    if (mail_elt (stream,i)->spare) {
                /* parse envelope, set body, do warnings */
      if (parse_envs) mail_fetchstructure (stream,i,parse_bodies ? &b : NIL);
      quell_events = T;        /* can't do any events now */
      PSOUT ("* ");        /* leader */
      pnum (i);
      PSOUT (" FETCH (");
      (*f[0]) (i,fa[0]);    /* do first attribute */
                /* for each subsequent attribute */
      for (k = 1; f[k] && (response != loseunknowncte); k++) {
    PBOUT (' ');        /* delimit with space */
    (*f[k]) (i,fa[k]);    /* do that attribute */
      }
      PSOUT (")\015\012");    /* trailer */
      quell_events = NIL;    /* events alright now */
    }
  }
}

/* Fetch message body structure (extensible)
 * Accepts: message number
 *        extra argument
 */

void fetch_bodystructure (unsigned long i,void *args)
{
  BODY *body;
  mail_fetchstructure (stream,i,&body);
  PSOUT ("BODYSTRUCTURE ");
  pbodystructure (body);    /* output body */
}


/* Fetch message body structure (non-extensible)
 * Accepts: message number
 *        extra argument
 */


void fetch_body (unsigned long i,void *args)
{
  BODY *body;
  mail_fetchstructure (stream,i,&body);
  PSOUT ("BODY ");        /* output attribute */
  pbody (body);            /* output body */
}

/* Fetch body part MIME header
 * Accepts: message number
 *        extra argument
 */

void fetch_body_part_mime (unsigned long i,void *args)
{
  TEXTARGS *ta = (TEXTARGS *) args;
  if (i) {            /* do work? */
    SIZEDTEXT st;
    unsigned long uid = mail_uid (stream,i);
    char *tmp = (char *) fs_get (100 + strlen (ta->section));
    sprintf (tmp,"BODY[%s.MIME]",ta->section);
                /* try to use remembered text */
    if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
    else {            /* get data */
      st.data = (unsigned char *)
    mail_fetch_mime (stream,i,ta->section,&st.size,ta->flags);
      if (ta->first || ta->last) remember (uid,tmp,&st);
    }
    pbodypartstring (i,tmp,&st,NIL,ta);
    fs_give ((void **) &tmp);
  }
  else {            /* clean up the arguments */
    fs_give ((void **) &ta->section);
    fs_give ((void **) &args);
  }
}


/* Fetch body part contents
 * Accepts: message number
 *        extra argument
 */

void fetch_body_part_contents (unsigned long i,void *args)
{
  TEXTARGS *ta = (TEXTARGS *) args;
  if (i) {            /* do work? */
    SIZEDTEXT st;
    char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
    unsigned long uid = mail_uid (stream,i);
    sprintf (tmp,"BODY[%s]",ta->section ? ta->section : "");
                /* try to use remembered text */
    if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
                /* get data */
    else if ((st.data = (unsigned char *)
          mail_fetch_body (stream,i,ta->section,&st.size,
                   ta->flags | FT_RETURNSTRINGSTRUCT)) &&
         (ta->first || ta->last)) remember (uid,tmp,&st);
    pbodypartstring (i,tmp,&st,&stream->private.string,ta);
    fs_give ((void **) &tmp);
  }
  else {            /* clean up the arguments */
    if (ta->section) fs_give ((void **) &ta->section);
    fs_give ((void **) &args);
  }
}

/* Fetch body part binary
 * Accepts: message number
 *        extra argument
 * Someday fix this to use stringstruct instead of memory
 */

void fetch_body_part_binary (unsigned long i,void *args)
{
  TEXTARGS *ta = (TEXTARGS *) args;
  if (i) {            /* do work? */
    SIZEDTEXT st,cst;
    BODY *body = mail_body (stream,i,ta->section);
    char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
    unsigned long uid = mail_uid (stream,i);
                /* try to use remembered text */
    if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
    else {            /* get data */
      st.data = (unsigned char *)
    mail_fetch_body (stream,i,ta->section,&st.size,ta->flags);
      if (ta->first || ta->last) remember (uid,tmp,&st);
    }
                /* what encoding was used? */
    if (body) switch (body->encoding) {
    case ENCBASE64:
      if (cst.data = rfc822_base64 (st.data,st.size,&cst.size)) break;
      fetch_uid (i,NIL);    /* wrote a space, so must do something */
      if (lsterr) fs_give ((void **) &lsterr);
      lsterr = cpystr ("Undecodable BASE64 contents");
      response = loseunknowncte;
      fs_give ((void **) &tmp);
      return;
    case ENCQUOTEDPRINTABLE:
      if (cst.data = rfc822_qprint (st.data,st.size,&cst.size)) break;
      fetch_uid (i,NIL);    /* wrote a space, so must do something */
      if (lsterr) fs_give ((void **) &lsterr);
      lsterr = cpystr ("Undecodable QUOTED-PRINTABLE contents");
      response = loseunknowncte;
      fs_give ((void **) &tmp);
      return;
    case ENC7BIT:        /* no need to convert any of these */
    case ENC8BIT:
    case ENCBINARY:
      cst.data = NIL;        /* no converted data to free */
      break;
    default:            /* unknown encoding, oops */
      fetch_uid (i,NIL);    /* wrote a space, so must do something */
      if (lsterr) fs_give ((void **) &lsterr);
      lsterr = cpystr ("Unknown Content-Transfer-Encoding");
      response = loseunknowncte;
      fs_give ((void **) &tmp);
      return;
    }
    else {
      if (lsterr) fs_give ((void **) &lsterr);
      lsterr = cpystr ("Invalid body part");
      response = loseunknowncte;
      fs_give ((void **) &tmp);
      return;
    }

                /* use decoded version if exists */
    if (cst.data) memcpy ((void *) &st,(void *) &cst,sizeof (SIZEDTEXT));
    if (ta->binary & FTB_SIZE) {/* just want size? */
      sprintf (tmp,"BINARY.SIZE[%s] %lu",ta->section ? ta->section : "",
           st.size);
      PSOUT (tmp);
    }
    else {            /* no, blat binary data */
      int f = mail_elt (stream,i)->seen;
      if (st.data) {        /* only if have useful data */
                /* partial specifier */
    if (ta->first || ta->last)
      sprintf (tmp,"BINARY[%s]<%lu> ",
           ta->section ? ta->section : "",ta->first);
    else sprintf (tmp,"BINARY[%s] ",ta->section ? ta->section : "");
                  /* in case first byte beyond end of text */
    if (st.size <= ta->first) st.size = ta->first = 0;
    else {            /* offset and truncate */
      st.data += ta->first;    /* move to desired position */
      st.size -= ta->first;    /* reduced size */
      if (ta->last && (st.size > ta->last)) st.size = ta->last;
    }
    if (st.size) sprintf (tmp + strlen (tmp),"{%lu}\015\012",st.size);
    else strcat (tmp,"\"\"");
    PSOUT (tmp);        /* write binary output */
    if (st.size && (PSOUTR (&st) == EOF)) ioerror(stdout,"writing binary");
      }
      else {
    sprintf (tmp,"BINARY[%s] NIL",ta->section ? ta->section : "");
    PSOUT (tmp);
      }
      changed_flags (i,f);    /* write changed flags */
    }
                /* free converted data */
    if (cst.data) fs_give ((void **) &cst.data);
    fs_give ((void **) &tmp);    /* and temporary string */
  }
  else {            /* clean up the arguments */
    if (ta->section) fs_give ((void **) &ta->section);
    fs_give ((void **) &args);
  }
}

/* Fetch MESSAGE/RFC822 body part header
 * Accepts: message number
 *        extra argument
 */

void fetch_body_part_header (unsigned long i,void *args)
{
  TEXTARGS *ta = (TEXTARGS *) args;
  unsigned long len = 100 + (ta->section ? strlen (ta->section) : 0);
  STRINGLIST *s;
  for (s = ta->lines; s; s = s->next) len += s->text.size + 1;
  if (i) {            /* do work? */
    SIZEDTEXT st;
    char *tmp = (char *) fs_get (len);
    PSOUT ("BODY[");
                /* output attribute */
    if (ta->section && *ta->section) {
      PSOUT (ta->section);
      PBOUT ('.');
    }
    PSOUT ("HEADER");
    if (ta->lines) {
      PSOUT ((ta->flags & FT_NOT) ? ".FIELDS.NOT " : ".FIELDS ");
      pastringlist (ta->lines);
    }
    strcpy (tmp,"]");        /* close section specifier */
    st.data = (unsigned char *)    /* get data (no hope in using remember here) */
      mail_fetch_header (stream,i,ta->section,ta->lines,&st.size,ta->flags);
    pbodypartstring (i,tmp,&st,NIL,ta);
    fs_give ((void **) &tmp);
  }
  else {            /* clean up the arguments */
    if (ta->lines) mail_free_stringlist (&ta->lines);
    if (ta->section) fs_give ((void **) &ta->section);
    fs_give ((void **) &args);
  }
}

/* Fetch MESSAGE/RFC822 body part text
 * Accepts: message number
 *        extra argument
 */

void fetch_body_part_text (unsigned long i,void *args)
{
  TEXTARGS *ta = (TEXTARGS *) args;
  if (i) {            /* do work? */
    SIZEDTEXT st;
    char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
    unsigned long uid = mail_uid (stream,i);
                /* output attribute */
    if (ta->section && *ta->section) sprintf (tmp,"BODY[%s.TEXT]",ta->section);
    else strcpy (tmp,"BODY[TEXT]");
                /* try to use remembered text */
    if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
                /* get data */
    else if ((st.data = (unsigned char *)
          mail_fetch_text (stream,i,ta->section,&st.size,
                   ta->flags | FT_RETURNSTRINGSTRUCT)) &&
         (ta->first || ta->last)) remember (uid,tmp,&st);
    pbodypartstring (i,tmp,&st,&stream->private.string,ta);
    fs_give ((void **) &tmp);
  }
  else {            /* clean up the arguments */
    if (ta->section) fs_give ((void **) &ta->section);
    fs_give ((void **) &args);
  }
}


/* Remember body part text for subsequent partial fetching
 * Accepts: message UID
 *        body part id
 *        text
 *        string
 */

void remember (unsigned long uid,char *id,SIZEDTEXT *st)
{
  lastuid = uid;        /* remember UID */
  if (lastid) fs_give ((void **) &lastid);
  lastid = cpystr (id);        /* remember body part id */
  if (lastst.data) fs_give ((void **) &lastst.data);
                /* remember text */
  lastst.data = (unsigned char *)
    memcpy (fs_get (st->size + 1),st->data,st->size);
  lastst.size = st->size;
}


/* Fetch envelope
 * Accepts: message number
 *        extra argument
 */

void fetch_envelope (unsigned long i,void *args)
{
  ENVELOPE *env = mail_fetchenvelope (stream,i);
  PSOUT ("ENVELOPE ");        /* output attribute */
  penv (env);            /* output envelope */
}

/* Fetch flags
 * Accepts: message number
 *        extra argument
 */

void fetch_flags (unsigned long i,void *args)
{
  unsigned long u;
  char *t,tmp[MAILTMPLEN];
  int c = NIL;
  MESSAGECACHE *elt = mail_elt (stream,i);
  if (!elt->valid) {        /* have valid flags yet? */
    sprintf (tmp,"%lu",i);
    mail_fetch_flags (stream,tmp,NIL);
  }
  PSOUT ("FLAGS (");        /* output attribute */
                /* output system flags */
  if (elt->recent) put_flag (&c,"\\Recent");
  if (elt->seen) put_flag (&c,"\\Seen");
  if (elt->deleted) put_flag (&c,"\\Deleted");
  if (elt->flagged) put_flag (&c,"\\Flagged");
  if (elt->answered) put_flag (&c,"\\Answered");
  if (elt->draft) put_flag (&c,"\\Draft");
  if (u = elt->user_flags) do    /* any user flags? */
    if (t = stream->user_flags[find_rightmost_bit (&u)]) put_flag (&c,t);
  while (u);            /* until no more user flags */
  PBOUT (')');            /* end of flags */
  elt->spare2 = NIL;        /* we've sent the update */
}


/* Output a flag
 * Accepts: pointer to current delimiter character
 *        flag to output
 * Changes delimiter character to space
 */

void put_flag (int *c,char *s)
{
  if (*c) PBOUT (*c);        /* put delimiter */
  PSOUT (s);            /* dump flag */
  *c = ' ';            /* change delimiter if necessary */
}


/* Output flags if was unseen
 * Accepts: message number
 *        prior value of Seen flag
 */

void changed_flags (unsigned long i,int f)
{
                /* was unseen, now seen? */
  if (!f && mail_elt (stream,i)->seen) {
    PBOUT (' ');        /* yes, delimit with space */
    fetch_flags (i,NIL);    /* output flags */
  }
}

/* Fetch message internal date
 * Accepts: message number
 *        extra argument
 */

void fetch_internaldate (unsigned long i,void *args)
{
  char tmp[MAILTMPLEN];
  MESSAGECACHE *elt = mail_elt (stream,i);
  if (!elt->day) {        /* have internal date yet? */
    sprintf (tmp,"%lu",i);
    mail_fetch_fast (stream,tmp,NIL);
  }
  PSOUT ("INTERNALDATE \"");
  PSOUT (mail_date (tmp,elt));
  PBOUT ('"');
}


/* Fetch unique identifier
 * Accepts: message number
 *        extra argument
 */

void fetch_uid (unsigned long i,void *args)
{
  PSOUT ("UID ");
  pnum (mail_uid (stream,i));
}

/* Fetch complete RFC-822 format message
 * Accepts: message number
 *        extra argument
 */

void fetch_rfc822 (unsigned long i,void *args)
{
  if (i) {            /* do work? */
    int f = mail_elt (stream,i)->seen;
#if 0
    SIZEDTEXT st;
    st.data = (unsigned char *)
      mail_fetch_message (stream,i,&st.size,(long) args);
    pbodypartstring (i,"RFC822",&st,NIL,NIL);
#else
    /* Yes, this version is bletcherous, but mail_fetch_message() requires
       too much memory */
    SIZEDTEXT txt,hdr;
    char *s = mail_fetch_header (stream,i,NIL,NIL,&hdr.size,FT_PEEK);
    hdr.data = (unsigned char *) memcpy (fs_get (hdr.size),s,hdr.size);
    txt.data = (unsigned char *)
      mail_fetch_text (stream,i,NIL,&txt.size,
               ((long) args) | FT_RETURNSTRINGSTRUCT);
    PSOUT ("RFC822 {");
    pnum (hdr.size + txt.size);
    PSOUT ("}\015\012");
    ptext (&hdr,NIL);
    ptext (&txt,&stream->private.string);
    fs_give ((void **) &hdr.data);
#endif
    changed_flags (i,f);    /* output changed flags */
  }
}


/* Fetch RFC-822 header
 * Accepts: message number
 *        extra argument
 */

void fetch_rfc822_header (unsigned long i,void *args)
{
  SIZEDTEXT st;
  st.data = (unsigned char *)
    mail_fetch_header (stream,i,NIL,NIL,&st.size,FT_PEEK);
  pbodypartstring (i,"RFC822.HEADER",&st,NIL,NIL);
}


/* Fetch RFC-822 message length
 * Accepts: message number
 *        extra argument
 */

void fetch_rfc822_size (unsigned long i,void *args)
{
  char tmp[MAILTMPLEN];
  MESSAGECACHE *elt = mail_elt (stream,i);
  if (!elt->rfc822_size) {    /* have message size yet? */
    sprintf (tmp,"%lu",i);
    mail_fetch_fast (stream,tmp,NIL);
  }
  PSOUT ("RFC822.SIZE ");
  pnum (elt->rfc822_size);
}

/* Fetch RFC-822 text only
 * Accepts: message number
 *        extra argument
 */

void fetch_rfc822_text (unsigned long i,void *args)
{
  if (i) {            /* do work? */
    int f = mail_elt (stream,i)->seen;
    SIZEDTEXT st;
    st.data = (unsigned char *)
      mail_fetch_text (stream,i,NIL,&st.size,
               ((long) args) | FT_RETURNSTRINGSTRUCT);
    pbodypartstring (i,"RFC822.TEXT",&st,&stream->private.string,NIL);
  }
}

/* Print envelope
 * Accepts: body
 */

void penv (ENVELOPE *env)
{
  PBOUT ('(');            /* delimiter */
  if (env) {            /* only if there is an envelope */
    pnstring (env->date);    /* output envelope fields */
    PBOUT (' ');
    pnstring (env->subject);
    PBOUT (' ');
    paddr (env->from);
    PBOUT (' ');
    paddr (env->sender);
    PBOUT (' ');
    paddr (env->reply_to);
    PBOUT (' ');
    paddr (env->to);
    PBOUT (' ');
    paddr (env->cc);
    PBOUT (' ');
    paddr (env->bcc);
    PBOUT (' ');
    pnstring (env->in_reply_to);
    PBOUT (' ');
    pnstring (env->message_id);
  }
                /* no envelope */
  else PSOUT ("NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL");
  PBOUT (')');            /* end of envelope */
}

/* Print body structure (extensible)
 * Accepts: body
 */

void pbodystructure (BODY *body)
{
  PBOUT ('(');            /* delimiter */
  if (body) {            /* only if there is a body */
    PART *part;
                /* multipart type? */
    if (body->type == TYPEMULTIPART) {
                /* print each part */
      if (part = body->nested.part)
    for (; part; part = part->next) pbodystructure (&(part->body));
      else pbodystructure (NIL);
      PBOUT (' ');        /* space delimiter */
      pstring (body->subtype);    /* subtype */
      PBOUT (' ');
      pparam (body->parameter);    /* multipart body extension data */
      PBOUT (' ');
      if (body->disposition.type) {
    PBOUT ('(');
    pstring (body->disposition.type);
    PBOUT (' ');
    pparam (body->disposition.parameter);
    PBOUT (')');
      }
      else PSOUT ("NIL");
      PBOUT (' ');
      pnstringorlist (body->language);
      PBOUT (' ');
      pnstring (body->location);
    }

    else {            /* non-multipart body type */
      pstring ((char *) body_types[body->type]);
      PBOUT (' ');
      pstring (body->subtype);
      PBOUT (' ');
      pparam (body->parameter);
      PBOUT (' ');
      pnstring (body->id);
      PBOUT (' ');
      pnstring (body->description);
      PBOUT (' ');
      pstring ((char *) body_encodings[body->encoding]);
      PBOUT (' ');
      pnum (body->size.bytes);
      switch (body->type) {    /* extra stuff depends upon body type */
      case TYPEMESSAGE:
                /* can't do this if not RFC822 */
    if (strcmp (body->subtype,"RFC822")) break;
    PBOUT (' ');
    penv (body->nested.msg->env);
    PBOUT (' ');
    pbodystructure (body->nested.msg->body);
      case TYPETEXT:
    PBOUT (' ');
    pnum (body->size.lines);
    break;
      default:
    break;
      }
      PBOUT (' ');
      pnstring (body->md5);
      PBOUT (' ');
      if (body->disposition.type) {
    PBOUT ('(');
    pstring (body->disposition.type);
    PBOUT (' ');
    pparam (body->disposition.parameter);
    PBOUT (')');
      }
      else PSOUT ("NIL");
      PBOUT (' ');
      pnstringorlist (body->language);
      PBOUT (' ');
      pnstring (body->location);
    }
  }
                /* no body */
  else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0 NIL NIL NIL NIL");
  PBOUT (')');            /* end of body */
}

/* Print body (non-extensible)
 * Accepts: body
 */

void pbody (BODY *body)
{
  PBOUT ('(');            /* delimiter */
  if (body) {            /* only if there is a body */
    PART *part;
                /* multipart type? */
    if (body->type == TYPEMULTIPART) {
                /* print each part */
      if (part = body->nested.part)
    for (; part; part = part->next) pbody (&(part->body));
      else pbody (NIL);
      PBOUT (' ');        /* space delimiter */
      pstring (body->subtype);    /* and finally the subtype */
    }
    else {            /* non-multipart body type */
      pstring ((char *) body_types[body->type]);
      PBOUT (' ');
      pstring (body->subtype);
      PBOUT (' ');
      pparam (body->parameter);
      PBOUT (' ');
      pnstring (body->id);
      PBOUT (' ');
      pnstring (body->description);
      PBOUT (' ');
      pstring ((char *) body_encodings[body->encoding]);
      PBOUT (' ');
      pnum (body->size.bytes);
      switch (body->type) {    /* extra stuff depends upon body type */
      case TYPEMESSAGE:
                /* can't do this if not RFC822 */
    if (strcmp (body->subtype,"RFC822")) break;
    PBOUT (' ');
    penv (body->nested.msg ? body->nested.msg->env : NIL);
    PBOUT (' ');
    pbody (body->nested.msg ? body->nested.msg->body : NIL);
      case TYPETEXT:
    PBOUT (' ');
    pnum (body->size.lines);
    break;
      default:
    break;
      }
    }
  }
                /* no body */
  else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0");
  PBOUT (')');            /* end of body */
}

/* Print parameter list
 * Accepts: paramter
 */

void pparam (PARAMETER *param)
{
  if (param) {            /* one specified? */
    PBOUT ('(');
    do {
      pstring (param->attribute);
      PBOUT (' ');
      pstring (param->value);
      if (param = param->next) PBOUT (' ');
    } while (param);
    PBOUT (')');        /* end of parameters */
  }
  else PSOUT ("NIL");
}


/* Print address list
 * Accepts: address list
 */

void paddr (ADDRESS *a)
{
  if (a) {            /* have anything in address? */
    PBOUT ('(');        /* open the address list */
    do {            /* for each address */
      PBOUT ('(');        /* open the address */
      pnstring (a->personal);    /* personal name */
      PBOUT (' ');
      pnstring (a->adl);    /* at-domain-list */
      PBOUT (' ');
      pnstring (a->mailbox);    /* mailbox */
      PBOUT (' ');
      pnstring (a->host);    /* domain name of mailbox's host */
      PBOUT (')');        /* terminate address */
    } while (a = a->next);    /* until end of address */
    PBOUT (')');        /* close address list */
  }
  else PSOUT ("NIL");        /* empty address */
}

/* Print set
 * Accepts: set
 */

void pset (SEARCHSET **set)
{
  SEARCHSET *cur = *set;
  while (cur) {            /* while there's a set to do */
    pnum (cur->first);        /* output first value */
    if (cur->last) {        /* if range, output second value of range */
      PBOUT (':');
      pnum (cur->last);
    }
    if (cur = cur->next) PBOUT (',');
  }
  mail_free_searchset (set);    /* flush set */
}


/* Print number
 * Accepts: number
 */

void pnum (unsigned long i)
{
  char tmp[MAILTMPLEN];
  sprintf (tmp,"%lu",i);
  PSOUT (tmp);
}


/* Print string
 * Accepts: string
 */

void pstring (char *s)
{
  SIZEDTEXT st;
  st.data = (unsigned char *) s;/* set up sized text */
  st.size = strlen (s);
  psizedstring (&st,NIL);    /* print string */
}


/* Print nstring
 * Accepts: string or NIL
 */

void pnstring (char *s)
{
  if (s) pstring (s);        /* print string */
  else PSOUT ("NIL");
}


/* Print atom or string
 * Accepts: astring
 */

void pastring (char *s)
{
  char *t;
  if (!*s) PSOUT ("\"\"");    /* empty string */
  else {            /* see if atom */
    for (t = s; (*t > ' ') && !(*t & 0x80) &&
     (*t != '"') && (*t != '\\') && (*t != '(') && (*t != ')') &&
     (*t != '{') && (*t != '%') && (*t != '*'); t++);
    if (*t) pstring (s);    /* not an atom */
    else PSOUT (s);        /* else plop down as atomic */
  }
}

/* Print sized text as quoted
 * Accepts: sized text
 */

void psizedquoted (SIZEDTEXT *s)
{
  PBOUT ('"');            /* use quoted string */
  ptext (s,NIL);
  PBOUT ('"');
}


/* Print sized text as literal
 * Accepts: sized text
 */

void psizedliteral (SIZEDTEXT *s,STRING *st)
{
  PBOUT ('{');            /* print literal size */
  pnum (s->size);
  PSOUT ("}\015\012");
  ptext (s,st);
}

/* Print sized text as literal or quoted string
 * Accepts: sized text
 *        alternative stringstruct of text
 */

void psizedstring (SIZEDTEXT *s,STRING *st)
{
  unsigned char c;
  unsigned long i;
        
  if (s->data) {        /* if text, check if must use literal */
    for (i = 0; ((i < s->size) && ((c = s->data[i]) & 0xe0) &&
         !(c & 0x80) && (c != '"') && (c != '\\')); ++i);
                /* must use literal if not all QUOTED-CHAR */
    if (i < s->size) psizedliteral (s,st);
    else psizedquoted (s);
  }
  else psizedliteral (s,st);
}


/* Print sized text as literal or quoted string
 * Accepts: sized text
 */

void psizedastring (SIZEDTEXT *s)
{
  unsigned long i;
  unsigned int atomp = s->size ? T : NIL;
  for (i = 0; i < s->size; i++){/* check if must use literal */
    if (!(s->data[i] & 0xe0) || (s->data[i] & 0x80) ||
    (s->data[i] == '"') || (s->data[i] == '\\')) {
      psizedliteral (s,NIL);
      return;
    }
    else switch (s->data[i]) {    /* else see if any atom-specials */
    case '(': case ')': case '{': case ' ':
    case '%': case '*':        /* list-wildcards */
    case ']':            /* resp-specials */
                /* CTL and quoted-specials in literal check */
      atomp = NIL;        /* not an atom */
    }
  }
  if (atomp) ptext (s,NIL);    /* print as atom */
  else psizedquoted (s);    /* print as quoted string */
}

/* Print string list
 * Accepts: string list
 */

void pastringlist (STRINGLIST *s)
{
  PBOUT ('(');            /* start list */
  do {
    psizedastring (&s->text);    /* output list member */
    if (s->next) PBOUT (' ');
  } while (s = s->next);
  PBOUT (')');            /* terminate list */
}


/* Print nstring or list of strings
 * Accepts: string / string list
 */

void pnstringorlist (STRINGLIST *s)
{
  if (!s) PSOUT ("NIL");    /* no argument given */
  else if (s->next) {        /* output list as list of strings*/
    PBOUT ('(');        /* start list */
    do {            /* output list member */
      psizedstring (&s->text,NIL);
      if (s->next) PBOUT (' ');
    } while (s = s->next);
    PBOUT (')');        /* terminate list */
  } 
                /* and single-element list as string */
  else psizedstring (&s->text,NIL);
}

/* Print body part string
 * Accepts: message number
 *        body part id (note: must have space at end to append stuff)
 *        sized text of string
 *        alternative stringstruct of string
 *        text printing arguments
 */

void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs,
              TEXTARGS *ta)
{
  int f = mail_elt (stream,msgno)->seen;
                /* ignore stringstruct if non-initialized */
  if (bs && !bs->curpos) bs = NIL;
  if (ta && st->size) {        /* only if have useful data */
                /* partial specifier */
    if (ta->first || ta->last) sprintf (id + strlen (id),"<%lu>",ta->first);
                  /* in case first byte beyond end of text */
    if (st->size <= ta->first) st->size = ta->first = 0;
    else {
      if (st->data) {        /* offset and truncate */
    st->data += ta->first;    /* move to desired position */
    st->size -= ta->first;    /* reduced size */
      }
      else if (bs && (SIZE (bs) >= ta->first))
    SETPOS (bs,ta->first + GETPOS (bs));
      else st->size = 0;    /* shouldn't happen */
      if (ta->last && (st->size > ta->last)) st->size = ta->last;
    }
  }
  PSOUT (id);
  PBOUT (' ');
  psizedstring (st,bs);        /* output string */
  changed_flags (msgno,f);    /* and changed flags */
}

/*  RFC 3501 technically forbids NULs in literals.  Normally, the delivering
 * MTA would take care of MIME converting the message text so that it is
 * NUL-free.  If it doesn't, then we have the choice of either violating
 * IMAP by sending NULs, corrupting the data, or going to lots of work to do
 * MIME conversion in the IMAP server.
 */

/* Print raw sized text
 * Accepts: sizedtext
 */

void ptext (SIZEDTEXT *txt,STRING *st)
{
  unsigned char c,*s;
  unsigned long i = txt->size;
  if (s = txt->data) while (i && ((PBOUT ((c = *s++) ? c : 0x80) != EOF))) --i;
  else if (st) while (i && (PBOUT ((c = SNX (st)) ? c : 0x80) != EOF)) --i;
                /* failed to complete? */
  if (i) ioerror (stdout,"writing text");
}

/* Print thread
 * Accepts: thread
 */

void pthread (THREADNODE *thr)
{
  THREADNODE *t;
  while (thr) {            /* for each branch */
    PBOUT ('(');        /* open branch */
    if (thr->num) {        /* first node message number */
      pnum (thr->num);
      if (t = thr->next) {    /* any subsequent nodes? */
    PBOUT (' ');
    while (t) {        /* for each subsequent node */
      if (t->branch) {    /* branches? */
        pthread (t);    /* yes, recurse to do branch */
        t = NIL;        /* done */
      }
      else {        /* just output this number */
        pnum (t->num);
        t = t->next;    /* and do next message */
      }
      if (t) PBOUT (' ');    /* delimit if more to come */
    }
      }
    }
    else pthread (thr->next);    /* nest for dummy */
    PBOUT (')');        /* done with this branch */
    thr = thr->branch;        /* do next branch */
  }
}

/* Print capabilities
 * Accepts: option flag
 */

void pcapability (long flag)
{
  unsigned long i;
  char *s;
  struct stat sbuf;
  AUTHENTICATOR *auth;
  THREADER *thr = (THREADER *) mail_parameters (NIL,GET_THREADERS,NIL);
                /* always output protocol level */
  PSOUT ("CAPABILITY IMAP4REV1 I18NLEVEL=1 LITERAL+");
#ifdef NETSCAPE_BRAIN_DAMAGE
  PSOUT (" X-NETSCAPE");
#endif
  if (flag >= 0) {        /* want post-authentication capabilities? */
    PSOUT (" IDLE UIDPLUS NAMESPACE CHILDREN MAILBOX-REFERRALS BINARY UNSELECT ESEARCH WITHIN SCAN SORT");
    while (thr) {        /* threaders */
      PSOUT (" THREAD=");
      PSOUT (thr->name);
      thr = thr->next;
    }
    if (!anonymous) PSOUT (" MULTIAPPEND");
  }
  if (flag <= 0) {        /* want pre-authentication capabilities? */
    PSOUT (" SASL-IR LOGIN-REFERRALS");
    if (s = ssl_start_tls (NIL)) fs_give ((void **) &s);
    else PSOUT (" STARTTLS");
                /* disable plaintext */
    if (!(i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL)))
      PSOUT (" LOGINDISABLED");
    for (auth = mail_lookup_auth (1); auth; auth = auth->next)
      if (auth->server && !(auth->flags & AU_DISABLE) &&
      !(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) {
    PSOUT (" AUTH=");
    PSOUT (auth->name);
      }
    if (!stat (ANOFILE,&sbuf)) PSOUT (" AUTH=ANONYMOUS");
  }
}

/* Anonymous users may only use these mailboxes in these namespaces */

char *oktab[] = {"#news.", "#ftp/", "#public/", 0};


/* Check if mailbox name is OK
 * Accepts: reference name
 *        mailbox name
 */

long nameok (char *ref,char *name)
{
  int i;
  unsigned char *s,*t;
  if (!name) return NIL;    /* failure if missing name */
  if (!anonymous) return T;    /* otherwise OK if not anonymous */
                /* validate reference */
  if (ref && ((*ref == '#') || (*ref == '{')))
    for (i = 0; oktab[i]; i++) {
      for (s = ref, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++);
      if (!*t) {        /* reference OK */
    if (*name == '#') break;/* check name if override */
    else return T;        /* otherwise done */
      }
    }
                /* ordinary names are OK */
  if ((*name != '#') && (*name != '{')) return T;
  for (i = 0; oktab[i]; i++) {    /* validate mailbox */
    for (s = name, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++);
    if (!*t) return T;        /* name is OK */
  }
  response = "%.80s NO Anonymous may not %.80s this name\015\012";
  return NIL;
}


/* Convert possible BBoard name to actual name
 * Accepts: command
 *        mailbox name
 * Returns: maibox name
 */

char *bboardname (char *cmd,char *name)
{
  if (cmd[0] == 'B') {        /* want bboard? */
    char *s = litstk[litsp++] = (char *) fs_get (strlen (name) + 9);
    sprintf (s,"#public/%s",(*name == '/') ? name+1 : name);
    name = s;
  }
  return name;
}

/* Test if name is news proxy
 * Accepts: name
 * Returns: T if news proxy, NIL otherwise
 */

long isnewsproxy (char *name)
{
  return (nntpproxy && (name[0] == '#') &&
      ((name[1] == 'N') || (name[1] == 'n')) &&
      ((name[2] == 'E') || (name[2] == 'e')) &&
      ((name[3] == 'W') || (name[3] == 'w')) &&
      ((name[4] == 'S') || (name[4] == 's')) && (name[5] == '.')) ?
    LONGT : NIL;
}


/* News proxy generate canonical pattern
 * Accepts: reference
 *        pattern
 *        buffer to return canonical pattern
 * Returns: T on success with pattern in buffer, NIL on failure
 */

long newsproxypattern (char *ref,char *pat,char *pattern,long flag)
{
  if (!nntpproxy) return NIL;
  if (strlen (ref) > NETMAXMBX) {
    sprintf (pattern,"Invalid reference specification: %.80s",ref);
    mm_log (pattern,ERROR);
    return NIL;
  }
  if (strlen (pat) > NETMAXMBX) {
    sprintf (pattern,"Invalid pattern specification: %.80s",pat);
    mm_log (pattern,ERROR);
    return NIL;
  }
  if (flag) {            /* prepend proxy specifier */
    sprintf (pattern,"{%.300s/nntp}",nntpproxy);
    pattern += strlen (pattern);
  }
  if (*ref) {            /* have a reference */
    strcpy (pattern,ref);    /* copy reference to pattern */
                /* # overrides mailbox field in reference */
    if (*pat == '#') strcpy (pattern,pat);
                /* pattern starts, reference ends, with . */
    else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
      strcat (pattern,pat + 1);    /* append, omitting one of the period */
    else strcat (pattern,pat);    /* anything else is just appended */
  }
  else strcpy (pattern,pat);    /* just have basic name */
  return isnewsproxy (pattern);
}

/* IMAP4rev1 Authentication responder
 * Accepts: challenge
 *        length of challenge
 *        pointer to response length return location if non-NIL
 * Returns: response
 */

#define RESPBUFLEN 8*MAILTMPLEN

char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen)
{
  unsigned long i,j;
  unsigned char *t,resp[RESPBUFLEN];
  if (initial) {        /* initial response given? */
    if (clen) return NIL;    /* not permitted */
                /* set up response */
    i = strlen ((char *) (t = initial));
    initial = NIL;        /* no more initial response */
    if ((*t == '=') && !t[1]) {    /* SASL-IR does this for 0-length response */
      if (rlen) *rlen = 0;    /* set length zero if empty */
      return cpystr ("");    /* and return empty string as response */
    }
  }
  else {            /* issue challenge, get response */
    PSOUT ("+ ");
    for (t = rfc822_binary ((void *) challenge,clen,&i),j = 0; j < i; j++)
      if (t[j] > ' ') PBOUT (t[j]);
    fs_give ((void **) &t);
    CRLF;
    PFLUSH ();            /* dump output buffer */
                /* slurp response buffer */
    slurp ((char *) resp,RESPBUFLEN,INPUTTIMEOUT);
    if (!(t = (unsigned char *) strchr ((char *) resp,'\012')))
      return (char *) flush ();
    if (t[-1] == '\015') --t;    /* remove CR */
    *t = '\0';            /* tie off buffer */
    if (resp[0] == '*') {
      cancelled = T;
      return NIL;
    }
    i = t - resp;        /* length of response */
    t = resp;            /* set up for return call */
  }
  return (i % 4) ? NIL :    /* return if valid BASE64 */
    (char *) rfc822_base64 (t,i,rlen ? rlen : &i);
}

/* Proxy copy across mailbox formats
 * Accepts: mail stream
 *        sequence to copy on this stream
 *        destination mailbox
 *        option flags
 * Returns: T if success, else NIL
 */

long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
{
  MAILSTREAM *ts;
  STRING st;
  MSGDATA md;
  SEARCHSET *set;
  char tmp[MAILTMPLEN];
  unsigned long i,j;
  md.stream = stream;
  md.msgno = 0;
  md.flags = md.date = NIL;
  md.message = &st;
  /* Currently ignores CP_MOVE and CP_DEBUG */
  if (!((options & CP_UID) ?    /* validate sequence */
    mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence)))
    return NIL;
  response = win;        /* cancel previous errors */
  if (lsterr) fs_give ((void **) &lsterr);
                /* c-client clobbers sequence, use spare */
  for (i = 1,j = 0,set = mail_newsearchset (); i <= nmsgs; i++)
    if (mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence) {
      mail_append_set (set,mail_uid (stream,i));
      if (!j) md.msgno = (j = i) - 1;
    }
                /* only if at least one message to copy */
  if (j && !mail_append_multiple (NIL,mailbox,proxy_append,(void *) &md)) {
    response = trycreate ? losetry : lose;
    if (set) mail_free_searchset (&set);
    return NIL;
  }
  if (caset) csset = set;    /* set for return value now */
  else if (set) mail_free_searchset (&set);
  response = win;        /* stomp any previous babble */
  if (md.msgno) {        /* get new driver name if was dummy */
    sprintf (tmp,"Cross-format (%.80s -> %.80s) COPY completed",
         stream->dtb->name,(ts = mail_open (NIL,mailbox,OP_PROTOTYPE)) ?
         ts->dtb->name : "unknown");
    mm_log (tmp,NIL);
  }
  return LONGT;
}

/* Proxy append message callback
 * Accepts: MAIL stream
 *        append data package
 *        pointer to return initial flags
 *        pointer to return message internal date
 *        pointer to return stringstruct of message or NIL to stop
 * Returns: T if success (have message or stop), NIL if error
 */

long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date,
           STRING **message)
{
  MESSAGECACHE *elt;
  unsigned long i;
  char *s,*t,tmp[MAILTMPLEN];
  MSGDATA *md = (MSGDATA *) data;
  if (md->flags) fs_give ((void **) &md->flags);
  if (md->date) fs_give ((void **) &md->date);
  *message = NIL;        /* assume all done */
  *flags = *date = NIL;
  while (++md->msgno <= nmsgs)
    if ((elt = mail_elt (md->stream,md->msgno))->spare) {
      if (!(elt->valid && elt->day)) {
    sprintf (tmp,"%lu",md->msgno);
    mail_fetch_fast (md->stream,tmp,NIL);
      }
      memset (s = tmp,0,MAILTMPLEN);
                /* copy flags */
      if (elt->seen) strcat (s," \\Seen");
      if (elt->deleted) strcat (s," \\Deleted");
      if (elt->flagged) strcat (s," \\Flagged");
      if (elt->answered) strcat (s," \\Answered");
      if (elt->draft) strcat (s," \\Draft");
      if (i = elt->user_flags) do 
    if ((t = md->stream->user_flags[find_rightmost_bit (&i)]) && *t &&
        (strlen (t) < ((size_t) (MAILTMPLEN-((s += strlen (s))+2-tmp))))) {
    *s++ = ' ';        /* space delimiter */
    strcpy (s,t);
      } while (i);        /* until no more user flags */
      *message = md->message;    /* set up return values */
      *flags = md->flags = cpystr (tmp + 1);
      *date = md->date = cpystr (mail_date (tmp,elt));
      INIT (md->message,msg_string,(void *) md,elt->rfc822_size);
      break;            /* process this message */
    }
  return LONGT;
}

/* Append message callback
 * Accepts: MAIL stream
 *        append data package
 *        pointer to return initial flags
 *        pointer to return message internal date
 *        pointer to return stringstruct of message or NIL to stop
 * Returns: T if success (have message or stop), NIL if error
 */

long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date,
         STRING **message)
{
  unsigned long i,j;
  char *t;
  APPENDDATA *ad = (APPENDDATA *) data;
  unsigned char *arg = ad->arg;
                /* flush text of previous message */
  if (t = ad->flags) fs_give ((void **) &ad->flags);
  if (t = ad->date) fs_give ((void **) &ad->date);
  if (t = ad->msg) fs_give ((void **) &ad->msg);
  *flags = *date = NIL;        /* assume no flags or date */
  if (t) {            /* have previous message? */
    if (!*arg) {        /* if least one message, and no more coming */
      *message = NIL;        /* set stop */
      return LONGT;        /* return success */
    }
    else if (*arg++ != ' ') {    /* must have a delimiter to next argument */
      response = misarg;    /* oops */
      return NIL;
    }
  }
  *message = ad->message;    /* return pointer to message stringstruct */
  if (*arg == '(') {        /* parse optional flag list */
    t = ++arg;            /* pointer to flag list contents */
    while (*arg && (*arg != ')')) arg++;
    if (*arg) *arg++ = '\0';
    if (*arg == ' ') arg++;
    *flags = ad->flags = cpystr (t);
  }
                /* parse optional date */
  if (*arg == '"') *date = ad->date = cpystr (snarf (&arg));
  if (!arg || (*arg != '{'))    /* parse message */
    response = "%.80s BAD Missing literal in %.80s\015\012";
  else if (!isdigit (arg[1]))
    response = "%.80s BAD Missing message to %.80s\015\012";
  else if (!(i = strtoul (arg+1,&t,10)))
    response = "%.80s NO Empty message to %.80s\015\012";
  else if (i > MAXAPPENDTXT)    /* maybe relax this a little */
    response = "%.80s NO Excessively large message to %.80s\015\012";
  else if (((*t == '+') && (t[1] == '}') && !t[2]) || ((*t == '}') && !t[1])) {
                /* get a literal buffer */
    inliteral (ad->msg = (char *) fs_get (i+1),i);
                    /* get new command tail */
    slurp (ad->arg,CMDLEN - (ad->arg - cmdbuf),INPUTTIMEOUT);
    if (strchr (ad->arg,'\012')) {
                /* reset strtok mechanism, tie off if done */
      if (!strtok (ad->arg,"\015\012")) *ad->arg = '\0';
                /* possible LITERAL+? */
      if (((j = strlen (ad->arg)) > 3) && (ad->arg[j - 1] == '}') &&
      (ad->arg[j - 2] == '+') && isdigit (ad->arg[j - 3])) {
                /* back over possible count */
    for (j -= 4; j && isdigit (ad->arg[j]); j--);
    if (ad->arg[j] == '{') {/* found a literal? */
      litplus.ok = T;    /* yes, note LITERAL+ in effect, set size */
      litplus.size = strtoul (ad->arg + j + 1,NIL,10);
    }
      }
                /* initialize stringstruct */
      INIT (ad->message,mail_string,(void *) ad->msg,i);
      return LONGT;        /* ready to go */
    }
    flush ();            /* didn't find end of line? */
    fs_give ((void **) &ad->msg);
  }
  else response = badarg;    /* not a literal */
  return NIL;            /* error */
}

/* Got COPY UID data
 * Accepts: MAIL stream
 *        mailbox name
 *        UID validity
 *        source set of UIDs
 *        destination set of UIDs
 */

void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity,
          SEARCHSET *sourceset,SEARCHSET *destset)
{
  if (cauidvalidity) fatal ("duplicate COPYUID/APPENDUID data");
  cauidvalidity = uidvalidity;
  csset = sourceset;
  caset = destset;
}


/* Got APPEND UID data
 * Accepts: mailbox name
 *        UID validity
 *        destination set of UIDs
 */

void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set)
{
  copyuid (NIL,mailbox,uidvalidity,NIL,set);
}


/* Got a referral
 * Accepts: MAIL stream
 *        URL
 *        referral type code
 */

char *referral (MAILSTREAM *stream,char *url,long code)
{
  if (lstref) fs_give ((void **) &lstref);
  lstref = cpystr (url);    /* set referral */
                /* set error if not a logged in referral */
  if (code != REFAUTH) response = lose;
  if (!lsterr) lsterr = cpystr ("Try referral URL");
  return NIL;            /* don't chase referrals for now */
}

/* Co-routines from MAIL library */


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

void mm_searched (MAILSTREAM *s,unsigned long msgno)
{
                /* nothing to do 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)
{
                /* note change in number of messages */
  if ((s != tstream) && (nmsgs != number)) {
    nmsgs = number;        /* always update number of messages */
    if (quell_events) existsquelled = T;
    else {
      PSOUT ("* ");
      pnum (nmsgs);
      PSOUT (" EXISTS\015\012");
    }
    recent = 0xffffffff;    /* make sure update recent too */
  }
}


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

void mm_expunged (MAILSTREAM *s,unsigned long number)
{
  if (quell_events) fatal ("Impossible EXPUNGE event");
  if (s != tstream) {
    PSOUT ("* ");
    pnum (number);
    PSOUT (" EXPUNGE\015\012");
  }
  nmsgs--;
  existsquelled = T;        /* do EXISTS when command done */
}


/* Message status changed
 * Accepts: MAIL stream
 *        message number
 */

void mm_flags (MAILSTREAM *s,unsigned long number)
{
  if (s != tstream) mail_elt (s,number)->spare2 = T;
}

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

void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
{
  mm_list_work ("LIST",delimiter,name,attributes);
}


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

void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
{
  mm_list_work ("LSUB",delimiter,name,attributes);
}


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

void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
{
  if (!quell_events) {
    char tmp[MAILTMPLEN];
    tmp[0] = tmp[1] = '\0';
    if (status->flags & SA_MESSAGES)
      sprintf (tmp + strlen (tmp)," MESSAGES %lu",status->messages);
    if (status->flags & SA_RECENT)
      sprintf (tmp + strlen (tmp)," RECENT %lu",status->recent);
    if (status->flags & SA_UNSEEN)
      sprintf (tmp + strlen (tmp)," UNSEEN %lu",status->unseen);
    if (status->flags & SA_UIDNEXT)
      sprintf (tmp + strlen (tmp)," UIDNEXT %lu",status->uidnext);
    if (status->flags & SA_UIDVALIDITY)
      sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",status->uidvalidity);
    PSOUT ("* STATUS ");
    pastring (mailbox);
    PSOUT (" (");
    PSOUT (tmp+1);
    PBOUT (')');
    CRLF;
  }
}

/* Worker routine for LIST and LSUB
 * Accepts: name of response
 *        hierarchy delimiter
 *        mailbox name
 *        attributes
 */

void mm_list_work (char *what,int delimiter,char *name,long attributes)
{
  char *s;
  if (!quell_events) {
    char tmp[MAILTMPLEN];
    if (finding) {
      PSOUT ("* MAILBOX ");
      PSOUT (name);
    }
                /* new form */
    else if ((cmd[0] == 'R') || !(attributes & LATT_REFERRAL)) {
      PSOUT ("* ");
      PSOUT (what);
      PSOUT (" (");
      tmp[0] = tmp[1] = '\0';
      if (attributes & LATT_NOINFERIORS) strcat (tmp," \\NoInferiors");
      if (attributes & LATT_NOSELECT) strcat (tmp," \\NoSelect");
      if (attributes & LATT_MARKED) strcat (tmp," \\Marked");
      if (attributes & LATT_UNMARKED) strcat (tmp," \\UnMarked");
      if (attributes & LATT_HASCHILDREN) strcat (tmp," \\HasChildren");
      if (attributes & LATT_HASNOCHILDREN) strcat (tmp," \\HasNoChildren");
      PSOUT (tmp+1);
      switch (delimiter) {
      case '\\':        /* quoted delimiter */
      case '"':
    PSOUT (") \"\\");
    PBOUT (delimiter);
    PBOUT ('"');
    break;
      case '\0':        /* no delimiter */
    PSOUT (") NIL");
    break;
      default:            /* unquoted delimiter */
    PSOUT (") \"");
    PBOUT (delimiter);
    PBOUT ('"');
    break;
      }
      PBOUT (' ');
                /* output mailbox name */
      if (proxylist && (s = strchr (name,'}'))) pastring (s+1);
      else pastring (name);
    }
    CRLF;
  }
}

/* Notification event
 * Accepts: MAIL stream
 *        string to log
 *        error flag
 */

void mm_notify (MAILSTREAM *stream,char *string,long errflg)
{
  SIZEDTEXT msg;
  char *s,*code;
  if (!quell_events && (!tstream || (stream != tstream))) {
    switch (errflg) {
    case NIL:            /* information message, set as OK response */
      if ((string[0] == '[') &&
      ((string[1] == 'T') || (string[1] == 't')) &&
      ((string[2] == 'R') || (string[2] == 'r')) &&
      ((string[3] == 'Y') || (string[3] == 'y')) &&
      ((string[4] == 'C') || (string[4] == 'c')) &&
      ((string[5] == 'R') || (string[5] == 'r')) &&
      ((string[6] == 'E') || (string[6] == 'e')) &&
      ((string[7] == 'A') || (string[7] == 'a')) &&
      ((string[8] == 'T') || (string[8] == 't')) &&
      ((string[9] == 'E') || (string[9] == 'e')) && (string[10] == ']'))
    trycreate = T;
    case BYE:            /* some other server signing off */
    case PARSE:            /* parse glitch, output unsolicited OK */
      code = "* OK ";
      break;
    case WARN:            /* warning, output unsolicited NO (kludge!) */
      code = "* NO ";
      break;
    case ERROR:            /* error that broke command */
    default:            /* default should never happen */
      code = "* BAD ";
      break;
    }
    PSOUT (code);
    msg.size = (s = strpbrk ((char *) (msg.data = (unsigned char *) string),
                 "\015\012")) ?
      (s - string) : strlen (string);
    PSOUTR (&msg);
    CRLF;
    PFLUSH ();            /* let client see it immediately */
  }
}

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

void mm_log (char *string,long errflg)
{
  SIZEDTEXT msg;
  char *s;
  msg.size = 
    (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ?
      (s - string) : strlen (string);
  switch (errflg) {
  case NIL:            /* information message, set as OK response */
    if (response == win) {    /* only if no other response yet */
      if (lsterr) {        /* if there was a previous message */
    if (!quell_events) {
      PSOUT ("* OK ");    /* blat it out */
      PSOUT (lsterr);
      CRLF;
      PFLUSH ();        /* let client see it immediately */
    }
    fs_give ((void **) &lsterr);
      }
      lsterr = cpystr (string); /* copy string for later use */
      if (s) lsterr[s - string] = NIL;
    }
    break;
  case PARSE:            /* parse glitch, output unsolicited OK */
    if (!quell_events) {
      PSOUT ("* OK [PARSE] ");
      PSOUTR (&msg);
      CRLF;
      PFLUSH ();        /* let client see it immediately */
    }
    break;
  case WARN:            /* warning, output unsolicited NO */
                /* ignore "Mailbox is empty" (KLUDGE!) */
    if (strcmp (string,"Mailbox is empty")) {
      if (lstwrn) {        /* have previous warning? */
    if (!quell_events) {
      PSOUT ("* NO ");
      PSOUT (lstwrn);
      CRLF;
      PFLUSH ();        /* make sure client sees it immediately */
    }
    fs_give ((void **) &lstwrn);
      }
      lstwrn = cpystr (string); /* note last warning */
      if (s) lstwrn[s - string] = NIL;
    }
    break;
  case ERROR:            /* error that broke command */
  default:            /* default should never happen */
    response = trycreate ? losetry : lose;
    if (lsterr) fs_give ((void **) &lsterr);
    lsterr = cpystr (string);    /* note last error */
    if (s) lsterr[s - string] = NIL;
    break;
  }
}

/* Return last error
 */

char *lasterror (void)
{
  if (lsterr) return lsterr;
  if (lstwrn) return lstwrn;
  return "<unknown>";
}


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

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

/* Get user name and password for this host
 * Accepts: parse of network user name
 *        where to return user name
 *        where to return password
 *        trial count
 */

void mm_login (NETMBX *mb,char *username,char *password,long trial)
{
                /* set user name */
  strncpy (username,*mb->user ? mb->user : (char *) user,NETMAXUSER);
  strncpy (password,pass,256);    /* and password */
}


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

void mm_critical (MAILSTREAM *s)
{
  ++critical;
}


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

void mm_nocritical (MAILSTREAM *s)
{
                /* go non-critical, pending death? */
  if (!--critical && (state == LOGOUT)) {
                /* clean up iff needed */
    if (s && (stream != s) && !s->lock && (s->dtb->flags & DR_XPOINT))
      s = mail_close (s);
    longjmp (jmpenv,1);        /* die now */
  }
}

/* Disk error found
 * Accepts: stream
 *        system error code
 *        flag indicating that mailbox may be clobbered
 * Returns: abort flag
 */

long mm_diskerror (MAILSTREAM *s,long errcode,long serious)
{
  if (serious) {        /* try your damnest if clobberage likely */
    mm_notify (s,"Retrying to fix probable mailbox damage!",ERROR);
    PFLUSH ();            /* dump output buffer */
    syslog (LOG_ALERT,
        "Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
        user ? (char *) user : "???",tcp_clienthost (),
        (stream && stream->mailbox) ? stream->mailbox : "???",
        strerror (errcode));
    settimeout (0);        /* make damn sure timeout disabled */
    sleep (60);            /* give it some time to clear up */
    return NIL;
  }
  if (!quell_events) {        /* otherwise die before more damage is done */
    PSOUT ("* NO Disk error: ");
    PSOUT (strerror (errcode));
    CRLF;
  }
  return T;
}


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

void mm_fatal (char *string)
{
  SIZEDTEXT msg;
  char *s;
  msg.size = 
    (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ?
      (s - string) : strlen (string);
  if (!quell_events) {
    PSOUT ("* BYE [ALERT] IMAP4rev1 server crashing: ");
    PSOUTR (&msg);
    CRLF;
    PFLUSH ();
  }
  syslog (LOG_ALERT,"Fatal error user=%.80s host=%.80s mbx=%.80s: %.80s",
      user ? (char *) user : "???",tcp_clienthost (),
      (stream && stream->mailbox) ? stream->mailbox : "???",string);
}

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.0 [PHP 7 Update] [25.02.2019] maintained by KaizenLouie | C99Shell Github | Generation time: 0.0234 ]--