/*
* Interactive Mail Access Protocol 2 (IMAP2) routines
*
* Mark Crispin, SUMEX Computer Project, 15 June 1988
*
* Copyright (c) 1988 Stanford University
*
*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "imap2.h"
#include "tcpsio.h"
#include "misc.h"


void imap_free_messagearray();
void imap_free_envelope();
void imap_free_address();
void map_debug();
void imap_free_elt();
void imap_free_address();
void imap_parse_user_flag();
void imap_parse_sys_flag();
void imap_parse_flags();
void imap_parse_text();
void imap_parse_prop ();
void imap_unlock ();
void imap_lock();
void imap_exists();
void imap_parse_data();
void imap_parse_unsolicited ();
void imap_parse_flaglst ();
void imap_searched ();
void imap_expunged ();
void imap_recent();
void imap_logout();

char *ucase();
char *lcase();
int imap_parse_number();

int imap_OK();
int imap_login();
int imap_select();

extern void mm_exists();
extern void mm_expunged();
extern void mm_searched();
extern void getpassword();
extern void usrlog();
extern void dbglog();
extern tcp_open();
extern atoi();
extern void tcp_getbuffer();

/* Mail Access Protocol routines
*
*  map_xxx routines are the interface between this module and the outside
* world.  Only these routines should be referenced by external callers.
* The idea is that if IMAPn is ever replaced by some other mail access
* protocol this discipline may reduce the amount of conversion effort.
*
*  Note that there is an important difference between a "sequence" and a
* "message #" (msgno).  A sequence is a string representing a sequence in
* IMAP2 format ("n", "n:m", or combination separated by commas), whereas
* a msgno is a single integer.
*
*/


/* Mail Access Protocol open
* Accepts: mailbox name in "{host}mailbox" form
*          candidate stream for recycling
*          initial debugging flag
* Returns: stream to use on success, NIL on failure
*/

IMAPSTREAM *map_open (name,oldstream,debug)
   char *name;
   IMAPSTREAM *oldstream;
   short debug;
{
 char tmp[1024];
 IMAPPARSEDREPLY *greeting;
 char *host = hostfield (name);
 char *mailbox = mailboxfield (name);
 IMAPSTREAM *stream = NIL;     /* initialize returned stream */
 if (!host) {                  /* must have a host */
   usrlog ("Host not specified");
   return (NIL);
 }
 if (!mailbox) {               /* must have a mailbox */
   usrlog ("Mailbox not specified");
   return (NIL);
 }
 if (oldstream) {              /* if we have an old stream */
   if (strcmp (host,tcp_host (oldstream->tcpstream))) {
                               /* if hosts are different punt it */
     sprintf (tmp,"Closing connection to %s",tcp_host (oldstream->tcpstream));
     usrlog (tmp);
     imap_logout (oldstream);
   }                           /* else recycle if still alive */
   else if (stream = imap_noop (oldstream)) {
     sprintf (tmp,"Reusing connection to %s",host);
     usrlog (tmp);
                               /* free up as memory as we can */
     if (stream->mailbox) free (stream->mailbox);
     stream->mailbox = NIL;
     imap_free_messagearray (stream->messagearray);
   }
 }
 if (!stream) {                /* if no recycled stream */
                               /* open connection and log in */
   if (!(stream = imap_open (host))) return (NIL);
   if (debug) map_debug (stream);
   greeting = imap_reply (stream,NIL);
   if (!imap_OK (greeting)) return (NIL);
   if (!imap_login (stream)) return (NIL);
 }
                               /* select mailbox */
 if (!imap_select (stream,mailbox)) return (NIL);
 if (!stream->nmsgs) {         /* punt if mailbox empty */
   usrlog ("Mailbox is empty");
   imap_logout (stream);
   return (NIL);
 }
 return (stream);              /* return stream to caller */
}


/* Mail Access Protocol close
* Accepts: IMAP2 stream
*/

map_close (stream)
   IMAPSTREAM *stream;
{
  imap_logout (stream);                /* only need to logout */
}


/* Mail Access Protocol fetch fast information
* Accepts: IMAP2 stream
*          sequence
*
* Generally, map_fetchenvelope is preferred
*/

map_fetchfast (stream,sequence)
   IMAPSTREAM *stream;
   char *sequence;
{                               /* send "FETCH sequence FAST" */
 char tmp[1024];
 sprintf (tmp,"%s FAST",sequence);
 imap_send (stream,"FETCH",tmp);
}


/* Mail Access Protocol fetch flags
* Accepts: IMAP2 stream
*          sequence
*/

map_fetchflags (stream,sequence)
   IMAPSTREAM *stream;
   char *sequence;
{                               /* send "FETCH sequence FLAGS" */
 char tmp[1024];
 sprintf (tmp,"%s FLAGS",sequence);
 imap_send (stream,"FETCH",tmp);
}


/* Mail Access Protocol fetch envelope
* Accepts: IMAP2 stream
*          message # to fetch
* Returns: envelope of this message
*
* Fetches the "fast" information as well
*/

ENVELOPE *map_fetchenvelope (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 int i,j;
 char tmp[1024];
 MESSAGECACHE *cache = map_elt (stream,msgno);
 if (!cache->envPtr) {         /* if we don't have an envelope */
                               /* never lookahead if last message */
   if (msgno == stream->nmsgs) i = msgno;
   else {                      /* calculate lookahead range */
     j = min (msgno+MAPLOOKAHEAD-1,stream->nmsgs);
                               /* i is last envelope to fetch */
     for (i = msgno+1; i < j; ++i)
     if (map_elt (stream,i)->envPtr) break;
   }
                               /* send "FETCH msgno:i ALL" */
   sprintf (tmp,"%d:%d ALL",msgno,i);
   imap_send (stream,"FETCH",tmp);
 }
 return (cache->envPtr);       /* return the envelope */
}


