/* Copyright (c) 2017, Computer History Museum All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines * * Author: Mark Crispin * Networks and Distributed Computing * Computing & Communications * University of Washington * Administration Building, AG-44 * Seattle, WA 98195 * Internet: MRC@CAC.Washington.EDU * * Date: 15 June 1988 * Last Edited: 4 March 1997 * * Sponsorship: The original version of this work was developed in the * Symbolic Systems Resources Group of the Knowledge Systems * Laboratory at Stanford University in 1987-88, and was funded * by the Biomedical Research Technology Program of the National * Institutes of Health under grant number RR-00785. * * Original version Copyright 1988 by The Leland Stanford Junior University * Copyright 1997 by the University of Washington * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, provided * that the above copyright notices appear in all copies and that both the * above copyright notices and this permission notice appear in supporting * documentation, and that the name of the University of Washington or The * Leland Stanford Junior University not be used in advertising or publicity * pertaining to distribution of the software without specific, written prior * permission. This software is made available "as is", and * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "mail.h" #include "osdep.h" #include "imap4r1.h" #include "rfc822.h" #include "misc.h" #include "myssl.h" #pragma segment IMAP /* allow ucase(NULL) */ #define ucase MyUpperCase static char *extraheaders = " BODY.PEEK[HEADER.FIELDS (Path Message-ID Newsgroups Followup-To References)]"; /* Driver dispatch used by MAIL */ DRIVER imapdriver = { "imap", // driver name DR_MAIL|DR_NEWS|DR_NAMESPACE, // driver flags (DRIVER *) NULL, // next driver imap_valid, // mailbox is valid for us imap_parameters, // manipulate parameters imap_scan, // scan mailboxes imap_list, // find mailboxes imap_lsub, // find subscribed mailboxes imap_subscribe, // subscribe to mailbox imap_unsubscribe, // unsubscribe from mailbox imap_create, // create mailbox imap_delete, // delete mailbox imap_rename, // rename mailbox imap_status, // status of mailbox imap_open, // open mailbox imap_close, // close mailbox imap_fast, // fetch message "fast" attributes imap_flags, // fetch message flags NIL, // fetch overview // JOK - added imap_envelope imap_envelope, imap_structure, // fetch message envelopes NULL, // fetch message IMAPheader NULL, // fetch message body imap_msgdata, // fetch partial message imap_uid, // unique identifier imap_msgno, // message number imap_flag, // modify flags NULL, // per-message modify flags imap_search, // search for message based on criteria NULL, // sort messages NULL, // thread messages imap_ping, // ping mailbox to see if still alive imap_check, // check for new messages imap_expunge, // expunge deleted messages imap_copy, // copy messages to another mailbox imap_append, // append string message to mailbox imap_gc, // garbage collect stream // added these, too ... imap_connected, // return if this stream is connected. imap_rfc822size }; /* driver parameters */ static unsigned long imap_maxlogintrials = MAXLOGINTRIALS; static long imap_lookahead = IMAPLOOKAHEAD; static long imap_uidlookahead = IMAPUIDLOOKAHEAD; static long imap_defaultport = 0; static long imap_prefetch = IMAPLOOKAHEAD; static long imap_closeonerror = NULL; // UIDPLUS Support void VerifyUIDValidity(MAILSTREAM *stream, char *pUids, int len); OSErr UIDStringToUIDs(char *pUids, int len, Accumulator *pAccu); OSErr StoreUIDPLUSResponses(MAILSTREAM *stream, IMAPPARSEDREPLY *reply); /* IMAP validate mailbox * Accepts: mailbox name * Returns: our driver if name is valid, NULL otherwise */ DRIVER *imap_valid (char *name) { return mail_valid_net (name,&imapdriver,NULL,NULL); } /* IMAP manipulate driver parameters * Accepts: function code * function-dependent value * Returns: function-dependent return value */ void *imap_parameters (long function,void *value) { switch ((int) function) { case SET_MAXLOGINTRIALS: imap_maxlogintrials = (long) value; break; case GET_MAXLOGINTRIALS: value = (void *) imap_maxlogintrials; break; case SET_LOOKAHEAD: imap_lookahead = (long) value; break; case GET_LOOKAHEAD: value = (void *) imap_lookahead; break; case SET_UIDLOOKAHEAD: imap_uidlookahead = (long) value; break; case GET_UIDLOOKAHEAD: value = (void *) imap_uidlookahead; break; case SET_IMAPPORT: imap_defaultport = (long) value; break; case GET_IMAPPORT: value = (void *) imap_defaultport; break; case SET_PREFETCH: imap_prefetch = (long) value; break; case GET_PREFETCH: value = (void *) imap_prefetch; break; case SET_CLOSEONERROR: imap_closeonerror = (long) value; break; case GET_CLOSEONERROR: value = (void *) imap_closeonerror; break; default: value = NULL; // error case break; } return value; } /* IMAP scan mailboxes * Accepts: mail stream * reference * pattern to search * string to scan */ void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) { imap_list_work (stream,ref,pat,T,contents); } /* IMAP list mailboxes * Accepts: mail stream * reference * pattern to search */ void imap_list (MAILSTREAM *stream,char *ref,char *pat) { imap_list_work (stream,ref,pat,T,NULL); } /* IMAP list subscribed mailboxes * Accepts: mail stream * reference * pattern to search */ void imap_lsub (MAILSTREAM *stream,char *ref,char *pat) { // we don't currently do anything with subscriptions // JDB 6-23-98 #ifdef NOT_USED void *sdb = NULL; char *s; imap_list_work (stream,ref,pat,NULL,NULL); // only if null stream and * requested if (!stream && !ref && !strcmp (pat,"*") && (s = sm_read (&sdb))) { do if (imap_valid (s)) mm_lsub (stream,NULL,s,NULL); while (s = sm_read (&sdb)); // until no more subscriptions } #endif } /* IMAP find list of mailboxes * Accepts: mail stream * reference * pattern to search * list flag * string to scan */ void imap_list_work (MAILSTREAM *stream,char *ref,char *pat,long list, char *contents) { char *s, prefix[MAILTMPLEN], mbx[MAILTMPLEN]; IMAPARG *args[4],aref,apat,acont; // Must have an open stream (JOK). if (!(stream && LOCAL && stream->transStream)) return; // have a reference? if (ref && *ref) { if (strlen (ref) >= sizeof (prefix)) return; strcpy (prefix, ref); // build prefix LOCAL->prefix = prefix; // note prefix } else { LOCAL->prefix = NIL; // no prefix } if (contents) { // want to do a scan? if (LOCAL->use_scan) { // can we? args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL; aref.type = ASTRING; aref.text = (void *) (ref ? ref : ""); apat.type = LISTMAILBOX; apat.text = (void *) pat; acont.type = ASTRING; acont.text = (void *) contents; imap_send (stream,"SCAN",args); } else mm_log ("Scan not valid on this IMAP server",WARN); } else if (LEVELIMAP4 (stream)) {// easy if IMAP4 args[0] = &aref; args[1] = &apat; args[2] = NIL; aref.type = ASTRING; aref.text = (void *) (ref ? ref : ""); apat.type = LISTMAILBOX; apat.text = (void *) pat; imap_send (stream,list ? "LIST" : "LSUB",args); } else if (LEVEL1176 (stream)) {// convert to IMAP2 format wildcard // kludgy application of reference if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); else strcpy (mbx,pat); for (s = mbx; *s; s++) { if (*s == '%') *s = '*'; } args[0] = &apat; args[1] = NIL; apat.type = LISTMAILBOX; apat.text = (void *) mbx; if (!(list && // if list, try IMAP2bis, then RFC-1176 form strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) || !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD")) { LOCAL->rfc1176 = NIL; // must be RFC-1064 } } LOCAL->prefix = NIL; // no more prefix } /* IMAP subscribe to mailbox * Accepts: mail stream * mailbox to add to subscription list * Returns: T on success, NULL on failure */ long imap_subscribe (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Subscribe Mailbox",NULL); } /* IMAP unsubscribe to mailbox * Accepts: mail stream * mailbox to delete from manage list * Returns: T on success, NULL on failure */ long imap_unsubscribe (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Unsubscribe Mailbox",NULL); } /* IMAP create mailbox * Accepts: mail stream * mailbox name to create * Returns: T on success, NULL on failure */ long imap_create (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Create",NULL); } /* IMAP delete mailbox * Accepts: mail stream * mailbox name to delete * Returns: T on success, NULL on failure */ long imap_delete (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Delete",NULL); } /* IMAP rename mailbox * Accepts: mail stream * old mailbox name * new mailbox name * Returns: T on success, NULL on failure */ long imap_rename (MAILSTREAM *stream,char *old,char *newname) { return imap_manage (stream,old,"Rename",newname); } /* IMAP manage a mailbox * Accepts: mail stream * mailbox to manipulate * command to execute * optional second argument * Returns: T on success, NULL on failure */ long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2) { MAILSTREAM *st = stream; IMAPPARSEDREPLY *reply; long ret = NULL; char mbx[MAILTMPLEN],mbx2[MAILTMPLEN]; IMAPARG *args[3],ambx,amb2; ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx; amb2.text = (void *) mbx2; args[0] = &ambx; args[1] = args[2] = NULL; // Must have a valid open stream. if (!(stream && stream && stream->transStream)) return NIL; // Validate network stuff. if ( imapmail_valid_net (stream, &imapdriver, NIL) ) { // JOK Don't use what was put into mbx. Assume caller specified a correct mailbox name. strcpy (mbx, mailbox); // Also, if second argument, use arg2 as passed in. if (arg2) { strcpy (mbx2, arg2); args[1] = &amb2; /* second arg present? */ } // JOK reply = imap_send (stream,command,args); if (reply) { ret = imap_OK (stream, reply); mm_log (reply->text,ret ? (long) NIL : IMAP_ERROR); } } return ret; } /* IMAP status * Accepts: mail stream * mailbox name * status flags * Returns: T on success, NULL on failure */ long imap_status (MAILSTREAM *stream,char *mbx,long flags) { IMAPARG *args[3],ambx,aflg; char tmp[MAILTMPLEN]; NETMBX mb; long ret = NIL; // Must have a valid and open stream (JOK)!! if (!(stream && LOCAL && stream->transStream)) return NIL; // must have a mailbox name if (!mbx) return NIL; // Copy stuff into NETMBX. imapmail_valid_net_parse (stream, &mb); // use the name of the mailbox passed in, please - JDB 060899 strcpy(mb.mailbox, mbx); /* can't use stream if not IMAP4rev1, STATUS, * or halfopen and right host */ if ((!(LEVELSTATUS (stream) || stream->halfopen) || strcmp (ucase (strcpy (tmp,imap_host (stream))), ucase (mb.host)))) { // JOK - fail, return NIL; } args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */ ambx.type = ASTRING; ambx.text = (void *) mb.mailbox; if (LEVELSTATUS (stream)) { /* have STATUS command? */ aflg.type = FLAGS; aflg.text = (void *) tmp; args[1] = &aflg; args[2] = NIL; tmp[0] = tmp[1] = '\0'; /* build flag list */ if (flags & SA_MESSAGES) strcat (tmp," MESSAGES"); if (flags & SA_RECENT) strcat (tmp," RECENT"); if (flags & SA_UNSEEN) strcat (tmp," UNSEEN"); if (flags & SA_UIDNEXT) strcat (tmp,LEVELIMAP4rev1 (stream) ? "UIDNEXT" : " UID-NEXT"); if (flags & SA_UIDVALIDITY) strcat (tmp,LEVELIMAP4rev1 (stream) ? "UIDVALIDITY" : " UID-VALIDITY"); tmp[0] = '('; strcat (tmp,")"); /* send "STATUS mailbox flag" */ if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T; } // JOK - Don't do imap2 status. return T; } /* IMAP authenticate * Accepts: stream to authenticate * parsed network mailbox structure * scratch buffer * place to return user name * Returns: T on success, NULL on failure */ unsigned long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) { unsigned long trial,ua; char tag[16]; AUTHENTICATOR *at; IMAPPARSEDREPLY *reply = nil; for (ua = LOCAL->use_auth; stream->transStream && ua;) { if (!(at = mail_lookup_auth(find_rightmost_bit (&ua) + 1))) fatal ("Authenticator logic bug!"); trial = 0; do { sprintf (tag,"A%05ld",stream->gensym++); // gensym a new tag sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name); // build command if (imap_soutr (stream,tmp) && (*at->client) (imap_challenge,imap_response,mb,stream,&trial,usr)) { // This replaces original (JOK). while (1) { reply = imap_reply (stream, tag); if (!reply) break; if (strcmp(reply->tag, tag)) imap_soutr(stream,"*"); else break; } #ifdef JOK_ORIGINAL // (JOK) while (strcmp ((reply = imap_reply (stream,tag))->tag,tag)) imap_soutr (stream,"*"); #endif // done if got success response if (imap_OK (stream,reply)) return T; mm_log (reply->text,WARN); } } while (stream->transStream && !LOCAL->byeseen && trial && (trial < imap_maxlogintrials)); } if (PrefIsSet(PREF_IMAP_STUPID_PASSWORD)) { // the login failed for some reason. Wipe the password. InvalidatePasswords(false,false,false); } else { // invalidate the password, if we got a real reply back from the server. The password was bad. if (reply && (!reply->fake) && (strcmp(reply->key,"BAD") || strcmp(reply->key,"NO"))) InvalidatePasswords(false,false,false); } return NULL; // ran out of authenticators } /* IMAP login * Accepts: stream to login * parsed network mailbox structure * scratch buffer * place to return user name * Returns: T on success, NULL on failure */ unsigned long imap_login (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) { unsigned long trial = 0; IMAPPARSEDREPLY *reply; IMAPARG *args[3]; IMAPARG ausr,apwd; //Must have a stream if (!stream) return NIL; ausr.type = apwd.type = ASTRING; ausr.text = (void *) usr; apwd.text = (void *) tmp; args[0] = &ausr; args[1] = &apwd; args[2] = NULL; while (stream->transStream && !LOCAL->byeseen && (trial < imap_maxlogintrials)) { tmp[0] = 0; // get password // automatic if want anonymous access if (mb->anoflag || stream->anonymous) { strcpy (usr,"anonymous"); strcpy (tmp,net_localhost (stream->transStream)); trial = imap_maxlogintrials; } else // otherwise get the user's login information. { mm_login (mb,usr,tmp,trial++); if (!tmp[0]) // user refused to give a password { mm_log ("Login aborted",IMAP_ERROR); return NULL; } } // send "LOGIN usr tmp" if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args))) { // login successful, note if anonymous stream->anonymous = strcmp (usr,"anonymous") ? NULL : T; // If we have succeeded, tell that to the stream stream->bAuthenticated = TRUE; return T; // successful login } mm_log (reply->text,WARN); } // Make sure the stream knows. stream->bAuthenticated = FALSE; stream->bSelected = FALSE; if (PrefIsSet(PREF_IMAP_STUPID_PASSWORD)) { // the login failed for some reason. Wipe the password. InvalidatePasswords(false,false,false); } else { // invalidate the password, if we got a real reply back from the server. The password was bad. if (reply && (!reply->fake) && (strcmp(reply->key,"BAD") || strcmp(reply->key,"NO"))) InvalidatePasswords(false,false,false); } mm_log ("Incorrect password specified.",IMAP_ERROR); return NULL; } /* Get challenge to authenticator in binary * Accepts: stream * pointer to returned size * Returns: challenge or NULL if not challenge */ // // NOTE: (JOK) - This function returns a "ready response" from the server, already // base64 decoded. That is, the response from the server that follows the "+" sign. // NOTE: This returns allocated memory that must be freed by the caller!! // void *imap_challenge (void *s, unsigned long *len) { MAILSTREAM *stream = (MAILSTREAM *) s; IMAPPARSEDREPLY *reply = NIL; char *p = net_getline (stream->transStream); // JOK if (p) { reply = imap_parse_reply (stream, p); if (reply) { return strcmp (reply->tag,"+") ? NIL : rfc822_base64 ((unsigned char *) reply->text, strlen (reply->text), len); } } return NIL; } /* Send authenticator response in BASE64 * Accepts: MAIL stream * string to send * length of string * Returns: T if successful, else NULL */ long imap_response (void *s,char *response,unsigned long size) { MAILSTREAM *stream = (MAILSTREAM *) s; unsigned long i,j,ret; char *t,*u; if (size) // make CRLFless BASE64 string { for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;j < i; j++) if (t[j] > ' ') *u++ = t[j]; *u = '\0'; // tie off string for mm_dlog() if (stream->debug) mm_dlog (t); // append CRLF *u++ = '\015'; *u++ = '\012'; ret = net_sout (stream->transStream,t,i = u - t); fs_give ((void **) &t); } else if (response) ret = imap_soutr (stream,""); else // abort requested ret = imap_soutr (stream,"*"); return ret; } /* IMAP close * Accepts: MAIL stream * option flags */ void imap_close (MAILSTREAM *stream,long options) { IMAPPARSEDREPLY *reply; if (stream && LOCAL) // send "LOGOUT" { if (!LOCAL->byeseen) // don't even think of doing it if saw a BYE { // expunge silently if requested if (options & CL_EXPUNGE) imap_send (stream,"EXPUNGE",NULL); // don't bother waiting for the logout response unless we're being polite if (!PrefIsSet(PREF_IMAP_POLITE_LOGOUT)) stream->fastLogout = true; if (stream->transStream && !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NULL))) mm_log (reply->text,WARN); } // close NET connection if still open if (stream->transStream) net_close (stream->transStream); stream->transStream = NULL; // reset the stream state stream->bConnected = FALSE; stream->bAuthenticated = FALSE; stream->bSelected = FALSE; // throw away any list results. if (stream->fListResultsHandle) DisposeMailboxTree(&(stream->fListResultsHandle)); if (stream->fUIDResults) UID_LL_Zap(&(stream->fUIDResults)); // free up any network data we may have stored if (stream->fNetData) DisposeHandle(stream->fNetData); // free up memory if (LOCAL->user) fs_give ((void **) &LOCAL->user); if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); // nuke the local data fs_give ((void **) &stream->local); } } /* IMAP fetch fast information * Accepts: MAIL stream * sequence * option flags * * Generally, imap_fetchstructure is preferred */ //JDB now returns result Boolean imap_fast (MAILSTREAM *stream,char *sequence,long flags) { char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,aatt; Boolean result = false; //Format command aseq.type = SEQUENCE; aseq.text = (void *) sequence; aatt.type = ATOM; aatt.text = (LEVELIMAP4 (stream)) ? (void *) "(FLAGS INTERNALDATE RFC822.SIZE UID)" : (void *) "FAST"; args[0] = &aseq; args[1] = &aatt; args[2] = NULL; reply = imap_send(stream, cmd, args); if (reply) { if (imap_OK(stream, reply)) result = true; if (!result) mm_log (reply->text,IMAP_ERROR); } return (result); } /* IMAP fetch flags * Accepts: MAIL stream * sequence * option flags */ //JDB returns result now. Boolean imap_flags (MAILSTREAM *stream,char *sequence,long flags) { char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,aatt; Boolean result = false; //Format the command. aseq.type = SEQUENCE; aseq.text = (void *) sequence; aatt.type = ATOM; // JDB 1-11-99 fetch the size of the message along with the flags for efficiency #ifdef DEBUG if (stream->flagsRefN > 0) aatt.text = (void *) "FLAGS"; else #endif if (!PrefIsSet(PREF_IMAP_NOTHING_BUT_HEAD)) aatt.text = (void *) "(FLAGS RFC822.SIZE)"; else aatt.text = (void *) "FLAGS"; args[0] = &aseq; args[1] = &aatt; args[2] = NULL; //Send it. reply = imap_send (stream,cmd,args); if (reply) { if ( imap_OK (stream, reply) ) result = true; if (!result) mm_log (reply->text,IMAP_ERROR); } return result; } /* IMAP fetch structure * Accepts: MAIL stream * message # to fetch * pointer to return body * option flags * Returns: envelope of this message, body returned in body value * * Fetches the "fast" information as well */ // NOTE: // JOK - This is a new imap_structure!! It fetches just the body structure. // The parsing routine allocates a BODY and attaches it to the "current elt" in thye // stream. We check for the body, detaches it from the elt and passes it to the caller. // The caller MUST free the body when done with it. // END NOTE IMAPBODY *imap_structure (MAILSTREAM *stream,unsigned long msgno, long flags) { char seq[128],tmp[MAILTMPLEN]; IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; IMAPBODY *b; MESSAGECACHE *elt = NULL; // Cannot have a zero msgno. if (msgno <= 0) return NULL; // Initialize: "b" is what's returned. b = NULL; // Setup for IMAP call. args[0] = &aseq; args[1] = &aatt; args[2] = NIL; aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = NIL; // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); // Allocate a new one. elt = mail_elt (stream); if (elt) { if (flags & FT_UID) elt->privat.uid = msgno; else elt->msgno = msgno; } // NOTE: "msgno" can be a UID or a message sequence number. sprintf (seq,"%lu",msgno); /* initial sequence (UID or msgno) */ // Format command based on server capability. // NOTE: Can't handle any IMAP version older than imap2bis!! if (LEVELIMAP4 (stream) && (flags & FT_UID)) { sprintf (tmp,"(UID BODYSTRUCTURE)"); aatt.text = (void *) tmp; /* do the built command */ if (!imap_OK (stream, reply = imap_send (stream,"UID FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } } else if (LEVELIMAP2bis (stream)) { /* has non-extensive body and no UID. */ sprintf (tmp,"(BODY)"); aatt.text = (void *) tmp; /* do the built command */ if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } } // "b" is what's returned. b = NULL; // Did we get anything? if (stream->CurrentElt) { // Make sure the UID's or msgno's matched. if (flags & FT_UID) { if (stream->CurrentElt->privat.uid == msgno) b = stream->CurrentElt->privat.msg.body; } else { if (stream->CurrentElt->msgno == msgno) b = stream->CurrentElt->privat.msg.body; } // Make sure we detach the body pointer if there was one. // If it's not our body, we'd want to delete it. if (b) stream->CurrentElt->privat.msg.body = NULL; // Now free the elt. mail_free_elt (&stream->CurrentElt); } return b; } /* IMAP fetch message data * Accepts: MAIL stream * message number * section specifier * offset of first designated byte or 0 to start at beginning * maximum number of bytes or 0 for all bytes * lines to fetch if IMAPheader * flags * Returns: T on success, NULL on failure */ long imap_msgdata (MAILSTREAM *stream, unsigned long msgno, char *sequence, char *section, unsigned long first,unsigned long last,STRINGLIST *lines,long flags) { char *t,tmp[MAILTMPLEN],part[40]; char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; IMAPPARSEDREPLY *reply; IMAPARG *args[5],aseq,aatt,alns,acls; if (sequence) { aseq.type = SEQUENCE; aseq.text = (void *) sequence; } else { aseq.type = NUMBER; aseq.text = (void *) msgno; } aatt.type = ATOM; // assume atomic attribute alns.type = LIST; alns.text = (void *) lines; acls.type = BODYCLOSE; acls.text = (void *) part; args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NULL; part[0] = '\0'; // initially no partial specifier if (LEVELIMAP4rev1 (stream)) // easy case if IMAP4rev1 server { aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; if (lines) // want specific IMAPheader lines? { sprintf (tmp,"%s.FIELDS%s",section,(flags & FT_NOT) ? ".NOT" : ""); aatt.text = (void *) tmp; args[2] = &alns; args[3] = &acls; } else { aatt.text = (void *) section; args[2] = &acls; } if (first || last) sprintf (part,"<%lu.%lu>",first,last ? last:-1); } else if (!strcmp (section,"HEADER")) // IMAPBODY.PEEK[HEADER] becomes RFC822.HEADER { if (flags & FT_PEEK) aatt.text = (void *) "RFC822.HEADER"; else { mm_notify (stream,"[NOTIMAP4] Can't do non-peeking IMAPheader fetch",WARN); return NULL; } } else if ((flags & FT_PEEK) && !LEVEL1730 (stream)) // other peeking was introduced in RFC-1730 { mm_notify (stream,"[NOTIMAP4] Can't do peeking fetch",WARN); return NULL; } else if (!strcmp (section,"TEXT")) // IMAPBODY[TEXT] becomes RFC822.TEXT { aatt.text = (void *)((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" : "RFC822.TEXT"); } else if (!section[0]) // IMAPBODY[] becomes RFC822 { aatt.text = (void *)((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822"); } else if (t = strstr (section,".HEADER")) // nested IMAPheader { if (!LEVEL1730 (stream)) // this was introduced in RFC-1730 { mm_notify (stream,"[NOTIMAP4] Can't do nested IMAPheader fetch",WARN); return NULL; } aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; args[2] = &acls; // will need to close section aatt.text = (void *) tmp; // convert .HEADER to .0 for RFC-1730 server strncpy (tmp,section,t-section); strcpy (tmp+(t-section),".0"); } else if (strstr (section,".MIME") || strstr (section,".TEXT")) // extended nested text { mm_notify (stream,"[NOTIMAP4REV1] Can't do extended body part fetch",WARN); return NULL; } else if (LEVELIMAP2bis (stream)) // nested message { aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; args[2] = &acls; /* will need to close section */ aatt.text = (void *) section; } else // ancient server { mm_notify (stream,"[NOTIMAP2BIS] Can't do body part fetch",WARN); return NULL; } //Note to self: just what exactly happens to the reply after this? Who cleans it up? if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) // send the fetch command { mm_log (reply->text,IMAP_ERROR); return NULL; // failure } return T; } /* IMAP fetch UID * Accepts: MAIL stream * message number * Returns: UID */ // NOTE: Modified by JOK. unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno) { IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; char seq[MAILTMPLEN]; unsigned long uid; MESSAGECACHE *elt = NULL; /* IMAP2 didn't have UIDs */ if (!LEVELIMAP4 (stream)) return msgno; // Cannot have a zero msgno. if (msgno <= 0) return msgno; // Initialize: uid = 0; // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); // Allocate a new one. elt = mail_elt (stream); if (elt) elt->msgno = msgno; // Setup for IMAP call. sprintf (seq, "%lu", msgno); aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = (void *) "UID"; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; /* send "FETCH msgno UID" */ if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } // Did we get anything? if (stream->CurrentElt) { uid = stream->CurrentElt->privat.uid; // Now free the elt. mail_free_elt (&stream->CurrentElt); } return uid; } /* IMAP fetch message number from UID * Accepts: MAIL stream * UID * Returns: message number */ // NOTE: Modified by JOK. unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid) { IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; char seq[MAILTMPLEN]; unsigned long msgno; MESSAGECACHE *elt = NULL; /* IMAP2 didn't have UIDs */ if (!LEVELIMAP4 (stream)) return uid; // Initialize msgno = 0; /* have server hunt for UID */ aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = (void *) "UID"; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; sprintf (seq,"%lu",uid); // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); // Allocate a new one. elt = mail_elt (stream); if (elt) elt->privat.uid = uid; /* send "UID FETCH uid UID" */ if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } // Did we get anything? if (stream->CurrentElt) { // Make sure the uid's matched. if (stream->CurrentElt->privat.uid == uid) msgno = stream->CurrentElt->msgno; // Now free the elt. mail_free_elt (&stream->CurrentElt); } return msgno; /* didn't find the UID anywhere */ } /* IMAP modify flags * Accepts: MAIL stream * sequence * flag(s) * option flags */ // JDB - added Boolean return to indicate success Boolean imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) { char *cmd; IMAPPARSEDREPLY *reply; IMAPARG *args[4],aseq,ascm,aflg; Boolean result = true; MESSAGECACHE *elt = NULL; // Must have a stream if (!stream) return false; cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE"; aseq.type = SEQUENCE; aseq.text = (void *) sequence; ascm.type = ATOM; ascm.text = (void *)((flags & ST_SET) ? ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? "+Flags.silent" : "+Flags") : ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? "-Flags.silent" : "-Flags")); aflg.type = FLAGS; aflg.text = (void *) flag; args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NULL; // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); // Allocate a new one. elt = mail_elt (stream); // send "STORE sequence +Flags flag" if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { result = false; mm_log (reply->text,IMAP_ERROR); } // Did we get anything? if (stream->CurrentElt) { // BUG: Is ST_SILENT is NOT set, we should check to see if the flags were // returned from the server correctly. // Now free the elt. mail_free_elt (&stream->CurrentElt); } return (result); } /* IMAP search for messages * Accepts: MAIL stream * character set * search program * option flags */ // JOK // This now just sends the command to the server. Untagged respponses will // be sent to a callback function. // Boolean imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags) { Boolean result = true; IMAPPARSEDREPLY *reply; IMAPARG *args[3],apgm,aseq,aatt; args[1] = args[2] = NIL; apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm; aseq.type = SEQUENCE; aatt.type = ATOM; if (charset) { args[0] = &aatt; args[1] = &apgm; aatt.text = (void *) charset; } else args[0] = &apgm; /* do the SEARCH */ if (!imap_OK (stream, reply = imap_send (stream, (flags & SE_UID) ? "UID SEARCH" : "SEARCH", args))) { if (reply) { mm_log (reply->text,IMAP_ERROR); result = false; // the search failed. } } return (result); } /* IMAP ping mailbox * Accepts: MAIL stream * Returns: T if stream still alive, else NULL */ long imap_ping (MAILSTREAM *stream) { return (stream->transStream && /* send "NOOP" */ imap_OK (stream,imap_send (stream,"NOOP",NULL))) ? T : NULL; } /* IMAP check mailbox * Accepts: MAIL stream */ void imap_check (MAILSTREAM *stream) { /* send "CHECK" */ IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NULL); mm_log (reply->text,imap_OK (stream,reply) ? (long) NULL : IMAP_ERROR); } /* IMAP expunge mailbox * Accepts: MAIL stream */ long imap_expunge (MAILSTREAM *stream) { long result = NULL; /* send "EXPUNGE" */ IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NULL); result = imap_OK (stream,reply) ? (long) NULL : IMAP_ERROR; mm_log (reply->text,result); return (result ? NULL : T); } /* IMAP copy message(s) * Accepts: MAIL stream * sequence * destination mailbox * option flags * Returns: T if successful else NULL */ long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags) { char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY"; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,ambx; aseq.type = SEQUENCE; aseq.text = (void *) sequence; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[0] = &aseq; args[1] = &ambx; args[2] = NULL; /* send "COPY sequence mailbox" */ if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { mm_log (reply->text,IMAP_ERROR); return NULL; } /* check for UIDPLUS responses */ StoreUIDPLUSResponses(stream, reply); /* delete the messages if the user said to */ if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted", ST_SET + ((flags & CP_UID) ? ST_UID : NULL)); return T; } /* IMAP append message string * Accepts: mail stream * destination mailbox * stringstruct of message to append * Returns: T on success, NULL on failure */ /* Modified by JOK, May, 1997. */ long imap_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date, STRING *msg) { char tmp[MAILTMPLEN]; long ret = NIL; IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[5],ambx,aflg,adat,amsg; // START JOK // Must have a valid mailbox if (!mailbox) return ret; // Copy mailbox into tmp. strcpy (tmp, mailbox); // END JOK /* Must have a valid and open stream. (JOK) */ if (IsConnected(stream) && !stream->halfopen) { if (imapmail_valid_net (stream, &imapdriver,NIL)) { ambx.type = ASTRING; ambx.text = (void *) tmp; aflg.type = FLAGS; aflg.text = (void *) flags; adat.type = ASTRING; adat.text = (void *) date; amsg.type = LITERAL; amsg.text = (void *) msg; if (flags || date) { /* IMAP4 form? */ int i = 0; args[i++] = &ambx; if (flags) args[i++] = &aflg; if (date) args[i++] = &adat; args[i++] = &amsg; args[i++] = NIL; reply = imap_send (stream,"APPEND",args); if ( reply && !strcmp (reply->key,"OK") ) ret = T; } /* try IMAP2bis form if IMAP4 form fails */ if (!(ret || (reply && strcmp (reply->key,"BAD")))) { args[0] = &ambx; args[1] = &amsg; args[2] = NIL; if (imap_OK (stream, reply = imap_send (stream,"APPEND",args))) ret = T; } if (!ret) { if (reply) mm_log (reply->text,IMAP_ERROR); } } } else mm_log ("Can't access server for append",IMAP_ERROR); return ret; /* return */ } /* IMAP garbage collect stream * Accepts: Mail stream * garbage collection flags */ void imap_gc (MAILSTREAM *stream,long gcflags) { unsigned long i; MESSAGECACHE *elt; mailcache_t mc = (mailcache_t) mail_parameters (NULL,GET_CACHE,NULL); // make sure the cache is large enough (*mc) (stream,stream->nmsgs,CH_SIZE); if (gcflags & GC_TEXTS) // garbage collect texts? { if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i) if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) imap_gc_body (elt->privat.msg.body); imap_gc_body (stream->body); } // gc cache if requested and unlocked if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i) if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) && (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE); } /* IMAP garbage collect body texts * Accepts: body to GC */ void imap_gc_body (IMAPBODY *body) { PART *part; if (body) // have a body? { if (body->mime.text.data) // flush MIME data fs_give ((void **) &body->mime.text.data); // flush text contents if (body->contents.text.data) fs_give ((void **) &body->contents.text.data); body->mime.text.size = body->contents.text.size = 0; // multipart? if (body->type == TYPEMULTIPART) { for (part = body->nested.part; part; part = part->next) imap_gc_body (&part->body); } else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) // MESSAGE/RFC822? { imap_gc_body (body->nested.msg->body); if (body->nested.msg->full.text.data) fs_give ((void **) &body->nested.msg->full.text.data); if (body->nested.msg->IMAPheader.text.data) fs_give ((void **) &body->nested.msg->IMAPheader.text.data); if (body->nested.msg->text.text.data) fs_give ((void **) &body->nested.msg->text.text.data); body->nested.msg->full.text.size = body->nested.msg->IMAPheader.text.size = body->nested.msg->text.text.size = 0; } } } /* Internal routines */ /* IMAP send atom-string * Accepts: MAIL stream * reply tag * pointer to current position pointer of output bigbuf * atom-string to output * string length * flag if list_wildcards allowed * Returns: error reply or NULL if success */ IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s, char *t,unsigned long size,long wildok) { unsigned long j; STRING st; int quoted = size ? NULL : T; // default to not quoted unless empty for (j = 0; j < size; j++) switch (t[j]) { case '\0': // not a CHAR case '\012': case '\015': // not a TEXT-CHAR case '"': case '\\': // quoted-specials (IMAP2 required this) INIT (&st,mail_string,(void *) t,size); return imap_send_literal (stream,tag,s,&st); default: // all other characters if (t[j] > ' ') break; // break if not a CTL case '*': case '%': // list_wildcards if (wildok) break; // allowed if doing the wild thing // atom_specials case '(': case ')': case '{': case ' ': quoted = T; // must use quoted string format break; } if (quoted) *(*s)++ = '"'; // write open quote while (size--) *(*s)++ = *t++; // write the characters if (quoted) *(*s)++ = '"'; // write close quote return NULL; } /* IMAP send literal * Accepts: MAIL stream * reply tag * pointer to current position pointer of output bigbuf * literal to output as stringstruct * Returns: error reply or NULL if success */ IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,STRING *st) { IMAPPARSEDREPLY *reply; unsigned long i = SIZE (st); sprintf (*s,"{%ld}",i); // write literal count *s += strlen (*s); // size of literal count // send the command reply = imap_sout (stream,tag,LOCAL->tmp,s); if (strcmp (reply->tag,"+")) // prompt for more data? { //No longer //mail_unlock (stream); // no, give up return reply; } while (i) // dump the text { if (!net_sout (stream->transStream,st->curpos,st->cursize)) { //No longer //mail_unlock (stream); return imap_fake (stream,tag,"IMAP connection broken (data)"); } i -= st->cursize; // note that we wrote out this much st->curpos += (st->cursize - 1); st->cursize = 0; (*st->dtb->next) (st); // advance to next buffer's worth } return NULL; // success } /* IMAP send search program * Accepts: MAIL stream * reply tag * pointer to current position pointer of output bigbuf * search program to output * Returns: error reply or NIL if success */ IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char **s, SEARCHPGM *pgm) { IMAPPARSEDREPLY *reply; SEARCHHEADER *hdr; SEARCHOR *pgo; SEARCHPGMLIST *pgl; // char *t = "ALL"; char *t = ""; while (*t) *(*s)++ = *t++; /* default initial text */ /* message sequences */ if (pgm->msgno) imap_send_sset (s,pgm->msgno); if (pgm->uid) { /* UID sequence */ for (t = "UID "; *t; *(*s)++ = *t++); imap_send_sset (s,pgm->uid); } /* message sizes */ if (pgm->larger) { sprintf (*s,"LARGER %lu",pgm->larger); *s += strlen (*s); } if (pgm->smaller) { sprintf (*s,"SMALLER %lu",pgm->smaller); *s += strlen (*s); } /* message flags */ if (pgm->answered) for (t = "ANSWERED "; *t; *(*s)++ = *t++); if (pgm->unanswered) for (t ="UNANSWERED "; *t; *(*s)++ = *t++); if (pgm->deleted) for (t ="DELETED "; *t; *(*s)++ = *t++); if (pgm->undeleted) for (t ="UNDELETED "; *t; *(*s)++ = *t++); if (pgm->draft) for (t ="DRAFT "; *t; *(*s)++ = *t++); if (pgm->undraft) for (t ="UNDRAFT "; *t; *(*s)++ = *t++); if (pgm->flagged) for (t ="FLAGGED "; *t; *(*s)++ = *t++); if (pgm->unflagged) for (t ="UNFLAGGED "; *t; *(*s)++ = *t++); if (pgm->recent) for (t ="RECENT "; *t; *(*s)++ = *t++); if (pgm->old) for (t ="OLD "; *t; *(*s)++ = *t++); if (pgm->seen) for (t ="SEEN "; *t; *(*s)++ = *t++); if (pgm->unseen) for (t ="UNSEEN "; *t; *(*s)++ = *t++); if ((pgm->keyword && /* keywords */ (reply = imap_send_slist (stream,tag,s,"KEYWORD",pgm->keyword))) || (pgm->unkeyword && (reply = imap_send_slist (stream,tag,s,"UNKEYWORD",pgm->unkeyword)))) return reply; /* sent date ranges */ if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore); if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton); if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince); /* internal date ranges */ if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before); if (pgm->on) imap_send_sdate (s,"ON",pgm->on); if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since); /* search texts */ if ((pgm->bcc && (reply = imap_send_slist (stream,tag,s,"BCC",pgm->bcc))) || (pgm->cc && (reply = imap_send_slist (stream,tag,s,"CC",pgm->cc))) || (pgm->from && (reply = imap_send_slist(stream,tag,s,"FROM",pgm->from)))|| (pgm->to && (reply = imap_send_slist (stream,tag,s,"TO",pgm->to)))) return reply; if ((pgm->subject && (reply = imap_send_slist (stream,tag,s,"SUBJECT",pgm->subject))) || (pgm->body && (reply = imap_send_slist(stream,tag,s,"BODY",pgm->body)))|| (pgm->text && (reply = imap_send_slist (stream,tag,s,"TEXT",pgm->text)))) return reply; if (hdr = pgm->IMAPheader) do { for (t = "HEADER "; *t; *(*s)++ = *t++); for (t = hdr->line; *t; *(*s)++ = *t++); // JOK - Add a space!! for (t = " "; *t; *(*s)++ = *t++); if (reply = imap_send_astring (stream,tag,s,hdr->text, (unsigned long) strlen (hdr->text),NIL)) return reply; } while (hdr = hdr->next); if (pgo = pgm->or) do { for (t = "OR ("; *t; *(*s)++ = *t++); if (reply = imap_send_spgm (stream,tag,s,pgm->or->first)) return reply; for (t = ") ("; *t; *(*s)++ = *t++); if (reply = imap_send_spgm (stream,tag,s,pgm->or->second)) return reply; *(*s)++ = ')'; } while (pgo = pgo->next); if (pgl = pgm->not) do { for (t = "NOT ("; *t; *(*s)++ = *t++); if (reply = imap_send_spgm (stream,tag,s,pgl->pgm)) return reply; *(*s)++ = ')'; } while (pgl = pgl->next); return NIL; /* search program written OK */ } /* IMAP send search set * Accepts: pointer to current position pointer of output bigbuf * search set to output */ void imap_send_sset (char **s,SEARCHSET *set) { char c = 0; char *t; // Sanity: Must have at least these. if (!(set && set->first)) { ASSERT (0); return; } do { /* run down search set */ if (c) { // If last is 0xFFFFFFFF, replace by "*". (JOK) // if (set->last == 0xFFFFFFFF) { sprintf (*s, "%c%lu:*", c, set->first); } else { sprintf (*s, set->last ? "%c%lu:%lu" : "%c%lu",c,set->first, set->last); } } else { if (set->last == 0xFFFFFFFF) { sprintf (*s, "%lu:*",set->first); } else { sprintf (*s, set->last ? "%lu:%lu" : "%lu",set->first,set->last); } } *s += strlen (*s); c = ','; /* if there are any more */ } while (set = set->next); // (JOK) Add a space after the list. for (t = " "; *t; *(*s)++ = *t++); } /* IMAP send search list * Accepts: MAIL stream * reply tag * pointer to current position pointer of output bigbuf * name of search list * search list to output * Returns: NIL if success, error reply if error */ IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char **s, char *name,STRINGLIST *list) { char *t; IMAPPARSEDREPLY *reply = NIL; do { // (JOK) Space screws up some servers!! *(*s)++ = ' '; /* output name of search list */ for (t = name; *t; *(*s)++ = *t++); *(*s)++ = ' '; reply=imap_send_astring (stream,tag,s,list->text.data,list->text.size,NIL); } while (!reply && (list = list->next)); return reply; } /* IMAP send search date * Accepts: pointer to current position pointer of output bigbuf * field name * search date to output */ void imap_send_sdate (char **s,char *name,unsigned short date) { sprintf (*s," %s %d-%s-%d",name,date & 0x1f, months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9)); *s += strlen (*s); } /* IMAP send null-terminated string to sender * Accepts: MAIL stream * string * Returns: T if success, else NIL */ long imap_soutr (MAILSTREAM *stream,char *string) { char tmp[MAILTMPLEN]; if (stream->debug) mm_dlog (string); return (net_soutr (stream->transStream,strcat (strcpy (tmp,string),"\015\012"))); } /* IMAP fake reply * Accepts: MAIL stream * tag * text of fake reply * Returns: parsed reply * * Added field to IMAPPARSEDREPLY to distinguish a fake reply from a real one -jdboyd 01/19/00 */ IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text) { IMAPPARSEDREPLY *reply = nil; mm_notify (stream,text,BYE); // send bye alert // don't kill the net stream anymore. Take care of this elsewhere. -JDB //if (stream->transStream) net_close (stream->transStream); //stream->transStream = NIL; // farewell, dear NET stream... // build fake reply string sprintf (LOCAL->tmp,"%s NO [CLOSED] %s",tag ? tag : "*",text); // parse and return it reply = imap_parse_reply (stream,cpystr (LOCAL->tmp)); if (reply) reply->fake = true; return (reply); } /* IMAP message has been expunged * Accepts: MAIL stream * message number * * Calls external "mail_expunged" function to notify main program */ void imap_expunged (MAILSTREAM *stream,unsigned long msgno) { // Must have a stream. if (!stream) return; // All we do is to pass this to the upper layers. mail_expunged (stream, msgno); } /* IMAP parse data * Accepts: MAIL 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 (MAILSTREAM *stream,unsigned long msgno,char *text,IMAPPARSEDREPLY *reply) { char *prop; MESSAGECACHE *elt = mail_elt (stream); if (!elt) return; // Must have a reply ... if (!reply) return; ++text; // skip past open parenthesis // parse Lisp-form property list while (prop = (char *) strtok (text," )")) { // point at value text = (char *) strtok (NIL,"\n"); // parse the property and its value imap_parse_prop (stream,elt,ucase (prop),&text,reply); } } /* IMAP parse property * Accepts: MAIL stream * cache item * property name * property value text pointer * parsed reply */ void imap_parse_prop (MAILSTREAM *stream,MESSAGECACHE *elt,char *prop,char **txtptr,IMAPPARSEDREPLY *reply) { char *s; ENVELOPE **env; IMAPBODY **body; GETS_DATA md; // Make sure we have a reply if (!reply) return; INIT_GETS (md,stream,elt->msgno,NIL,0,0); if (!strcmp (prop,"ENVELOPE")) { imapenvelope_t ie = (imapenvelope_t) mail_parameters (NIL,GET_IMAPENVELOPE,NIL); if (stream->scache) // short cache, flush old stuff { mail_free_body (&stream->body); stream->msgno=elt->msgno; // set new current message number env = &stream->env; // get pointer to envelope } else env = &elt->privat.msg.env; imap_parse_envelope (stream,env,txtptr,reply); // do callback if requested if (ie) (*ie) (stream,elt->msgno,*env); } else if (!strcmp (prop,"FLAGS")) { imap_parse_flags (stream,elt,txtptr); // JOK (6/23/97) Pass the elt to top level to record the info. // Do this only if the uid has already been obtained and flags are valid. if (elt->privat.uid && elt->valid) { mm_elt_flags (stream, elt); // After we have notified the upper layers, reset the elt's status // because it mey be re-used. elt->privat.uid = 0; elt->valid = NIL; } } else if (!strcmp (prop,"INTERNALDATE")) { if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL)) { if (!mail_parse_date (elt,s)) { sprintf (LOCAL->tmp,"Bogus date: %.80s",s); mm_log (LOCAL->tmp,WARN); } fs_give ((void **) &s); } } else if (!strcmp (prop,"UID")) // unique identifier { elt->privat.uid = strtoul (*txtptr,txtptr,10); // JOK (6/23/97) Pass the elt to top level to record the info. // Do this only if the uid has already been obtained and flags are valid. if (elt->privat.uid && elt->valid) { mm_elt_flags (stream, elt); // After we have notified the upper layers, reset the elt's status // because it mey be re-used. elt->privat.uid = 0; elt->valid = NIL; } // return the UID in the stream if we're chunking headers if (stream->chunkHeaders) { stream->headerUID = elt->privat.uid; } } else if (!strcmp (prop,"RFC822.HEADER") || !strcmp (prop,"BODY[HEADER]") || !strcmp (prop,"BODY[0]")) { if (elt->privat.msg.IMAPheader.text.data) fs_give ((void **) &elt->privat.msg.IMAPheader.text.data); md.what = "HEADER"; elt->privat.msg.IMAPheader.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.IMAPheader.text.size); } else if (!strcmp (prop,"RFC822.SIZE")) elt->rfc822_size = strtoul (*txtptr,txtptr,10); else if (!strcmp (prop,"RFC822.TEXT") || !strcmp (prop,"BODY[TEXT]")) { if (elt->privat.msg.text.text.data) fs_give ((void **) &elt->privat.msg.text.text.data); md.what = "TEXT"; elt->privat.msg.text.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.text.text.size); } else if (!strcmp (prop,"RFC822") || !strcmp (prop,"BODY[]")) { if (elt->privat.msg.full.text.data) fs_give ((void **) &elt->privat.msg.full.text.data); md.what = ""; elt->privat.msg.full.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.full.text.size); } else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' && prop[3] == 'Y') { s = cpystr (prop+4); // copy segment specifier if (stream->scache) // short cache, flush old stuff { if (elt->msgno != stream->msgno) { /* losing real bad here */ mail_free_envelope (&stream->env); sprintf (LOCAL->tmp,"Body received for %lu when current is %lu", elt->msgno,stream->msgno); mm_log (LOCAL->tmp,WARN); stream->msgno = elt->msgno; } body = &stream->body; /* get pointer to body */ } else body = &elt->privat.msg.body; imap_parse_body (&md,body,s,txtptr,reply); fs_give ((void **) &s); } else { sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop); mm_log (LOCAL->tmp,WARN); } } /* Parse RFC822 message IMAPheader * Accepts: MAIL stream * envelope to parse into * IMAPheader as sized text */ void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr) { ENVELOPE *nenv; // parse what we can from this IMAPheader rfc822_parse_msg (&nenv,NIL,hdr->data,hdr->size,NIL,imap_host (stream)); if (*env) // need to merge this IMAPheader into envelope? { if (!(*env)->newsgroups) // need Newsgroups? { (*env)->newsgroups = nenv->newsgroups; nenv->newsgroups = NIL; } if (!(*env)->followup_to) // need Followup-To? { (*env)->followup_to = nenv->followup_to; nenv->followup_to = NIL; } if (!(*env)->references) // need References? { (*env)->references = nenv->references; nenv->references = NIL; } mail_free_envelope (&nenv); } else *env = nenv; // otherwise set it to this } /* IMAP parse envelope * Accepts: MAIL stream * pointer to envelope pointer * current text pointer * parsed reply * * Updates text pointer */ void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,char **txtptr,IMAPPARSEDREPLY *reply) { ENVELOPE *oenv = *env; char c = *((*txtptr)++); // grab first character // ignore leading spaces while (c == ' ') c = *((*txtptr)++); switch (c) // dispatch on first character { case '(': // if envelope S-expression *env = mail_newenvelope (); // parse the new envelope (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL); (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL); (*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,NIL,NIL); (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL); if (oenv) // need to merge old envelope? { (*env)->newsgroups = oenv->newsgroups; oenv->newsgroups = NIL; (*env)->followup_to = oenv->followup_to; oenv->followup_to = NIL; (*env)->references = oenv->references; oenv->references = NIL; mail_free_envelope(&oenv); // free old envelope } if (**txtptr != ')') { sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else ++*txtptr; // skip past delimiter break; case 'N': // if NIL case 'n': ++*txtptr; ++*txtptr; break; default: sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); break; } } /* IMAP parse address list * Accepts: MAIL stream * current text pointer * parsed reply * Returns: address list, NIL on failure * * Updates text pointer */ ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { ADDRESS *adr = NIL; char c = **txtptr; // sniff at first character // ignore leading spaces while (c == ' ') c = *++*txtptr; ++*txtptr; // skip past open paren switch (c) { case '(': // if envelope S-expression adr = imap_parse_address (stream,txtptr,reply); if (**txtptr != ')') // need to merge old envelope? { sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else ++*txtptr; // skip past delimiter break; case 'N': //If NIL case 'n': ++*txtptr; ++*txtptr; break; default: sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); break; } return adr; } /* IMAP parse address * Accepts: MAIL stream * current text pointer * parsed reply * Returns: address, NIL on failure * * Updates text pointer */ ADDRESS *imap_parse_address (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { ADDRESS *adr = NIL; ADDRESS *ret = NIL; ADDRESS *prev = NIL; char c = **txtptr; /* sniff at first address character */ switch (c) { case '(': // if envelope S-expression while (c == '(') // recursion dies on small stack machines { ++*txtptr; // skip past open paren if (adr) prev = adr; // note previous if any adr = mail_newaddr (); // instantiate address and parse its fields adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL); adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL); adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL); adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL); if (**txtptr != ')') // handle trailing paren { sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else ++*txtptr; // skip past close paren c = **txtptr; // set up for while test // ignore leading spaces in front of next while (c == ' ') c = *++*txtptr; if (!ret) ret = adr; // if first time note first adr // if previous link new block to it if (prev) prev->next = adr; } break; case 'N': //Nil case 'n': *txtptr += 3; // bump past NIL break; default: sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); break; } return ret; } /* IMAP parse flags * Accepts: current message cache * current text pointer * * Updates text pointer */ void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,char **txtptr) { char *flag; char c = '\0'; Str255 sentFlag; // read the flag that we'll treat as //Sent from the settings ... GetRString(sentFlag, IMAP_SENT_FLAG); if (sentFlag[0]) { sentFlag[sentFlag[0] + 1] = 0; ucase(sentFlag); } elt->valid = T; // mark have valid flags now elt->user_flags = NIL; // zap old flag values elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = elt->sent = NIL; #ifdef DEBUG if (stream->flagsRefN > 0) { char *s = *txtptr; long count = strlen(*txtptr); while ((s < *txtptr + count) && (*s != ')')) s++; if (*s==')') s++; count = s - *txtptr; // write the line to the spool file if (count) { AWrite(stream->flagsRefN,&count,*txtptr); FSWriteP(stream->flagsRefN,Cr); } } #endif while (c != ')') // parse list of flags { // point at a flag while (*(flag = ++*txtptr) == ' '); // scan for end of flag while (**txtptr != ' ' && **txtptr != ')') ++*txtptr; c = **txtptr; // save delimiter **txtptr = '\0'; // tie off flag if (!*flag) break; // null flag else if (*ucase (flag) == '\\') // if starts with \ must be sys flag { if (sentFlag[0] && !strcmp (flag,sentFlag+1)) elt->sent = T; else if (!strcmp (flag,"\\SEEN")) elt->seen = T; else if (!strcmp (flag,"\\DELETED")) elt->deleted = T; else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T; else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T; else if (!strcmp (flag,"\\RECENT")) elt->recent = T; else if (!strcmp (flag,"\\DRAFT")) elt->draft = T; } else // otherwise user flag elt->user_flags |= imap_parse_user_flag (stream,flag); } ++*txtptr; // bump past delimiter mm_flags (stream,elt->msgno); // make sure top level knows } /* IMAP parse user flag * Accepts: MAIL stream * flag name * Returns: flag bit position */ unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag) { char tmp[MAILTMPLEN]; long i; // sniff through all user flags for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i] && !strcmp (flag,ucase (strcpy (tmp,stream->user_flags[i])))) return (1 << i); // found it! return (unsigned long) 0; // not found } /* IMAP parse string * Accepts: MAIL stream * current text pointer * parsed reply * mailgets data * returned string length * Returns: string * * Updates text pointer */ char *imap_parse_string (MAILSTREAM *stream,char **txtptr, IMAPPARSEDREPLY *reply,GETS_DATA *md, unsigned long *len) { char *st; char *string = NIL; unsigned long i,j,k; char c = **txtptr; // sniff at first character mailgets_t mg = (mailgets_t) mail_parameters (stream,GET_GETS,NIL); readprogress_t rp = (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL); while (c == ' ') c = *++*txtptr; // ignore leading spaces st = ++*txtptr; // remember start of string switch (c) { case '"': // if quoted string i = 0; // initial byte count while (**txtptr != '"') // search for end of string { if (**txtptr == '\\') ++*txtptr; ++i; // bump count ++*txtptr; // bump pointer } ++*txtptr; // bump past delimiter // JOK - If we have a mailgets, do that instead. if (md && mg) { // Allocate a ParenStr data object to send data to caller. ParenStrData strData; md->flags |= MG_COPY;/* otherwise flag need to copy */ strData.s = st; strData.size = i; (*mg) (str_getbuffer, &strData, i, md); // Doesn't return a value. string = NULL; } /* else must copy into free storage */ else { string = (char *) fs_get ((size_t) i + 1); if (!string) return (NULL); //fs_get returns nil if it fails for (j = 0; j < i; j++) // copy the string { if (*st == '\\') ++st; // quoted character string[j] = *st++; } string[j] = '\0'; // tie off string if (len) *len = i; // set return value too } break; case 'N': // if NIL case 'n': ++*txtptr; // bump past "I" ++*txtptr; // bump past "L" if (len) *len = 0; break; case '{': // if literal string // get size of string i = strtoul (*txtptr,txtptr,10); if (len) *len = i; // set return value if (md && mg) // have special routine to slurp string? { if (md->first) // partial fetch? { md->first--; // restore origin octet md->last = i; // number of octets that we got } else md->flags |= MG_COPY; // otherwise flag need to copy string = (*mg) (net_getbuffer,stream->transStream,i,md); } else // must slurp into free storage { string = (char *) fs_get ((size_t) i + 1); if (!string) return (NULL); //fs_get returns nil if it fails *string = '\0'; // init in case getbuffer fails if (rp) for (k = 0; j = min ((long) MAILTMPLEN,(long) i); i -= j) { net_getbuffer (stream->transStream,j,string + k); (*rp) (md,k += j); } else net_getbuffer (stream->transStream,i,string); } fs_give ((void **) &reply->line); // get new reply text line reply->line = net_getline (stream->transStream); if (stream->debug) mm_dlog (reply->line); *txtptr = reply->line; // set text pointer to point at it break; default: sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr); mm_log (LOCAL->tmp,WARN); if (len) *len = 0; break; } return string; } /* IMAP parse body structure or contents * Accepts: mailgets_data * pointer to body pointer * pointer to segment * current text pointer * parsed reply * * Updates text pointer, stores body */ void imap_parse_body (GETS_DATA *md,IMAPBODY **body,char *seg,char **txtptr,IMAPPARSEDREPLY *reply) { char *s; unsigned long size; STRINGLIST *stl = NIL; char *tmp = ((IMAPLOCAL *) md->stream->local)->tmp; MESSAGECACHE *elt; // Get the stream's "CurrentElt" (JOK) // This will allocate one if necessary. elt = mail_elt (md->stream); if (!elt) return; /* dispatch based on type of data */ switch (*seg++) { case 'S': /* extensible body structure */ if (strcmp (seg,"TRUCTURE")) { sprintf (tmp,"Bad body fetch: %.80s",seg); mm_log (tmp,WARN); return; } /* falls through */ case '\0': /* body structure */ mail_free_body (body); /* flush any prior body */ /* instantiate and parse a new body */ imap_parse_body_structure (md->stream, *body = mail_newbody(), txtptr, reply); break; // JOK: 4/24/98 - Added .PEEK because the Novell groupwise server returns BODY.PEEK!!! case '.': ucase (seg); /* make sure uppercase */ // Better be this: if (strncmp (seg,"PEEK[", 5)) { sprintf (tmp,"Bad body fetch: %.80s",seg); mm_log (tmp,WARN); return; } // Othersize, go pass PEEK[ and fall through: seg += 5; case '[': /* body section text */ ucase (seg); /* make sure uppercase */ /* header lines case? */ if (!(s = strchr(seg,']'))) { /* skip leading nesting */ for (s = seg; *s && (isdigit (*s) || (*s == '.')); s++); /* better be one of these */ if (strcmp (s,"HEADER.FIELDS") && strcmp (s,"HEADER.FIELDS.NOT")) { sprintf (tmp,"Unterminated section specifier: %.80s",seg); mm_log (tmp,WARN); return; } /* get list of headers */ if (!(stl = imap_parse_stringlist (md->stream,txtptr,reply))) { sprintf (tmp,"Bogus header field list: %.80s",*txtptr); mm_log (tmp,WARN); return; } // JOK - We don't really need the string list!!! mail_free_stringlist (&stl); // END JOK /* make sure terminated */ if (**txtptr != ']') { sprintf (tmp,"Unterminated header section specifier: %.80s",*txtptr); mm_log (tmp,WARN); mail_free_stringlist (&stl); return; } /* point after the text */ if (*txtptr = strchr (s = *txtptr,' ')) *(*txtptr)++ = '\0'; } *s++ = '\0'; /* tie off section specifier */ /* partial specifier? */ if (*s == '<') { md->first = strtoul (s+1,&s,10) + 1; // Some servers spit back a in the partial specifiers. // They are both wrong and stupid. See RFC 2060, the FETCH response. -jdboyd 8/19/99 if (*s == '.') { s++; // skip over the period while (isdigit(*s)) s++; // and any numbers that follow. } /* make sure properly terminated */ if ((*s == NULL) || (*s++ != '>')) { sprintf (tmp,"Unterminated partial data specifier: %.80s",s-1); mm_log (tmp,WARN); mail_free_stringlist (&stl); return; } } /* make sure no junk follows */ if (*s) { sprintf (tmp,"Junk after section specifier: %.80s",s); mm_log (tmp,WARN); mail_free_stringlist (&stl); return; } md->what = seg; /* get the body section text */ // JOK - Calling "imap_parse_string()" will send the data to the caller. s = NULL; // So we won't try to free it. imap_parse_string (md->stream, txtptr, reply, md, &size); // done if partial if (md->first || md->last) { mail_free_stringlist (&stl); return; } // JOK - Ignore the rest. break; default: /* bogon */ sprintf (tmp,"Bad body fetch: %.80s",seg); mm_log (tmp,WARN); return; } // switch } /* IMAP parse body structure * Accepts: MAIL stream * body structure to write into * current text pointer * parsed reply * * Updates text pointer */ void imap_parse_body_structure (MAILSTREAM *stream,IMAPBODY *body,char **txtptr,IMAPPARSEDREPLY *reply) { int i; char *s; PART *part = NIL; char c; // Must have these ... if (!stream || !body || !txtptr || !(*txtptr)) return; // grab first character c = *((*txtptr)++); // ignore leading spaces while (c == ' ') c = *((*txtptr)++); // dispatch on first character switch (c) { case '(': // body structure list if (**txtptr == '(') // multipart body? { body->type= TYPEMULTIPART; // yes, set its type // instantiate new body part do { if (part) part = part->next = mail_newbody_part (); else body->nested.part = part = mail_newbody_part (); // parse it imap_parse_body_structure (stream,&part->body,txtptr,reply); // ignore possible spaces until the next '(' (JOK) while (**txtptr == ' ') ++(*txtptr); } while (**txtptr == '('); // for each body part if (body->subtype = imap_parse_string (stream,txtptr,reply,NIL,NIL)) ucase (body->subtype); else { // Set it to "Multipart/Mixed" (JOK) // body->subtype = cpystr ("Mixed"); mm_log ("Missing multipart subtype",WARN); } // multipart parameters if (**txtptr == ' ') body->parameter = imap_parse_body_parameter (stream,txtptr,reply); // disposition if (**txtptr == ' ') imap_parse_disposition (stream,body,txtptr,reply); // language if (**txtptr == ' ') body->language = imap_parse_language (stream,txtptr,reply); while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); // validate ending if (**txtptr != ')') { sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else ++*txtptr; // skip past delimiter } else { // not multipart, parse type name if (**txtptr == ')') { // empty body? ++*txtptr; // bump past it break; // and punt } body->type = TYPEOTHER; // assume unknown type body->encoding = ENCOTHER; // and unknown encoding // parse type if (s = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL))) { for (i=0;(i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]);i++); if (i <= TYPEMAX) { // only if found a slot body->type = (unsigned short)i; // set body type if (body_types[i]) fs_give ((void **) &s); else body_types[i]=s; // assign empty slot } } // parse subtype body->subtype = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL)); // parse parameter body->parameter = imap_parse_body_parameter (stream,txtptr,reply); // parse id body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL); // parse description body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL); if (s = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL))) { // search for body encoding for (i = 0; (i <= ENCMAX) && body_encodings[i] && strcmp (s,body_encodings[i]); i++); if (i > ENCMAX) body->type = ENCOTHER; else { // only if found a slot body->encoding = (unsigned short) i; // set body encoding if (body_encodings[i]) fs_give ((void **) &s); else body_encodings[i] = s; } #ifdef WINDERZ // // If the subtype is binhex and the encoding is read as just text, // this should be decoded with binhex. Since we only pass an encoding type to // "FetchAttachmentContentsToFile" below, set an appropriate encoding flag. // CRString szBinhex (IDS_MIME_BINHEX); CString szSubtype = body->subtype; if ( (body->subtype != NULL) && (!strnicmp ( (LPCSTR)szSubtype, (LPCSTR)szBinhex, szBinhex.GetLength() ) ) ) { if ( body->encoding == ENC7BIT || body->encoding == ENCOTHER ) { body->encoding = ENCBINHEX; } } #endif } // parse size of contents in bytes body->size.bytes = strtoul (*txtptr,txtptr,10); // possible extra stuff switch (body->type) { case TYPEMESSAGE: // message envelope and body if (strcmp (body->subtype,"RFC822")) break; body->nested.msg = mail_newmsg (); imap_parse_envelope (stream,&body->nested.msg->env,txtptr,reply); body->nested.msg->body = mail_newbody (); imap_parse_body_structure(stream,body->nested.msg->body,txtptr,reply); // drop into text case // size in lines case TYPETEXT: body->size.lines = strtoul (*txtptr,txtptr,10); break; // otherwise nothing special default: break; } // if extension data if (**txtptr == ' ') body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL); // disposition if (**txtptr == ' ') imap_parse_disposition (stream,body,txtptr,reply); // language if (**txtptr == ' ') body->language = imap_parse_language (stream,txtptr,reply); while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); // validate ending if (**txtptr != ')') { sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else { ++*txtptr; // skip past delimiter // ignore possible spaces until the next '(' (JOK) while (**txtptr == ' ') ++(*txtptr); } } break; case 'N': // if NIL case 'n': ++*txtptr; // bump past "I" ++*txtptr; // bump past "L" break; default: // otherwise quite bogus sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); break; } } /* IMAP parse body parameter * Accepts: MAIL stream * current text pointer * parsed reply * Returns: body parameter * Updates text pointer */ PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { PARAMETER *ret = NIL; PARAMETER *par = NIL; char c,*s; // ignore leading spaces while ((c = *(*txtptr)++) == ' '); // parse parameter list if (c == '(') { while (c != ')') { // append new parameter to tail if (ret) par = par->next = mail_newbody_parameter (); else ret = par = mail_newbody_parameter (); if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL))) { mm_log ("Missing parameter attribute",WARN); par->attribute = cpystr ("UNKNOWN"); } if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL))) { sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute); mm_log (LOCAL->tmp,WARN); par->value = cpystr ("UNKNOWN"); } switch (c = **txtptr) // see what comes after { case ' ': // flush whitespace while ((c = *++*txtptr) == ' '); break; case ')': // end of attribute/value pairs ++*txtptr; // skip past closing paren break; default: sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); break; } } } // empty parameter, must be NIL else if (((c == 'N') || (c == 'n')) && ((*(s = *txtptr) == 'I') || (*s == 'i')) && ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2; else { sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,(*txtptr) - 1); mm_log (LOCAL->tmp,WARN); } return ret; } /* IMAP parse body disposition * Accepts: MAIL stream * body structure to write into * current text pointer * parsed reply */ void imap_parse_disposition (MAILSTREAM *stream,IMAPBODY *body,char **txtptr,IMAPPARSEDREPLY *reply) { switch (*++*txtptr) { case '(': ++*txtptr; // skip open paren body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL); body->disposition.parameter = imap_parse_body_parameter (stream,txtptr,reply); if (**txtptr != ')') // validate ending { sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); } else ++*txtptr; // skip past delimiter break; case 'N': // if NIL case 'n': ++*txtptr; // bump past "N" ++*txtptr; // bump past "I" ++*txtptr; // bump past "L" break; default: sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); // try to skip to next space while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr); break; } } /* IMAP parse body language * Accepts: MAIL stream * current text pointer * parsed reply * Returns: string list or NIL if empty or error */ STRINGLIST *imap_parse_language (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { unsigned long i; char *s; STRINGLIST *ret = NIL; // language is a list if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply); else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i)) { (ret = mail_newstringlist ())->text.data = s; ret->text.size = i; } return ret; } /* IMAP parse string list * Accepts: MAIL stream * current text pointer * parsed reply * Returns: string list or NIL if empty or error */ STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { STRINGLIST *stl = NIL; STRINGLIST *stc; char c,*s; char *t = *txtptr; // parse the list if (*t++ == '(') { while (*t != ')') { if (stl) stc = stc->next = mail_newstringlist (); else stc = stl = mail_newstringlist (); // atom if ((*t != '{') && (*t != '"') && (s = strpbrk (t," )"))) { c = *s; // note delimiter *s = '\0'; // tie off atom and copy it stc->text.size = strlen (stc->text.data = cpystr (t)); if (c == ' ') t = ++s; // another atom follows else *(t = s) = c; // restore delimiter } // string else if (!(stc->text.data = imap_parse_string (stream,&t,reply,NIL,&stc->text.size))) { sprintf (LOCAL->tmp,"Bogus string list member: %.80s",t); mm_log (LOCAL->tmp,WARN); mail_free_stringlist (&stl); break; } } } if (stl) *txtptr = ++t; // update return string return stl; } /* IMAP parse unknown body extension data * Accepts: MAIL stream * current text pointer * parsed reply * * Updates text pointer */ void imap_parse_extension (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply) { unsigned long i,j; switch (*++*txtptr) // action depends upon first character { case '(': while (**txtptr != ')') imap_parse_extension (stream,txtptr,reply); ++*txtptr; // bump past closing parenthesis break; case '"': // if quoted string while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr; ++*txtptr; // bump past closing quote break; case 'N': // if NIL case 'n': ++*txtptr; // bump past "N" ++*txtptr; // bump past "I" ++*txtptr; // bump past "L" break; case '{': // get size of literal ++*txtptr; // bump past open squiggle if (i = strtoul (*txtptr,txtptr,10)) do net_getbuffer (stream->transStream,j = min (i,(long)IMAPTMPLEN),LOCAL->tmp); // was "max", which makes little sense. -jdboyd 030304 while (i -= j); // get new reply text line reply->line = net_getline (stream->transStream); if (stream->debug) mm_dlog (reply->line); *txtptr = reply->line; // set text pointer to point at it break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': strtoul (*txtptr,txtptr,10); break; default: sprintf (LOCAL->tmp,"Unknown extension token: %.80s",*txtptr); mm_log (LOCAL->tmp,WARN); // try to skip to next space while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr); break; } } /* IMAP return host name * Accepts: MAIL stream * Returns: host name */ char *imap_host (MAILSTREAM *stream) { /* return host name on stream if open */ return (LOCAL && stream->transStream) ? net_host (stream->transStream) : ".NO-IMAP-CONNECTION."; } /* IMAP open * Accepts: stream to open * Returns: stream to use on success, NULL on failure */ MAILSTREAM *imap_open (MAILSTREAM *stream) { unsigned long i; unsigned long alive; char *s; char tmp[MAILTMPLEN]; char userName[MAILTMPLEN]; NETMBX mb; IMAPPARSEDREPLY *reply = NULL; // return prototype for OP_PROTOTYPE call if (!stream) return ((MAILSTREAM *)&imapdriver); // fill in the mb structure from the command line. imapmail_valid_net_parse(stream,&mb); userName[0] = NULL; // Username initially empty if (LOCAL) // if stream opened earlier by us { // if the stream is not autenticated at this point, close it and re-open it. if (!stream->bAuthenticated) imap_close(stream,nil); // if hosts are different, close the old one. Note, mb.host is a pstring else if (((mb.host[0] && imap_host(stream)) && !pstrincmp(mb.host,imap_host(stream),mb.host[0])) || (mb.user[0] && LOCAL->user && strcmp(mb.user,LOCAL->user))) { sprintf(tmp,"Closing connection to %s",imap_host(stream)); if (!stream->silent) mm_log(tmp,(long) NULL); imap_close(stream,NULL); } else if (stream->transStream) { // else recycle if still alive i = stream->silent; stream->silent = true; // alive = imap_ping(stream); // learn if stream still alive alive = true; // imapconnections.c has already determined this stream is still alive. - JDB 160799 stream->silent = i; if (alive) { sprintf(tmp,"Reusing connection to %s",mb.host); if (LOCAL->user) sprintf(tmp + strlen (tmp),"/user=%s",LOCAL->user); if (!stream->silent) mm_log(tmp,(long) NULL); } else imap_close(stream,NULL); } } // if a re-open // in case /debug switch given if (mb.dbgflag) stream->debug = true; // open new connection if no recycle if (!LOCAL) { stream->local = fs_get(sizeof (IMAPLOCAL)); if (!stream->local) return NULL; FillInLocal : // assume IMAP2bis server LOCAL->imap2bis = LOCAL->rfc1176 = true; if (!stream->transStream) // didn't get rimap? { unsigned long prt; // use the given port, or the default port if non specified. if(mb.port) prt = mb.port; else #ifdef ESSL if(GetPrefLong(PREF_SSL_IMAP_SETTING) & esslUseAltPort) prt = GetRLong(IMAP_SSL_PORT); else #endif prt = IMAPTCPPORT; s = mb.host; // try to open ordinary connection if ((stream->transStream = net_open(stream,s,"imap",prt)) && !imap_OK(stream,reply = imap_reply(stream,NULL))) { mm_log (reply->text,IMAP_ERROR); return NULL; } } if (stream->transStream) // if have a connection { // non-zero if not preauthenticated if (( reply->key != NULL ) && ( i = strcmp(reply->key,"PREAUTH" ))) userName[0] = '\0'; // get server capabilities reply = imap_send (stream,"CAPABILITY",NULL); #ifdef ESSL if(reply && imap_OK(stream,reply) && ShouldUseSSL(stream->transStream) && !(stream->transStream->ESSLSetting & esslSSLInUse)) { // OK, there are several cases here. // (1) Server doesn't offer TLS // (a) User has required TLS -- error // (b) TLS is optional - do nothing // (2) Server has offered TLS --> fire it up! // (a) ESSLStartSSL succeeeds --> We're good to go // (b) ESSLStartSSL fails // (1) User has required TLS - error // (2) TLS is optional - continue w/o TLS Boolean sslRequired = 0 == ( stream->transStream->ESSLSetting & esslOptional ); if (!LOCAL->use_tls) // No TLS for this server! { if ( sslRequired ) { ComposeStdAlert ( Note, ALRTStringsStrn+NO_SERVER_SSL ); goto DoSSLErr; } } else { IMAPPARSEDREPLY *sslReply; // starttls sslReply = imap_send (stream,"STARTTLS",NULL); if(!sslReply || !imap_OK(stream, sslReply) || (ESSLStartSSL(stream->transStream) != noErr)) { DoSSLErr : if ( sslRequired ) { net_close(stream->transStream); stream->transStream = NULL; mm_log(GetRString(tmp, SSL_ERR_STRING)+1, IMAP_ERROR); return NIL; } } else if(stream->transStream->ESSLSetting & esslSSLInUse) { WriteZero(LOCAL, sizeof(IMAPLOCAL)); goto FillInLocal; } } } #endif if (reply) { // need to authenticate? if (stream->transStream && i && !((LOCAL->use_auth && !(mb.anoflag || stream->anonymous) && !PrefIsSet(PREF_IMAP_DONT_AUTHENTICATE)) ? imap_auth (stream,&mb,tmp,userName) : imap_login (stream,&mb,tmp,userName))) { // Close the stream if we failed to authenticate. if (stream->transStream) net_close (stream->transStream); stream->transStream = NULL; return NIL; // authentication failed } // We succeeded. And we are now authenticated. stream->bAuthenticated = true; } else { // User must have quit. if (stream->transStream) net_close(stream->transStream); stream->transStream = NULL; return NIL; // authentication failed } } } if (stream->transStream) // still have a connection? { stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = LEVELIMAP4(stream) ? NULL : true; stream->perm_user_flags = LEVELIMAP4(stream) ? NULL : 0xffffffff; stream->sequence++; if ((i = net_port(stream->transStream)) & 0xffff0000) i = imap_defaultport ? imap_defaultport : IMAPTCPPORT; // record user name if (!LOCAL->user && userName[0]) LOCAL->user = cpystr(userName); sprintf (tmp,LOCAL->user ? "{%s:%lu/imap/user=%s}" : "{%s:%lu/imap}", net_host(stream->transStream),i,LOCAL->user); if (!stream->halfopen) // wants to open a mailbox? { IMAPARG *args[2]; IMAPARG ambx; ambx.type = ASTRING; ambx.text = (void *) mb.mailbox; args[0] = &ambx; args[1] = NULL; reply = imap_send (stream,stream->rdonly ? "EXAMINE": "SELECT",args); if (reply && imap_OK(stream,reply)) { // We've succeeded. stream->bSelected = TRUE; strcat(tmp,mb.mailbox); // note if server said it was readonly stream->rdonly = !strncmp(ucase (reply->text),"[READ-ONLY]",11); if (!stream->nmsgs && !stream->silent) mm_log ("Mailbox is empty",(long) NULL); } else // failed { // We're not selected stream->bSelected = FALSE; mm_log (reply->text,IMAP_ERROR); if (imap_closeonerror) return NULL; stream->halfopen = true; /* let him keep it half-open */ } } if (stream->halfopen) // half-open connection? { // Not selected stream->bSelected = FALSE; strcat(tmp,""); // make sure dummy message counts mail_exists(stream,(long) 0); mail_recent(stream,(long) 0); } fs_give ((void **) &stream->mailbox); stream->mailbox = cpystr (tmp); } /* success if stream open */ return stream->transStream ? stream : NULL; } /* IMAP get reply * Accepts: MAIL stream * tag to search or NIL if want a greeting * Returns: parsed reply, never NIL */ IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag) { IMAPPARSEDREPLY *reply; if (!stream) return (0); while (imap_connected(stream) && stream->transStream) { // parse reply from server if (reply = imap_parse_reply(stream,net_getline(stream->transStream))) { if (!strcmp (reply->tag,"+")) return reply; else if (!strcmp (reply->tag,"*")) { imap_parse_unsolicited (stream,reply); if (!tag) return reply; /* return if just wanted greeting */ } else // tagged data { if (tag && !strcmp (tag,reply->tag)) return reply; sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s", reply->tag,reply->key,reply->text); mm_log (LOCAL->tmp,WARN); } } } return imap_fake(stream,tag,"IMAP connection broken (server response)"); } /* IMAP check for OK response in tagged reply * Accepts: MAIL stream * parsed reply * Returns: T if OK else NIL */ long imap_OK(MAILSTREAM *stream, IMAPPARSEDREPLY *reply) { // OK - operation succeeded if (!strcmp (reply->key, "OK") || (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH"))) return T; // NO - operation failed else if (strcmp (reply->key,"NO")) { // BAD - operation rejected if (!strcmp (reply->key,"BAD")) sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text); else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",reply->key,reply->text); mm_log (LOCAL->tmp,WARN); /* log the sucker */ } else { // we received a NO response. See if we got an ALERT response along with it and handle it -jdboyd if (!strncmp (reply->text,"[ALERT]", 7)) mm_alert(stream, reply->text); } return NIL; } /* IMAP parse reply * Accepts: MAIL stream * text of reply * Returns: parsed reply, or NIL if can't parse at least a tag and key */ IMAPPARSEDREPLY *imap_parse_reply(MAILSTREAM *stream, char *text) { if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); if (!(LOCAL->reply.line = text)) // NIL text means the stream died { // Don't close the connection to the server anymore. We'll reuse it later. //if (stream->transStream) net_close (stream->transStream); //stream->transStream = NIL; return NIL; } if (stream->debug) mm_dlog (LOCAL->reply.line); LOCAL->reply.key = NIL; // init fields in case error LOCAL->reply.text = NIL; LOCAL->reply.fake = false; if (!(LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," "))) { mm_log ("IMAP server sent a blank line",WARN); return NIL; } // non-continuation replies if (strcmp (LOCAL->reply.tag,"+")) // parse key { if (!(LOCAL->reply.key = (char *) strtok (NIL," "))) //determine what is missing { sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag); mm_log (LOCAL->tmp,WARN); // pass up the barfage return NIL; // can't parse this text */ } ucase (LOCAL->reply.key); // make sure key is upper case if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n"))) //get text as well, allow empty text LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key); } else // special handling of continuation { LOCAL->reply.key = "BAD"; // so it barfs if not expecting continuation if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n"))) LOCAL->reply.text = "Ready for more command"; } return (&LOCAL->reply); } /* IMAP send command * Accepts: MAIL stream * command * argument list * Returns: parsed reply */ #define MAXSEQUENCE 1000 IMAPPARSEDREPLY *imap_send(MAILSTREAM *stream, char *cmd, IMAPARG *args[]) { IMAPPARSEDREPLY *reply; IMAPARG *arg,**arglst; STRINGLIST *list; char c,*s,*t,tag[16]; #ifdef DEBUG // For debugging (JDB) char *tmp_buf = LOCAL->tmp; #endif if (!stream->transStream) return imap_fake (stream,tag,"No-op dead stream"); //No longer needed. Already locked at this point. //mail_lock (stream); // lock up the stream sprintf (tag,"A%05ld",stream->gensym++); // gensym a new tag for (s = LOCAL->tmp,t = tag; *t; *s++ = *t++); *s++ = ' '; // delimit and write command for (t = cmd; *t; *s++ = *t++); if (arglst = args) while (arg = *arglst++) { *s++ = ' '; // delimit argument with space switch (arg->type) { case ATOM: // atom for (t = (char *) arg->text; *t; *s++ = *t++); break; case NUMBER: // number sprintf (s,"%lu",(unsigned long) arg->text); s += strlen (s); break; case FLAGS: // flag list as a single string if (*(t = (char *) arg->text) != '(') { *s++ = '('; // wrap parens around string while (*t) *s++ = *t++; *s++ = ')'; // wrap parens around string } else while (*t) *s++ = *t++; break; case ASTRING: // atom or string, must be literal? if (reply = imap_send_astring(stream,tag,&s,arg->text,(unsigned long) strlen (arg->text),NULL)) return reply; break; case LITERAL: // literal, as a stringstruct if (reply = imap_send_literal(stream,tag,&s,arg->text)) return reply; break; case LIST: // list of strings list = (STRINGLIST *) arg->text; c = '('; do { *s++ = c; // write prefix character if (reply = imap_send_astring (stream,tag,&s,list->text.data, list->text.size,NULL)) return reply; c = ' '; // prefix character for subsequent strings } while (list = list->next); *s++ = ')'; // close list break; case SEARCHPROGRAM: // search program if (reply = imap_send_spgm(stream,tag,&s,arg->text)) return reply; break; case BODYTEXT: /* body section */ // JOK - Now put "(" around BODY fetches (11/7/97). // Some servers expect () even with one fetch argument. for (t = "(BODY["; *t; *s++ = *t++); for (t = (char *) arg->text; *t; *s++ = *t++); break; case BODYPEEK: /* body section */ // JOK - Now put "(" around BODY fetches (11/7/97). for (t = "(BODY.PEEK["; *t; *s++ = *t++); for (t = (char *) arg->text; *t; *s++ = *t++); break; case BODYCLOSE: /* close bracket and possible length */ s[-1] = ']'; /* no leading space */ for (t = (char *) arg->text; *t; *s++ = *t++); // JOK - Now put ")" around BODY fetches (11/7/97). for (t = ")"; *t; *s++ = *t++); break; case SEQUENCE: // sequence // JOK - Don't recurse - send the complete sequence // falls through case LISTMAILBOX: // astring with wildcards if (reply = imap_send_astring (stream,tag,&s, (char *)arg->text, (unsigned long) strlen(arg->text),T)) { return reply; } break; default: fatal ("Unknown argument type in imap_send()!"); } } // send the command reply = imap_sout(stream,tag,LOCAL->tmp,&s); //no longer //mail_unlock(stream); // unlock stream return reply; } /* IMAP send buffered command to sender * Accepts: MAIL stream * reply tag * string * pointer to string tail pointer * Returns: reply */ IMAPPARSEDREPLY *imap_sout(MAILSTREAM *stream, char *tag, char *base, char **s) { IMAPPARSEDREPLY *reply; if (stream->debug) // output debugging telemetry { **s = '\0'; mm_dlog (base); } *(*s)++ = '\015'; // append CRLF *(*s)++ = '\012'; **s = '\0'; // send the command, parse the reply unless we're doing a fast logout if (net_sout(stream->transStream,base,*s - base) && !(stream->fastLogout)) reply = imap_reply (stream,tag); else reply = imap_fake (stream,tag,"IMAP connection broken (command)"); *s = base; /* restart buffer */ stream->fastLogout = false; // reset this just in case return reply; } /* IMAP parse and act upon unsolicited reply * Accepts: MAIL stream * parsed reply */ //Modified by JDB void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply) { unsigned long i = 0; unsigned long msgno; char *s,*t; char *keyptr,*txtptr; // Must have a stream, and a reply if (!stream || !reply) return; // create a NEW CurrentElt in the stream if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); stream->CurrentElt = mail_elt (stream); // see if key is a number msgno = strtoul (reply->key,&s,10); // if non-numeric if (*s) { if (!strcmp (reply->key,"FLAGS")) { // flush old user flags if any while ((i < NUSERFLAGS) && stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i++]); i = 0; // add flags if (s = (char *) strtok (reply->text+1," )")) do if (*s != '\\') stream->user_flags[i++] = cpystr (s); while (s = (char *) strtok (NIL," )")); } else if (!strcmp (reply->key,"SEARCH")) { // only do something if have text if (reply->text && (t = (char *) strtok (reply->text," "))) do { i = atol (t); if (!LOCAL->uidsearch) mail_elt (stream)->searched = T; mm_searched (stream,i); } while (t = (char *) strtok (NIL," ")); } else if (!strcmp (reply->key,"STATUS")) { MAILSTATUS status; char *txt; switch (*reply->text) // mailbox is an astring { case '"': // quoted string? case '{': // literal? txt = reply->text; // status data is in reply t = imap_parse_string (stream,&txt,reply,NIL,NIL); break; default: // must be atom t = cpystr (reply->text); if (txt = strchr (t,' ')) *txt++ = '\0'; break; } // JDB 060899, txt seems to point to " (", extra space after STATUS if (*txt == ' ') txt++; if (t && txt && (*txt++ == '(') && (s = strchr (txt,')')) && (s - txt) && !s[1]) { char *scan = nil; *s = '\0'; // tie off status data // initialize data block status.flags = status.messages = status.recent = status.unseen = status.uidnext = status.uidvalidity = 0; ucase (txt); // do case-independent match while (*txt && (s = strchr (txt,' '))) { *s++ = '\0'; // tie off status attribute name i = strtoul (s,&s,10);// get attribute value if (!strcmp (txt,"MESSAGES")) { status.flags |= SA_MESSAGES; status.messages = i; } else if (!strcmp (txt,"RECENT")) { status.flags |= SA_RECENT; status.recent = i; } else if (!strcmp (txt,"UNSEEN")) { status.flags |= SA_UNSEEN; status.unseen = i; } else if (!strcmp (txt,"UIDNEXT") || !strcmp (txt,"UID-NEXT")) { status.flags |= SA_UIDNEXT; status.uidnext = i; } else if (!strcmp (txt,"UIDVALIDITY")|| !strcmp (txt,"UID-VALIDITY")) { status.flags |= SA_UIDVALIDITY; status.uidvalidity = i; } // next attribute txt = (*s == ' ') ? s + 1 : s; } // was: // strcpy (strchr (strcpy (LOCAL->tmp,stream->mailbox),'}') + 1,t); strcpy (LOCAL->tmp,stream->mailbox); scan = strchr(LOCAL->tmp, '}'); if (scan) strcpy (scan+1,t); // pass status to main program mm_status (stream,LOCAL->tmp,&status); } fs_give ((void **) &t); } else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) && (*reply->text == '(') && (s = strchr (reply->text,')')) && (s[1] == ' ')) { char delimiter = '\0'; *s++ = '\0'; // tie off attribute list // parse attribute list if (t = (char *) strtok (reply->text+1," ")) do { if (!strcmp (ucase (t),"\\NOINFERIORS")) i |= LATT_NOINFERIORS; else if (!strcmp (t,"\\NOSELECT")) i |= LATT_NOSELECT; else if (!strcmp (t,"\\MARKED")) i |= LATT_MARKED; else if (!strcmp (t,"\\UNMARKED")) i |= LATT_UNMARKED; else if (LOCAL->use_children && !strcmp (t,"\\HASNOCHILDREN")) i |= LATT_HASNOCHILDREN; // ignore extension flags } while (t = (char *) strtok (NIL," ")); switch (*++s) // process delimiter { case 'N': // NIL case 'n': s += 4; // skip over NIL break; case '"': // have a delimiter delimiter = (*++s == '\\') ? *++s : *s; s += 3; // skip over } // need to prepend a prefix? if (LOCAL->prefix) strcpy (LOCAL->tmp,LOCAL->prefix); else LOCAL->tmp[0] = '\0';// no prefix needed // need to do string parse? if ((*s == '"') || (*s == '{')) { strcat (LOCAL->tmp,t = imap_parse_string (stream,&s,reply,NIL,NIL)); fs_give ((void **) &t); } else strcat(LOCAL->tmp,s); // atom is easy if (reply->key[1] == 'S') mm_lsub (stream,delimiter,LOCAL->tmp,i); else mm_list (stream,delimiter,LOCAL->tmp,i); } else if (!strcmp (reply->key,"MAILBOX")) { if (LOCAL->prefix) sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,reply->text); else t = reply->text; mm_list (stream,NIL,t,NIL); } else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) { if ((*reply->text == '[') && (t = strchr (s = reply->text + 1,']')) && ((i = t - s) < IMAPTMPLEN)) { // get text code strncpy (LOCAL->tmp,s,(size_t) i); LOCAL->tmp[i] = '\0'; // tie off text if (!strcmp (ucase (LOCAL->tmp),"READ-ONLY")) stream->rdonly = T; else if (!strcmp (LOCAL->tmp,"READ-WRITE")) stream->rdonly = NIL; else if (!strncmp (LOCAL->tmp,"UIDVALIDITY ",12)) { stream->uid_validity = strtoul (LOCAL->tmp+12,NIL,10); return; } else if (!strncmp (LOCAL->tmp,"PERMANENTFLAGS (",16)) { if (LOCAL->tmp[i-1] == ')') LOCAL->tmp[i-1] = '\0'; stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL; stream->perm_user_flags = NIL; if (s = strtok (LOCAL->tmp+16," ")) do { if (!strcmp (s,"\\SEEN")) stream->perm_seen = T; else if (!strcmp (s,"\\DELETED")) stream->perm_deleted = T; else if (!strcmp (s,"\\FLAGGED")) stream->perm_flagged = T; else if (!strcmp (s,"\\ANSWERED")) stream->perm_answered = T; else if (!strcmp (s,"\\DRAFT")) stream->perm_draft = T; else if (!strcmp (s,"\\*")) stream->kwd_create = T; else stream->perm_user_flags |= imap_parse_user_flag (stream,s); } while (s = strtok (NIL," ")); return; } else if (!strncmp (LOCAL->tmp,"ALERT", 5)) // see if this is an [ALERT] -jdboyd mm_alert(stream, reply->text); } mm_notify (stream,reply->text,(long) NIL); } else if (!strcmp (reply->key,"NO")) { if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd mm_alert(stream, reply->text); if (!stream->silent) mm_notify (stream,reply->text,WARN); } else if (!strcmp (reply->key,"BYE")) { if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd mm_alert(stream, reply->text); LOCAL->byeseen = T; // note that a BYE seen if (!stream->silent) mm_notify (stream,reply->text,BYE); } else if (!strcmp (reply->key,"BAD")) { if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd mm_alert(stream, reply->text); mm_notify (stream,reply->text,IMAP_ERROR); } else if (!strcmp (reply->key,"CAPABILITY")) { // only do something if have text if (reply->text && (t = (char *) strtok (ucase (reply->text)," "))) do { if (!strcmp (t,"IMAP4")) LOCAL->imap4 = T; else if (!strcmp (t,"IMAP4REV1")) LOCAL->imap4rev1 = T; else if (!strcmp (t,"SCAN")) LOCAL->use_scan = T; else if (!strcmp (t,"STARTTLS")) LOCAL->use_tls = T; else if (!strncmp (t,"AUTH",4) && ((t[4] == '=') || (t[4] == '-')) && (i = mail_lookup_auth_name(t+5)) && (--i < MAXAUTHENTICATORS)) LOCAL->use_auth |= (1 << i); else if (!strcmp (t,"CHILDREN")) LOCAL->use_children = T; else if (!strcmp (t,"UIDPLUS")) { LOCAL->uidplus = T; // Trust servers that advertise UIDPLUS do enough of it to support SpamWatch. IMAPSpamWatchSupported(true, true); } // unsupported IMAP4 extension else if (!strcmp (t,"STATUS")) LOCAL->use_status = T; // ignore other capabilities } while (t = (char *) strtok (NIL," ")); } else { sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key); mm_log (LOCAL->tmp,WARN); } } 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 // change in size of mailbox if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno); else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno); else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno); else if (!strcmp (keyptr,"FETCH")) { imap_parse_data (stream,msgno,txtptr,reply); // take care of minimal headers. if (stream->chunkHeaders && stream->headerUID) SaveMinimalHeader(stream); } else if (!strcmp (keyptr,"STORE")) imap_parse_data (stream,msgno,txtptr,reply); else if (strcmp (keyptr,"COPY")) { sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,keyptr); mm_log (LOCAL->tmp,WARN); } } } /* * imap_connected - see if we're connected * * Accepts: MAIL stream * Returns: true if connected, NULL if not. */ Boolean imap_connected (MAILSTREAM *stream) { if (stream && LOCAL && stream->transStream && !TransError(stream->transStream) && stream->bConnected && !CommandPeriod) return true; else return NULL; } /* IMAP fetch RFC822.SIZE * Accepts: MAIL stream * message uid * Returns: size, or 0 if error. */ // NOTE: Added by JOK. // FUNCTION // Fetch the RFC822.SIZE of the message. // Return size, or 0 if error. // END FUNCTION unsigned long imap_rfc822size (MAILSTREAM *stream, unsigned long msgno, long flags) { IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; char seq[MAILTMPLEN]; unsigned long rfc822size = 0; MESSAGECACHE *elt = NULL; // Cannot have a zero msgno. if (msgno <= 0) return msgno; // Initialize: rfc822size = 0; // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); stream->CurrentElt = NULL; // Allocate a new one. elt = mail_elt (stream); if (elt) { if (flags & FT_UID) elt->privat.uid == msgno; else elt->msgno = msgno; } else return 0; // Setup for IMAP call. sprintf (seq, "%lu", msgno); aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = (void *) "RFC822.SIZE"; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; /* send "FETCH msgno UID" */ if (!imap_OK (stream,reply = imap_send (stream, cmd, args))) { if (reply) mm_log (reply->text,WARN); return 0; } // Did we get anything? if (stream->CurrentElt) { // Make sure the uid's matched. if (flags & FT_UID) { if (stream->CurrentElt->privat.uid == msgno) rfc822size = stream->CurrentElt->rfc822_size; } else { if (stream->CurrentElt->msgno == msgno) rfc822size = stream->CurrentElt->rfc822_size; } // Now free the elt. mail_free_elt (&stream->CurrentElt); } return rfc822size; } /* IMAP fetch envelope. */ // NOTE: // JOK - This is a new driver member. It fetches just the message envelope. // The parsing routine allocates an ENVELOPE structure and attaches it to the // stream's "current elt". We check for the envelope, detaches it from the stream and passes it to the caller. // The caller MUST free the structure when done with it. // END NOTE /* Accepts: MAIL stream * message # to fetch * option flags * Returns: Returns an allocated ENVELOPE structure. * */ ENVELOPE *imap_envelope (MAILSTREAM *stream,unsigned long msgno, long flags) { char seq[128],tmp[MAILTMPLEN]; ENVELOPE *env; IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; MESSAGECACHE *elt = NULL; // Cannot have a zero msgno. if (msgno <= 0) return NULL; // Initialize: "env" is what's returned. env = NULL; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = NIL; // IF there is a CurrentElt in the stream, delete it. if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt); // Allocate a new one. elt = mail_elt (stream); if (elt) { if (flags & FT_UID) elt->privat.uid = msgno; else elt->msgno = msgno; } // NOTE: "msgno" can be a UID or a message sequence number. sprintf (seq,"%lu",msgno); /* initial sequence (UID or msgno) */ // Format command based on server capability. // NOTE: Can't handle any IMAP version older than imap2bis!! if (LEVELIMAP4 (stream) && (flags & FT_UID)) { sprintf (tmp,"(UID ENVELOPE)"); aatt.text = (void *) tmp; /* do the built command */ if (!imap_OK (stream, reply = imap_send (stream,"UID FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } } else if (LEVELIMAP2bis (stream)) { /* has non-extensive body and no UID. */ sprintf (tmp,"(BODY)"); aatt.text = (void *) tmp; /* do the built command */ if (!imap_OK (stream, reply = imap_send (stream,"FETCH",args))) { if (reply) mm_log (reply->text,IMAP_ERROR); } } // "env" is what's returned. env = NULL; // Did we get anything? if (stream->CurrentElt) { // Make sure the UID's or msgno's matched. if (flags & FT_UID) { if (stream->CurrentElt->privat.uid == msgno) env = stream->CurrentElt->privat.msg.env; } else { if (stream->CurrentElt->msgno == msgno) env = stream->CurrentElt->privat.msg.env; } // Make sure we detach the body pointer if there was one. // If it's not our env, we'd want to delete it. if (env) stream->CurrentElt->privat.msg.env = NULL; // Now free the elt. mail_free_elt (&stream->CurrentElt); } return env; } /************************************************************************ * StoreUIDPLUSResponses - Store the UIDPLUS responses given in a reply. * Currently works for COPYUID only. ************************************************************************/ OSErr StoreUIDPLUSResponses(MAILSTREAM *stream, IMAPPARSEDREPLY *reply) { OSErr err = noErr; char *s,*e; // Does this reply contain COPYUID responses? if (!strncmp(ucase (reply->text),"[COPYUID",8)) { // There are COPYUID reponses available IMAPSpamWatchSupported(true, true); // RFC 2359 // First number in the COPYUID response in the UIDVALIDITY of the destination mailbox s = reply->text + 9; e = strchr(s, ' '); if (e) { // make sure the UIDVALIDITY of the destination mailbox hasn't changed on us. // if it has, we'll need to discard the responses we've received so far. They're invalid. VerifyUIDValidity(stream, s, e-s); // next block is the range of messages transferred e++; if (*e) { e = strchr(e, ' '); if (e) { // Last block is the range of new UIDs s = ++e; e = strchr(s, ']'); if (e) { UIDStringToUIDs(s, e-s, &(stream->UIDPLUSResponse)); } } } } } else { // UID COPY was executed, but no COPYUID repsonse was detected. IMAPSpamWatchSupported(false, true); } return (err); } /************************************************************************ * VerifyUIDValidity - zap the UIDPlus responses up to now if we * find the UIDVALIDITY of the mailbox has changed on us. ************************************************************************/ void VerifyUIDValidity(MAILSTREAM *stream, char *pUids, int len) { Str255 scratch; int newUV; MakePStr(scratch, pUids, MIN(len, sizeof(scratch))); StringToNum(scratch, &newUV); // first time? if (stream->UIDPLUSuv == 0) stream->UIDPLUSuv = newUV; else if (stream->UIDPLUSuv != newUV) { // Zap Resposes so far, store new uidvalidity AccuZap(stream->UIDPLUSResponse); AccuInit(&stream->UIDPLUSResponse); stream->UIDPLUSuv = newUV; } // else // they match } /************************************************************************ * UIDStringToUIDs - given a UID set, add the individual UIDs to the * accumulator passed in. ************************************************************************/ OSErr UIDStringToUIDs(char *pUids, int len, Accumulator *pAccu) { OSErr err = noErr; long start, stop; char *pEnd, *pUidEnd = pUids + len; Str255 scratch; // only handles x and x:y type sets at this point pEnd = strchr(pUids, ':'); if (pEnd) { MakePStr(scratch, pUids, pEnd-pUids); StringToNum(scratch, &start); pUids = pEnd + 1; } MakePStr(scratch, pUids, pUidEnd - pUids); StringToNum(scratch, &stop); // no range was specified if (!pEnd) start = stop; for (;(err == noErr) && (start <= stop); start++) err = AccuAddPtr(pAccu, &start, sizeof(long)); return (err); }