/* * 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 */ }