/* Mail Access Protocol fetch message
* Accepts: IMAP2 stream
*          message # to fetch
* Returns: message text in RFC822 format
*/

char *map_fetchmessage (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 char tmp[1024];
/*  MESSAGECACHE *cache = map_elt (stream,msgno); */
                               /* send "FETCH msgno RFC822" */
 sprintf (tmp,"%d RFC822",msgno);
 imap_send (stream,"FETCH",tmp);
 return (stream->rfc822_text); /* return text of this message */
}


/* Mail Access Protocol fetch message header
* Accepts: IMAP2 stream
*          message # to fetch
* Returns: message header in RFC822 format
*/

char *map_fetchheader (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 char tmp[1024];
/*  MESSAGECACHE *cache = map_elt (stream,msgno); */
                               /* send "FETCH msgno RFC822.HEADER" */
 sprintf (tmp,"%d RFC822.HEADER",msgno);
 imap_send (stream,"FETCH",tmp);
 return (stream->rfc822_text); /* return text of this message */
}


/* Mail Access Protocol fetch message text (body only)
* Accepts: IMAP2 stream
*          message # to fetch
* Returns: message text in RFC822 format
*/

char *map_fetchtext (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 char tmp[1024];
/*  MESSAGECACHE *cache = map_elt (stream,msgno); */
                               /* send "FETCH msgno RFC822.TEXT" */
 sprintf (tmp,"%d RFC822.TEXT",msgno);
 imap_send (stream,"FETCH",tmp);
 return (stream->rfc822_text); /* return text of this message */
}


/* Mail Access Protocol fetch From string for menu
* Accepts: IMAP2 stream
*          message # to fetch
*          desired string length
* Returns: string of requested length
*/

char *map_fetchfromstring (stream,msgno,length)
   IMAPSTREAM *stream;
   int msgno;
   int length;
{
 char tmp[1024];
 ENVELOPE *envelope;
 ADDRESS *from;
 MESSAGECACHE *cache = map_elt (stream,msgno);
 if (!cache->fromText) {       /* if we don't have one yet */
                               /* create it */
   cache->fromText = (char *) malloc (length+1);
                               /* fill it with spaces */
   memset (cache->fromText,' ',length);
                               /* tie off with null */
   cache->fromText[length] = '\0';
                               /* get its envelope */
   if (envelope = map_fetchenvelope (stream,msgno)) {
     from = envelope->from;    /* get first From address */
                               /* if a personal name exists use it */
     if (from) {
       if (from->personalName) {
         memcpy (cache->fromText,from->personalName,
                 min (length,strlen (from->personalName)));
       }
       else {                  /* otherwise use mailbox@host */
                               /* not very fast but not done often */
         strcpy (tmp,from->mailBox);
         if (from->host) {
           strcat (tmp,"@");
           strcat (tmp,from->host);
         }
         memcpy (cache->fromText,tmp,min (length,strlen (tmp)));
       }
     }
   }
 }
 return (cache->fromText);
}


/* Mail Access Protocol fetch Subject string for menu
* Accepts: IMAP2 stream
*          message # to fetch
*          desired string length
* Returns: string of no more than requested length
*/

char *map_fetchsubjectstring (stream,msgno,length)
   IMAPSTREAM *stream;
   int msgno;
   int length;
{
 ENVELOPE *envelope;
 MESSAGECACHE *cache = map_elt (stream,msgno);
 if (!cache->subjectText) {    /* if we don't have one yet */
                               /* create it */
   cache->subjectText = (char *) malloc (length+1);
                               /* get its envelope */
   if (envelope = map_fetchenvelope (stream,msgno)) {
     if (envelope->subject) {  /* got a subject in the envelope? */
                               /* copy up to length characters */
       strncpy (cache->subjectText,envelope->subject,length);
                               /* tie off string with null */
       cache->subjectText[length] = '\0';
     } else {                  /* if no subject then just a space */
       cache->subjectText[0] = ' ';
       cache->subjectText[1] = '\0';
     }
   }
 }
 return (cache->subjectText);
}


/* Mail Access Protocol fetch cache element
* Accepts: IMAP2 stream
*          message # to fetch
* Returns: cache element of this message
*/

MESSAGECACHE *map_elt (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 int i = msgno-1;
 if (!stream->messagearray[i]) {
                               /* if no cache entry, create it */
   stream->messagearray[i] = (MESSAGECACHE *) malloc (sizeof (MESSAGECACHE));
                               /* initialize newly-created cache */
   stream->messagearray[i]->messageNumber = msgno;
   stream->messagearray[i]->internalDate = NIL;
   stream->messagearray[i]->userFlags = NIL;
   stream->messagearray[i]->systemFlags = NIL;
   stream->messagearray[i]->envPtr = NIL;
   stream->messagearray[i]->rfc822_size = 0;
   stream->messagearray[i]->fromText = NIL;
   stream->messagearray[i]->subjectText = NIL;
 }
                               /* return the cache element */
 return (stream->messagearray[i]);
}


/* Mail Access Protocol set flag
* Accepts: IMAP2 stream
*          sequence
*          flag(s)
*/

map_setflag (stream,sequence,flag)
   IMAPSTREAM *stream;
   char *sequence;
   char *flag;
{
 char tmp[1024];
 IMAPPARSEDREPLY *reply;
                               /* "STORE sequence +Flags flag" */
 sprintf (tmp,"%s +Flags %s",sequence,flag);
 reply = imap_send (stream,"STORE",tmp);
 if (!imap_OK (reply)) {
   sprintf (tmp,"Set flag rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol clear flag
* Accepts: IMAP2 stream
*          sequence
*          flag(s)
*/

map_clearflag (stream,sequence,flag)
   IMAPSTREAM *stream;
   char *sequence;
   char *flag;
{
 char tmp[1024];
 IMAPPARSEDREPLY *reply;
                               /* "STORE sequence -Flags flag" */
 sprintf (tmp,"%s -Flags %s",sequence,flag);
 reply = imap_send (stream,"STORE",tmp);
 if (!imap_OK (reply)) {
   sprintf (tmp,"Clear flag rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol search for messages
* Accepts: IMAP2 stream
*          search criteria
*/

map_search (stream,criteria)
   IMAPSTREAM *stream;
   char *criteria;
{
 char tmp[1024];
 IMAPPARSEDREPLY *reply;
                               /* send "SEARCH criteria" */
 reply = imap_send (stream,"SEARCH",criteria);
 if (!imap_OK (reply)) {
   sprintf (tmp,"Search rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol check mailbox
* Accepts: IMAP2 stream
*/

map_checkmailbox (stream)
   IMAPSTREAM *stream;
{
 char tmp[1024];
                               /* send "CHECK" */
 IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
 if (imap_OK (reply)) usrlog (reply->text);
 else {
   sprintf (tmp,"Check rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol expunge mailbox
* Accepts: IMAP2 stream
*/

map_expungemailbox (stream)
   IMAPSTREAM *stream;
{
 char tmp[1024];
                               /* send "EXPUNGE" */
 IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL);
 if (imap_OK (reply)) usrlog (reply->text);
 else {
   sprintf (tmp,"Expunge rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol copy message(s)
* Accepts: IMAP2 stream
*          sequence
*          destination mailbox
*/

map_copymessage (stream,sequence,mailbox)
   IMAPSTREAM *stream;
   char *sequence;
   char *mailbox;
{
 char tmp[1024];
 IMAPPARSEDREPLY *reply;
                               /* send "COPY sequence mailbox" */
 sprintf (tmp,"%s %s",sequence,mailbox);
 reply = imap_send (stream,"COPY",tmp);
 if (imap_OK (reply)) map_setflag (stream,sequence,"\\Seen");
 else {
   sprintf (tmp,"Copy rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol move message(s)
* Accepts: IMAP2 stream
*          sequence
*          destination mailbox
*/

map_movemessage (stream,sequence,mailbox)
   IMAPSTREAM *stream;
   char *sequence;
   char *mailbox;
{
 char tmp[1024];
 IMAPPARSEDREPLY *reply;
                               /* send "COPY sequence mailbox" */
 sprintf (tmp,"%s %s",sequence,mailbox);
 reply = imap_send (stream,"COPY",tmp);
 if (imap_OK (reply)) map_setflag (stream,sequence,"\\Deleted \\Seen");
 else {
   sprintf (tmp,"Copy rejected: %s",reply->text);
   usrlog (tmp);
 }
}


/* Mail Access Protocol check if stream locked
* Accepts: IMAP2 stream
* Returns: lock status
*/

map_checklock (stream)
   IMAPSTREAM *stream;
{
 return (stream->lock);
}


/* Mail Access Protocol turn on debugging telemetry
* Accepts: IMAP2 stream
*/

void map_debug (stream)
   IMAPSTREAM *stream;
{
 stream->debug = T;            /* turn on protocol telemetry */
}


/* Mail Access Protocol turn off debugging telemetry
* Accepts: IMAP2 stream
*/

map_nodebug (stream)
   IMAPSTREAM *stream;
{
 stream->debug = NIL;          /* turn off protocol telemetry */
}



/* Interactive Mail Access Protocol routines
*
*  imap_xxx routines are internal routines called only by the map_xxx
* routines.  These routines are what call the TCP routines.
*
*/


/* Interactive Mail Access Protocol open
* Accepts: host name
* Returns: stream if successful, else NIL
*/

IMAPSTREAM *imap_open (host)
   char *host;
{
 IMAPSTREAM *stream = NIL;
 int tcpstream;
 int i;
                               /* connect to host on IMAP2 port */
 if (tcpstream = tcp_open (host,IMAPTCPPORT)) {
                               /* got one, create an IMAP stream */
   stream = (IMAPSTREAM *) malloc (sizeof (IMAPSTREAM));
                               /* bind our stream */
   stream->tcpstream = tcpstream;
   stream->mailbox = NIL;      /* initialize stream fields */
   stream->lock = NIL;
   stream->debug = NIL;
   stream->gensym = 0;
   stream->reply = (IMAPPARSEDREPLY *) malloc (sizeof (IMAPPARSEDREPLY));
   stream->reply->line = NIL;
   for (i = 0; i < MAXMESSAGES; ++i) stream->messagearray[i] = NIL;
   stream->nmsgs = 0;
   stream->recent = 0;
   stream->flagstring = NIL;
   for (i = 0; i < NUSERFLAGS; ++i) stream->userFlags[i] = NIL;
 }
 return (stream);
}


/* Interactive Mail Access Protocol login
* Accepts: IMAP2 stream
* Returns: T if successful, else NIL and stream is logged out
*/

int imap_login (stream)
   IMAPSTREAM *stream;
{
 char username[64];
 char password[64];
 char tmp[1024];
 int i;
 IMAPPARSEDREPLY *reply;
 for (i = 1; i <= 3; ++i) {    /* make 3 tries to login */
   getpassword (tcp_host (stream->tcpstream),username,password);
                               /* send "LOGIN username password" */
   sprintf (tmp,"%s %s",username,password);
   reply = imap_send (stream,"LOGIN",tmp);
   if (imap_OK (reply)) return (T);
                               /* output failure and try again */
   sprintf (tmp,"Login failed: %s",reply->text);
   usrlog (tmp);
                               /* give up if connection died */
   if (!strcmp (reply->key,"BYE")) break;
 }
                               /* give up if too many failures */
 usrlog ("Too many login failures");
 imap_logout (stream);
 return (NIL);
}


/* Interactive Mail Access Protocol logout
* Accepts: IMAP2 stream
*
* Stream is garbage collected by this routine.
*/

void imap_logout (stream)
   IMAPSTREAM *stream;
{
 if (stream) {                 /* send "LOGOUT" */
   imap_send (stream,"LOGOUT",NIL);
                               /* close TCP connection */
   tcp_close (stream->tcpstream);
                               /* free up as memory as we can */
   if (stream->mailbox) free (stream->mailbox);
   imap_free_messagearray (stream->messagearray);
   if (stream->flagstring) free (stream->flagstring);
   if (stream->reply->line) free (stream->reply->line);
   free ((char *) stream->reply);
   free ((char *) stream);     /* finally nuke the stream */
 }
}


/* Interactive Mail Access Protocol no-operation
* Accepts: IMAP2 stream
* Returns: stream if succcessful, else NIL and stream is logged out
*/

IMAPSTREAM *imap_noop (stream)
   IMAPSTREAM *stream;
{                               /* send "NOOP" */
 IMAPPARSEDREPLY *reply = imap_send (stream,"NOOP",NIL);
 if (imap_OK (reply)) return (stream);
 else {
   imap_logout (stream);       /* sayonara */
   return (NIL);
 }
}


/* Interactive Mail Access Protocol select mailbox
* Accepts: IMAP2 stream
*          mailbox
* Returns: T if successful, else NIL and stream is logged out
*/

imap_select (stream,mailbox)
   IMAPSTREAM *stream;
   char *mailbox;
{
 char tmp[1024];
                               /* send "SELECT mailbox" */
 IMAPPARSEDREPLY *reply = imap_send (stream,"SELECT",mailbox);
 if (imap_OK (reply)) {        /* if OK, stash mailbox on stream */
   stream->mailbox = mailbox;
   return (T);                 /* return success */
 }
 else {
   sprintf (tmp,"Can't select mailbox: %s",reply->text);
   usrlog (tmp);
   imap_logout (stream);       /* sayonara */
   return (NIL);
 }
}


/* Interactive Mail Access Protocol send command
* Accepts: IMAP2 stream
*          command
*          command argument
* Returns: parsed reply
*/

IMAPPARSEDREPLY *imap_send (stream,command,args)
   IMAPSTREAM *stream;
   char *command;
   char *args;
{
 char tmp[1024];
 char tag[7];
 IMAPPARSEDREPLY *reply;
 imap_lock (stream);           /* lock up the stream */
                               /* gensym a new tag */
 sprintf (tag,"A%05d",stream->gensym++);
                               /* build the complete command */
 if (args) sprintf (tmp,"%s %s %s",tag,command,args);
 else sprintf (tmp,"%s %s",tag,command);
 if (stream->debug) dbglog (tmp);
 strcat (tmp,"\015\012");
                               /* send the command */
 if (!tcp_soutr (stream->tcpstream,tmp)) {
   imap_unlock (stream);       /* failed, unlock stream and punt */
   stream->reply->tag = "*";   /* return fake reply message */
   stream->reply->key = "BYE";
   stream->reply->text = "IMAP2 connection went away!";
   return (stream->reply);
 }
 while (T) {                   /* get reply from server */
   reply = imap_reply (stream,tag);
                               /* exit if we got our reply */
   if (!strcmp (tag,reply->tag)) break;
                               /* handle unsolicited data */
   if (!strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
                               /* report bogons */
   else {
     sprintf (tmp,"Unexpected tagged response: %s %s %s",
               reply->tag,reply->key,reply->text);
     usrlog (tmp);
   }
 }
                               /* note if server rejected command */
 if (!strcmp (reply->key,"BAD")) {
   sprintf (tmp,"IMAP2 protocol error: %s",reply->text);
   usrlog (tmp);
 }
 imap_unlock (stream);         /* unlock stream */
 return (reply);               /* return reply to caller */
}


/* Interactive Mail Access Protocol parse reply
* Accepts: IMAP2 stream
*          tag to use for generated error reply
* Returns: parsed reply
*/

IMAPPARSEDREPLY *imap_reply (stream,commandtag)
   IMAPSTREAM *stream;
   char *commandtag;
{
 char tmp[1024];
 while (T) {                   /* get a parsable reply */
                               /* clean up old reply */
   if (stream->reply->line) free (stream->reply->line);
                               /* get a text line from the net */
   if (!(stream->reply->line = tcp_getline (stream->tcpstream))) {
                               /* we died, return fake reply */
                               /* use imap_send tag if one */
     if (commandtag) stream->reply->tag = commandtag;
     else stream->reply->tag = "*";
     stream->reply->key = "BYE";
     stream->reply->text = "IMAP2 connection went away!";
     return (stream->reply);
   }
   if (stream->debug) dbglog (stream->reply->line);
   stream->reply->key = NIL;   /* init fields in case error */
   stream->reply->text = NIL;
                               /* parse separate tag, key, text */
   if (stream->reply->tag = (char *) strtok (stream->reply->line," "))
     if (stream->reply->key = (char *) strtok (NIL," ")) {
       stream->reply->text = (char *) strtok (NIL,"\n");
       break;
     }
                               /* tag or key is missing */
   if (stream->reply->tag) {
     sprintf (tmp,"Missing IMAP2 reply key: %s",stream->reply->tag);
     usrlog (tmp);
   }
   else usrlog ("IMAP2 server sent a blank line");
 }
 ucase (stream->reply->key);   /* make sure key is upper case */
 return (stream->reply);
}


/* Interactive Mail Access Protocol check for OK response
* Accepts: parsed IMAP2 reply
* Returns: T if OK reply else NIL
*/

imap_OK (reply)
   IMAPPARSEDREPLY *reply;
{
 if (!reply) return (NIL);     /* return NIL if no reply */
 if (!strcmp (reply->key,"OK")) return (T);
 else return (NIL);
}



/* Unsolicited reply handling
*
*  This is perhaps the most important part of all of the IMAP2 code; the
* code that actually handles received data from the mail server.
*
*/


/* Interactive Mail Access Protocol parse and act upon unsolicited reply
* Accepts: IMAP2 stream
*          parsed reply
*/

void imap_parse_unsolicited (stream,reply)
   IMAPSTREAM *stream;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 long msgno;
 char *endptr;
 char *keyptr,*txtptr;
                               /* see if key is a number */
 msgno = strtol (reply->key,&endptr,10);
 if (*endptr) {                /* if non-numeric */
   IFEQUAL (reply->key,"FLAGS",imap_parse_flaglst (stream,reply))
   IFEQUAL (reply->key,"SEARCH",imap_searched (stream,reply->text))
   IFEQUAL (reply->key,"BYE",usrlog (reply->text))
   IFEQUAL (reply->key,"OK",usrlog (reply->text))
   IFEQUAL (reply->key,"NO",usrlog (reply->text))
   IFEQUAL (reply->key,"BAD",usrlog (reply->text))
   sprintf (tmp,"Unexpected unsolicited message: %s",reply->key);
   usrlog (tmp);
 }
 else {                        /* if numeric, a keyword follows */
                               /* deposit null at end of keyword */
   keyptr = ucase ((char *) strtok (reply->text," "));
                               /* and locate the text after it */
   txtptr = (char *) strtok (NIL,"\n");
                               /* now take the action */
   IFEQUAL (keyptr,"EXISTS",imap_exists (stream,msgno))
   IFEQUAL (keyptr,"RECENT",imap_recent (stream,msgno))
   IFEQUAL (keyptr,"EXPUNGE",imap_expunged (stream,msgno))
   IFEQUAL (keyptr,"FETCH",imap_parse_data (stream,msgno,txtptr,reply))
   IFEQUAL (keyptr,"STORE",imap_parse_data (stream,msgno,txtptr,reply))
   IFEQUAL (keyptr,"COPY",)
   sprintf (tmp,"Unknown message data: %d %s",msgno,keyptr);
   usrlog (tmp);
 }
}


/* Interactive Mail Access Protocol parse flag list
* Accepts: IMAP2 stream
*          parsed reply
*
*  The reply->line is yanked out of the parsed reply and stored on
* stream->flagstring.  This is the original malloc'd reply string, and
* has all the flagstrings embedded in it.
*/

void imap_parse_flaglst (stream,reply)
   IMAPSTREAM *stream;
   IMAPPARSEDREPLY *reply;
{
 char *text = reply->text;
 char *flag;
 int i;
                               /* flush old flagstring and flags if any */
 if (stream->flagstring) free (stream->flagstring);
 for (i = 0; i < NUSERFLAGS; ++i) stream->userFlags[i] = NIL;
                               /* remember this new one */
 stream->flagstring = reply->line;
 reply->line = NIL;
 ++text;                       /* skip past open parenthesis */
                               /* get first flag if any */
 if (flag = (char *) strtok (text," )")) {
                               /* add first flag */
   if (flag[0] != '\\') stream->userFlags[i = 0] = flag;
                               /* add all subsequent flags */
   while (flag = (char *) strtok (NIL," )"))
     if (flag[0] != '\\') stream->userFlags[++i] = flag;
 }
}


/* Interactive Mail Access Protocol messages have been searched out
* Accepts: IMAP2 stream
*          list of message numbers
*
* Calls external "mm_searched" function to notify main program
*/

void imap_searched (stream,text)
   IMAPSTREAM *stream;
   char *text;
{
 char *num;
                               /* get first number */
 if (text) if (num = (char *) strtok (text," ")) {
                               /* add first number */
   mm_searched (stream,atoi (num));
                               /* add all subsequent numbers */
   while (num = (char *) strtok (NIL," ")) mm_searched (stream,atoi (num));
 }
}


/* Interactive Mail Access Protocol n messages exist
* Accepts: IMAP2 stream
*          number of messages
*
* Calls external "mm_exists" function that notifies main program prior
* to updating the stream
*/

void imap_exists (stream,nmsgs)
   IMAPSTREAM *stream;
   int nmsgs;
{
 char tmp[1024];
 if (nmsgs > MAXMESSAGES) {
   sprintf (tmp,"Mailbox has too many (%d) messages.  Using max of %d",
            nmsgs,MAXMESSAGES);
   usrlog (tmp);
   nmsgs = MAXMESSAGES;
 }
 mm_exists (stream,nmsgs);     /* notify main program of change */
 stream->nmsgs = nmsgs;        /* update stream status */
}


/* Interactive Mail Access Protocol n messages are recent
* Accepts: IMAP2 stream
*          number of recent messages
*/

void imap_recent (stream,recent)
   IMAPSTREAM *stream;
   int recent;
{                               /* update stream status */
 stream->recent = min (recent,MAXMESSAGES);
}


/* Interactive Mail Access Protocol message n is expunged
* Accepts: IMAP2 stream
*          message #
*
* Calls external "mm_expunged" function that notifies main program prior
* to updating the stream
*/

void imap_expunged (stream,msgno)
   IMAPSTREAM *stream;
   int msgno;
{
 int i;
                               /* free this message element */
 if (stream->messagearray[msgno-1])
   imap_free_elt (stream->messagearray[msgno-1]);
                               /* slide down remainder of cache */
 for (i = msgno; i < stream->nmsgs; ++i) {
   stream->messagearray[i-1] = stream->messagearray[i];
   stream->messagearray[i-1]->messageNumber = i;
 }
 stream->messagearray[stream->nmsgs-1] = NIL;
 --(stream->nmsgs);            /* update stream status */
 mm_expunged (stream,msgno);   /* notify main program of change */
}


/* Interactive Mail Access Protocol parse data
* Accepts: IMAP2 stream
*          message #
*          text to parse
*          parsed reply
*
*  This code should probably be made a bit more paranoid about malformed
* S-expressions.
*/

void imap_parse_data (stream,msgno,text,reply)
   IMAPSTREAM *stream;
   int msgno;
   char *text;
   IMAPPARSEDREPLY *reply;
{
 MESSAGECACHE *cache = map_elt (stream,msgno);
 char *prop;
 ++text;                       /* skip past open parenthesis */
                               /* parse Lisp-form property list */
                               /* stop on space or close paren */
 while (prop = (char *) strtok (text," )")) {
                               /* point at value */
   text = (char *) strtok (NIL,"\n");
                               /* parse the property and its value */
   imap_parse_prop (stream,cache,ucase (prop),&text,reply);
 }
}


/* Interactive Mail Access Protocol parse property
* Accepts: IMAP2 stream
*          cache item
*          property name
*          property value text pointer
*          parsed reply
*/

void imap_parse_prop (stream,cache,prop,txtptr,reply)
   IMAPSTREAM *stream;
   MESSAGECACHE *cache;
   char *prop;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 IFEQUAL (prop,"ENVELOPE",{
   if (cache->envPtr) imap_free_envelope (cache->envPtr);
   cache->envPtr = imap_parse_envelope (stream,txtptr,reply);
 })
 IFEQUAL (prop,"FLAGS",imap_parse_flags (stream,cache,txtptr))
 IFEQUAL (prop,"INTERNALDATE",{
   if (cache->internalDate) free (cache->internalDate);
   cache->internalDate = imap_parse_string (stream,txtptr,reply);
 })
 IFEQUAL (prop,"RFC822",imap_parse_text (stream,txtptr,reply))
 IFEQUAL (prop,"RFC822.HEADER",imap_parse_text (stream,txtptr,reply))
 IFEQUAL (prop,"RFC822.SIZE",cache->rfc822_size = imap_parse_number (txtptr))
 IFEQUAL (prop,"RFC822.TEXT",imap_parse_text (stream,txtptr,reply))
 sprintf (tmp,"Unknown message property: %s",prop);
 usrlog (tmp);
}


/* Interactive Mail Access Protocol parse envelope
* Accepts: IMAP2 stream
*          current text pointer
*          parsed reply
* Returns:  envelope, NIL on failure
*
* Updates text pointer
*/

ENVELOPE *imap_parse_envelope (stream,txtptr,reply)
   IMAPSTREAM *stream;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 ENVELOPE *env = NIL;
 char c = *txtptr[0];          /* sniff at first envelope character */
 ++*txtptr;                    /* skip past open paren */
 switch (c) {
   case '(':                   /* if envelope S-expression */
     env = (ENVELOPE *) malloc (sizeof (ENVELOPE));
     env->date = imap_parse_string (stream,txtptr,reply);
     env->subject = imap_parse_string (stream,txtptr,reply);
     env->from = imap_parse_adrlist (stream,txtptr,reply);
     env->sender = imap_parse_adrlist (stream,txtptr,reply);
     env->reply_to = imap_parse_adrlist (stream,txtptr,reply);
     env->to = imap_parse_adrlist (stream,txtptr,reply);
     env->cc = imap_parse_adrlist (stream,txtptr,reply);
     env->bcc = imap_parse_adrlist (stream,txtptr,reply);
     env->in_reply_to = imap_parse_string (stream,txtptr,reply);
     env->message_id = imap_parse_string (stream,txtptr,reply);
     if (*txtptr[0] != ')') {
       sprintf (tmp,"Junk at end of envelope: %s",*txtptr);
       usrlog (tmp);
     }
     else ++*txtptr;           /* skip past delimiter */
     break;
   case 'N':                   /* if NIL */
   case 'n':
     ++*txtptr;                /* bump past "I" */
     ++*txtptr;                /* bump past "L" */
     break;
   default:
     sprintf (tmp,"Not an envelope: %s",*txtptr);
     usrlog (tmp);
     break;
 }
 return (env);
}


/* Interactive Mail Access Protocol parse address list
* Accepts: IMAP2 stream
*          current text pointer
*          parsed reply
* Returns: address list, NIL on failure
*
* Updates text pointer
*/

ADDRESS *imap_parse_adrlist (stream,txtptr,reply)
   IMAPSTREAM *stream;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 ADDRESS *adr = NIL;
 char c = *txtptr[0];          /* sniff at first character */
 while (c == ' ') {            /* ignore leading spaces */
   ++*txtptr;
   c = *txtptr[0];
 }
 ++*txtptr;                    /* skip past open paren */
 switch (c) {
   case '(':                   /* if envelope S-expression */
     adr = imap_parse_address (stream,txtptr,reply);
     if (*txtptr[0] != ')') {
       sprintf (tmp,"Junk at end of address list: %s",*txtptr);
       usrlog (tmp);
     }
     else ++*txtptr;           /* skip past delimiter */
     break;
   case 'N':                   /* if NIL */
   case 'n':
     ++*txtptr;                /* bump past "I" */
     ++*txtptr;                /* bump past "L" */
     break;
   default:
     sprintf (tmp,"Not an address: %s",*txtptr);
     usrlog (tmp);
     break;
 }
 return (adr);
}


/* Interactive Mail Access Protocol parse address
* Accepts: IMAP2 stream
*          current text pointer
*          parsed reply
* Returns: address, NIL on failure
*
* Updates text pointer
*/

ADDRESS *imap_parse_address (stream,txtptr,reply)
   IMAPSTREAM *stream;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 ADDRESS *adr = NIL;
 ADDRESS *ret = NIL;
 ADDRESS *prev = NIL;
 char c = *txtptr[0];          /* sniff at first address character */
 switch (c) {
   case '(':                   /* if envelope S-expression */
     while (c == '(') {        /* recursion dies bad on small stack machines */
       ++*txtptr;              /* skip past open paren */
       if (adr) prev = adr;    /* note previous if any */
       adr = (ADDRESS *) malloc (sizeof (ADDRESS));
       if (!ret) ret = adr;    /* if first time note first adr */
                               /* if previous link new block to it */
       if (prev) prev->next = adr;
       adr->personalName = imap_parse_string (stream,txtptr,reply);
       adr->routeList = imap_parse_string (stream,txtptr,reply);
       adr->mailBox = imap_parse_string (stream,txtptr,reply);
       adr->host = imap_parse_string (stream,txtptr,reply);
       adr->next = NIL;        /* tie off address */
       if (*txtptr[0] != ')') {
         sprintf (tmp,"Junk at end of address: %s",*txtptr);
         usrlog (tmp);
       }
       else ++*txtptr;         /* skip past close paren */
       c = *txtptr[0];         /* set up for while test */
     }
     break;
   case 'N':                   /* if NIL */
   case 'n':
     *txtptr += 3;             /* bump past NIL */
     break;
   default:
     sprintf (tmp,"Not an address: %s",*txtptr);
     usrlog (tmp);
     break;
 }
 return (ret);
}


/* Interactive Mail Access Protocol parse flags
* Accepts: current message cache
*          current text pointer
*
* Updates text pointer
*/

void imap_parse_flags (stream,cache,txtptr)
   IMAPSTREAM *stream;
   MESSAGECACHE *cache;
   char **txtptr;
{
 char *flag;
 char c;
 cache->userFlags = NIL;       /* zap old flag values */
 cache->systemFlags = NIL;
 while (T) {                   /* parse list of flags */
   flag = ++*txtptr;           /* point at a flag */
                               /* scan for end of flag */
   while (*txtptr[0] != ' ' && *txtptr[0] != ')') {++*txtptr;}
   c = *txtptr[0];             /* save delimiter */
   *txtptr[0] = '\0';          /* tie off flag */
   if (flag[0] != '\0') {      /* if flag is non-null */
                               /* if starts with \ must be sys flag */
     if (flag[0] == '\\') imap_parse_sys_flag (cache,ucase (flag));
                               /* otherwise user flag */
     else imap_parse_user_flag (stream,cache,flag);
   }
   if (c == ')') break;        /* quit if end of list */
 }
 ++*txtptr;                    /* bump past delimiter */
}


/* Interactive Mail Access Protocol parse system flag
* Accepts: message cache element
*          flag name
*/

void imap_parse_sys_flag (cache,flag)
   MESSAGECACHE *cache;
   char *flag;
{
 IFEQUAL (flag,"\\SEEN",cache->systemFlags |= fSEEN);
 IFEQUAL (flag,"\\DELETED",cache->systemFlags |= fDELETED);
 IFEQUAL (flag,"\\FLAGGED",cache->systemFlags |= fFLAGGED);
 IFEQUAL (flag,"\\ANSWERED",cache->systemFlags |= fANSWERED);
 IFEQUAL (flag,"\\RECENT",cache->systemFlags |= fRECENT);
 cache->systemFlags |= fUNDEFINED;
}


/* Interactive Mail Access Protocol parse user flag
* Accepts: message cache element
*          flag name
*/

void imap_parse_user_flag (stream,cache,flag)
   IMAPSTREAM *stream;
   MESSAGECACHE *cache;
   char *flag;
{
 int i;
                               /* sniff through all user flags */
 for (i = 0; i < NUSERFLAGS; ++i)
                               /* match this one? */
   if (!strcmp (flag,stream->userFlags[i])) {
                               /* yes, set the bit for that flag */
     cache->userFlags |= 1 << i;
     break;                    /* and quit */
   }
}


/* Interactive Mail Access Protocol parse string
* Accepts: IMAP2 stream
*          current text pointer
*          parsed reply
* Returns: string
*
* Updates text pointer
*/

char *imap_parse_string (stream,txtptr,reply)
   IMAPSTREAM *stream;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 char *st;
 char *string = NIL;           /* string to return */
 int i = 1;                    /* string byte count */
 char c = *txtptr[0];          /* sniff at first character */
 while (c == ' ') {            /* ignore leading spaces */
   ++*txtptr;
   c = *txtptr[0];
 }
 st = ++*txtptr;               /* remember start of string */
 switch (c) {
   case '"':                   /* if quoted string */
                               /* search for end of string */
     while (*txtptr[0] != '"') {
       ++i;                    /* bump count */
       ++*txtptr;              /* bump pointer */
     }
     *txtptr[0] = '\0';        /* tie off string */
                               /* make space for destination */
     string = (char *) malloc (i);
     strncpy (string,st,i);    /* copy the string */
     ++*txtptr;                /* bump past delimiter */
     break;
   case '{':                   /* if literal string */
                               /* get size of string */
     i = imap_parse_number (txtptr);
                               /* make space for destination */
     string = (char *) malloc (i+1);
                               /* get the literal */
     tcp_getbuffer (stream->tcpstream,i,string);
     free (reply->line);       /* flush old reply text line */
                               /* get new reply text line */
     reply->line = tcp_getline (stream->tcpstream);
     *txtptr = reply->line;    /* set text pointer to point at it */
     break;
   case 'N':                   /* if NIL */
   case 'n':
     ++*txtptr;                /* bump past "I" */
     ++*txtptr;                /* bump past "L" */
     break;
   default:
     sprintf (tmp,"Not a string: %s",*txtptr);
     usrlog (tmp);
     break;
 }
 return (string);
}


/* Interactive Mail Access Protocol parse text into stream
* Accepts: IMAP2 stream
*          current text pointer
*          parsed reply
*
* Updates text pointer, stores text on the stream
*/

void imap_parse_text (stream,txtptr,reply)
   IMAPSTREAM *stream;
   char **txtptr;
   IMAPPARSEDREPLY *reply;
{
 char tmp[1024];
 int i = 1;                    /* string byte count */
 char c = *txtptr[0];          /* sniff at first character */
 char *st = ++*txtptr;         /* remember start of string */
 switch (c) {
   case '"':                   /* if quoted string */
                               /* search for end of string */
     while (*txtptr[0] != '"') {
       ++i;                    /* bump count */
       ++*txtptr;              /* bump pointer */
     }
     *txtptr[0] = '\0';        /* tie off string */
                               /* copy the string */
     strncpy (stream->rfc822_text,st,i);
     ++*txtptr;                /* bump past delimiter */
     break;
   case '{':                   /* if literal string */
                               /* get size of string */
     i = imap_parse_number (txtptr);
                               /* get the literal */
     tcp_getbuffer (stream->tcpstream,i,stream->rfc822_text);
     free (reply->line);       /* flush old reply text line */
                               /* get new reply text line */
     reply->line = tcp_getline (stream->tcpstream);
     *txtptr = reply->line;    /* set text pointer to point at it */
     break;
   case 'N':                   /* if NIL */
   case 'n':
     ++*txtptr;                /* bump past "I" */
     ++*txtptr;                /* bump past "L" */
                               /* make text empty */
     stream->rfc822_text[0] = '\0';
     break;
   default:
     sprintf (tmp,"Not a string: %s",*txtptr);
     usrlog (tmp);
     break;
 }
}


/* Interactive Mail Access Protocol parse number
* Accepts: current text pointer
* Returns: number parsed
*
* Updates text pointer
*/

int imap_parse_number (txtptr)
   char **txtptr;
{                               /* parse number */
 return (strtol (*txtptr,txtptr,10));
}



/* IMAP Utility routines */


/* Interactive Mail Access Protocol garbage collect messagearray
* Accepts: messagearray
*
* The messagearray is set to all NIL when this function finishes.
*/

void imap_free_messagearray (messagearray)
   MESSAGECACHE **messagearray;
{
 int i;
                               /* for each array element */
 for (i = 0; i < MAXMESSAGES; ++i)
   if (messagearray[i]) {      /* if something there free it */
     imap_free_elt (messagearray[i]);
     messagearray[i] = NIL;
   }
}


/* Interactive Mail Access Protocol garbage collect cache element
* Accepts: cache element
*/

void imap_free_elt (elt)
   MESSAGECACHE *elt;
{
 if (elt->internalDate) free (elt->internalDate);
 if (elt->envPtr) imap_free_envelope (elt->envPtr);
 if (elt->fromText) free (elt->fromText);
 if (elt->subjectText) free (elt->subjectText);
 free ((char *) elt);
}


/* Interactive Mail Access Protocol garbage collect envelope
* Accepts: envelope
*/

void imap_free_envelope (envelope)
   ENVELOPE *envelope;
{
 if (envelope->date) free (envelope->date);
 if (envelope->subject) free (envelope->subject);
 if (envelope->from) imap_free_address (envelope->from);
 if (envelope->sender) imap_free_address (envelope->sender);
 if (envelope->reply_to) imap_free_address (envelope->reply_to);
 if (envelope->to) imap_free_address (envelope->to);
 if (envelope->cc) imap_free_address (envelope->cc);
 if (envelope->bcc) imap_free_address (envelope->bcc);
 if (envelope->in_reply_to) free (envelope->in_reply_to);
 if (envelope->message_id) free (envelope->message_id);
 free ((char *) envelope);     /* finally free the envelope */
}


/* Interactive Mail Access Protocol garbage collect address
* Accepts: address
*/

void imap_free_address (address)
   ADDRESS *address;
{
 if (address->personalName) free (address->personalName);
 if (address->routeList) free (address->routeList);
 if (address->mailBox) free (address->mailBox);
 if (address->host) free (address->host);
 if (address->next) imap_free_address (address->next);
 free ((char *) address);      /* finally free the address */
}


/* Interactive Mail Access Protocol lock stream
* Accepts: IMAP2 stream
*/

void imap_lock (stream)
   IMAPSTREAM *stream;
{
/*  if (stream->lock) _exit (10); */    /* lock when already locked */
 if (stream->lock) exit (10);
 else stream->lock = T;        /* lock stream */
}


/* Interim Mail Access Protocol unlock stream
* Accepts: IMAP2 stream
*/

void imap_unlock (stream)
   IMAPSTREAM *stream;
{
/*  if (!stream->lock) _exit (11); */ /* unlock when not locked */
 if (!stream->lock) exit (11);
 else stream->lock = NIL;      /* unlock stream */
}