/* * Copyright 2016-2020 Eduardo Chappa * * Last Edited: Jan 26, 2020 Eduardo Chappa * */ /* ======================================================================== * Copyright 2008-2011 Mark Crispin * ======================================================================== */ /* * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines * * Author: Mark Crispin * * Date: 15 June 1988 * Last Edited: 3 October 2011 * * Previous versions of this file were * * Copyright 1988-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * This original version of this file is * Copyright 1988 Stanford University * and 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. */ #include #include #include #include "c-client.h" #include "imap4r1.h" /* Parameters */ #define IMAPLOOKAHEAD 20 /* envelope lookahead */ #define IMAPUIDLOOKAHEAD 1000 /* UID lookahead */ #define IMAPTCPPORT (long) 143 /* assigned TCP contact port */ #define IMAPSSLPORT (long) 993 /* assigned SSL TCP contact port */ #define MAXCOMMAND 1000 /* RFC 2683 guideline for cmd line length */ #define IDLETIMEOUT (long) 30 /* defined in RFC 3501 */ #define MAXSERVERLIT 0x7ffffffe /* maximum server literal size * must be smaller than 4294967295 */ /* Parsed reply message from imap_reply */ typedef struct imap_parsed_reply { unsigned char *line; /* original reply string pointer */ unsigned char *tag; /* command tag this reply is for */ unsigned char *key; /* reply keyword */ unsigned char *text; /* subsequent text */ } IMAPPARSEDREPLY; #define IMAPTMPLEN 16*MAILTMPLEN /* IMAP4 I/O stream local data */ typedef struct imap_local { NETSTREAM *netstream; /* TCP I/O stream */ IMAPPARSEDREPLY reply; /* last parsed reply */ MAILSTATUS *stat; /* status to fill in */ IMAPCAP cap; /* server capabilities */ char *appendmailbox; /* mailbox being appended to */ unsigned int uidsearch : 1; /* UID searching */ unsigned int byeseen : 1; /* saw a BYE response */ /* got implicit capabilities */ unsigned int gotcapability : 1; unsigned int setid : 1; /* set id of app */ unsigned int sensitive : 1; /* sensitive data in progress */ unsigned int tlsflag : 1; /* TLS session */ unsigned int tlssslv23 : 1; /* TLS using SSLv23 client method */ unsigned int notlsflag : 1; /* TLS not used in session */ unsigned int sslflag : 1; /* SSL session */ unsigned int tls1 : 1; /* using TLSv1 over SSL */ unsigned int tls1_1 : 1; /* using TLSv1_1 over SSL */ unsigned int tls1_2 : 1; /* using TLSv1_2 over SSL */ unsigned int tls1_3 : 1; /* using TLSv1_3 over SSL */ unsigned int novalidate : 1; /* certificate not validated */ unsigned int filter : 1; /* filter SEARCH/SORT/THREAD results */ unsigned int loser : 1; /* server is a loser */ unsigned int saslcancel : 1; /* SASL cancelled by protocol */ long authflags; /* required flags for authenticators */ unsigned long sortsize; /* sort return data size */ unsigned long *sortdata; /* sort return data */ struct { unsigned long uid; /* last UID returned */ unsigned long msgno; /* last msgno returned */ } lastuid; NAMESPACE **namespace; /* namespace return data */ THREADNODE *threaddata; /* thread return data */ char *referral; /* last referral */ char *prefix; /* find prefix */ char *user; /* logged-in user */ char *reform; /* reformed sequence */ char tmp[IMAPTMPLEN]; /* temporary buffer */ SEARCHSET *lookahead; /* fetch lookahead */ IDLIST *id; /* id of stream */ } IMAPLOCAL; /* Convenient access to local data */ #define LOCAL ((IMAPLOCAL *) stream->local) /* Arguments to imap_send() */ typedef struct imap_argument { int type; /* argument type */ void *text; /* argument text */ } IMAPARG; /* imap_send() argument types */ #define ATOM 0 #define NUMBER 1 #define FLAGS 2 #define ASTRING 3 #define LITERAL 4 #define LIST 5 #define SEARCHPROGRAM 6 #define SORTPROGRAM 7 #define BODYTEXT 8 #define BODYPEEK 9 #define BODYCLOSE 10 #define SEQUENCE 11 #define LISTMAILBOX 12 #define MULTIAPPEND 13 #define SNLIST 14 #define MULTIAPPENDREDO 15 /* Append data */ typedef struct append_data { append_t af; void *data; char *flags; char *date; STRING *message; } APPENDDATA; /* Function prototypes */ DRIVER *imap_valid (char *name); void *imap_parameters (long function,void *value); void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); void imap_list (MAILSTREAM *stream,char *ref,char *pat); void imap_lsub (MAILSTREAM *stream,char *ref,char *pat); void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat, char *contents); long imap_subscribe (MAILSTREAM *stream,char *mailbox); long imap_unsubscribe (MAILSTREAM *stream,char *mailbox); long imap_create (MAILSTREAM *stream,char *mailbox); long imap_delete (MAILSTREAM *stream,char *mailbox); long imap_rename (MAILSTREAM *stream,char *old,char *newname); long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2); long imap_status (MAILSTREAM *stream,char *mbx,long flags); MAILSTREAM *imap_open (MAILSTREAM *stream); IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb, char *usr,char *tmp); long imap_anon (MAILSTREAM *stream,char *tmp); long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr); long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr); void *imap_challenge (void *stream,unsigned long *len); long imap_response (void *stream,char *base,char *s,unsigned long size); void imap_close (MAILSTREAM *stream,long options); void imap_fast (MAILSTREAM *stream,char *sequence,long flags); void imap_flags (MAILSTREAM *stream,char *sequence,long flags); long imap_overview (MAILSTREAM *stream,overview_t ofn); ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, long flags); long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section, unsigned long first,unsigned long last,STRINGLIST *lines, long flags); unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno); unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid); void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); long imap_search_x_gm_ext1 (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, SORTPGM *pgm,long flags); THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset, SEARCHPGM *spg,long flags); THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset, SEARCHPGM *spg,long flags); long imap_ping (MAILSTREAM *stream); void imap_check (MAILSTREAM *stream); long imap_expunge (MAILSTREAM *stream,char *sequence,long options); long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data, char *flags,char *date,STRING *message, APPENDDATA *map,long options); IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox, char *flags,char *date,STRING *message); void imap_gc (MAILSTREAM *stream,long gcflags); void imap_gc_body (BODY *body); void imap_capability (MAILSTREAM *stream); long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]); IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]); IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s); long imap_soutr (MAILSTREAM *stream,char *string); IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s, SIZEDTEXT *as,long wildok,char *limit); IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s, STRING *st); IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base, char **s,SEARCHPGM *pgm,char *limit); char *imap_send_spgm_trim (char *base,char *s,char *text); IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base, char **s,SEARCHSET *set,char *prefix, char *limit); IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base, char **s,char *name,STRINGLIST *list, char *limit); void imap_send_sdate (char **s,char *name,unsigned short date); IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag); IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text); IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text); long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply); void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply); void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy); NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr); void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr, STRINGLIST *stl); void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env, unsigned char **txtptr,IMAPPARSEDREPLY *reply); ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt, unsigned char **txtptr); unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag); unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply,unsigned long *len); unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply,GETS_DATA *md, unsigned long *len,long flags); void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr, IMAPPARSEDREPLY *reply); void imap_parse_body_structure (MAILSTREAM *stream,BODY *body, unsigned char **txtptr,IMAPPARSEDREPLY *reply); PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream, unsigned char **txtptr, IMAPPARSEDREPLY *reply); void imap_parse_disposition (MAILSTREAM *stream,BODY *body, unsigned char **txtptr,IMAPPARSEDREPLY *reply); STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply); void imap_parse_capabilities (MAILSTREAM *stream,char *t); IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags); char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags); long imap_setid(MAILSTREAM *stream, IDLIST *idlist); IDLIST *imap_parse_idlist(char *text); /* Driver dispatch used by MAIL */ DRIVER imapdriver = { "imap", /* driver name */ /* driver flags */ DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN, (DRIVER *) NIL, /* 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 */ imap_overview, /* fetch overview */ imap_structure, /* fetch message envelopes */ NIL, /* fetch message header */ NIL, /* fetch message body */ imap_msgdata, /* fetch partial message */ imap_uid, /* unique identifier */ imap_msgno, /* message number */ imap_flag, /* modify flags */ NIL, /* per-message modify flags */ imap_search, /* search for message based on criteria */ imap_sort, /* sort messages */ imap_thread, /* 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 */ }; /* prototype stream */ MAILSTREAM imapproto = {&imapdriver}; /* driver parameters */ static unsigned long imap_maxlogintrials = MAXLOGINTRIALS; static long imap_lookahead = IMAPLOOKAHEAD; static long imap_uidlookahead = IMAPUIDLOOKAHEAD; static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD; static long imap_defaultport = 0; static long imap_sslport = 0; static long imap_tryssl = NIL; static long imap_prefetch = IMAPLOOKAHEAD; static long imap_closeonerror = NIL; static imapenvelope_t imap_envelope = NIL; static imapreferral_t imap_referral = NIL; static char *imap_extrahdrs = NIL; /* constants */ static char *hdrheader[] = { "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location", "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location", "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location", "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location", "BODY.PEEK[HEADER.FIELDS (Newsgroups" }; static char *hdrtrailer ="Followup-To References)]"; /* IMAP validate mailbox * Accepts: mailbox name * Returns: our driver if name is valid, NIL otherwise */ DRIVER *imap_valid (char *name) { return mail_valid_net (name,&imapdriver,NIL,NIL); } /* 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 GET_NAMESPACE: if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace && !((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace) imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL); value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace; break; case GET_THREADERS: value = (void *) ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader; break; case SET_FETCHLOOKAHEAD: /* must use pointer from GET_FETCHLOOKAHEAD */ fatal ("SET_FETCHLOOKAHEAD not permitted"); case GET_FETCHLOOKAHEAD: value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead; break; 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_SSLIMAPPORT: imap_sslport = (long) value; break; case GET_SSLIMAPPORT: value = (void *) imap_sslport; 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; case SET_IMAPENVELOPE: imap_envelope = (imapenvelope_t) value; break; case GET_IMAPENVELOPE: value = (void *) imap_envelope; break; case SET_IMAPREFERRAL: imap_referral = (imapreferral_t) value; break; case GET_IMAPREFERRAL: value = (void *) imap_referral; break; case SET_IMAPEXTRAHEADERS: imap_extrahdrs = (char *) value; break; case GET_IMAPEXTRAHEADERS: value = (void *) imap_extrahdrs; break; case SET_IMAPTRYSSL: imap_tryssl = (long) value; break; case GET_IMAPTRYSSL: value = (void *) imap_tryssl; break; case SET_FETCHLOOKAHEADLIMIT: imap_fetchlookaheadlimit = (long) value; break; case GET_FETCHLOOKAHEADLIMIT: value = (void *) imap_fetchlookaheadlimit; break; case SET_IDLETIMEOUT: fatal ("SET_IDLETIMEOUT not permitted"); case GET_IDLETIMEOUT: value = (void *) IDLETIMEOUT; break; case SET_IDSTREAM: /* set IMAP server ID */ fatal ("SET_IDSTREAM not permitted"); case GET_IDSTREAM: /* get IMAP server ID */ value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->id; break; default: value = NIL; /* 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,"SCAN",ref,pat,contents); } /* IMAP list mailboxes * Accepts: mail stream * reference * pattern to search */ void imap_list (MAILSTREAM *stream,char *ref,char *pat) { imap_list_work (stream,"LIST",ref,pat,NIL); } /* IMAP list subscribed mailboxes * Accepts: mail stream * reference * pattern to search */ void imap_lsub (MAILSTREAM *stream,char *ref,char *pat) { void *sdb = NIL; char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN]; /* do it on the server */ imap_list_work (stream,"LSUB",ref,pat,NIL); if (*pat == '{') { /* if remote pattern, must be IMAP */ if (!imap_valid (pat)) return; ref = NIL; /* good IMAP pattern, punt reference */ } /* if remote reference, must be valid IMAP */ if (ref && (*ref == '{') && !imap_valid (ref)) return; /* kludgy application of reference */ if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); else strcpy (mbx,pat); if ((s = sm_read (tmp,&sdb)) != NULL) do if (imap_valid (s) && pmatch (s,mbx)) mm_lsub (stream,NIL,s,NIL); /* until no more subscriptions */ while ((s = sm_read (tmp,&sdb)) != NULL); } /* IMAP find list of mailboxes * Accepts: mail stream * list command * reference * pattern to search * string to scan */ void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat, char *contents) { MAILSTREAM *st = stream; int pl; char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN]; IMAPARG *args[4],aref,apat,acont; if (ref && *ref) { /* have a reference? */ if (!(imap_valid (ref) && /* make sure valid IMAP name and open stream */ ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return; /* calculate prefix length */ pl = strchr (ref,'}') + 1 - ref; strncpy (prefix,ref,pl); /* build prefix */ prefix[pl] = '\0'; /* tie off prefix */ ref += pl; /* update reference */ } else { if (!(imap_valid (pat) && /* make sure valid IMAP name and open stream */ ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return; /* calculate prefix length */ pl = strchr (pat,'}') + 1 - pat; strncpy (prefix,pat,pl); /* build prefix */ prefix[pl] = '\0'; /* tie off prefix */ pat += pl; /* update reference */ } LOCAL->prefix = prefix; /* note prefix */ if (contents) { /* want to do a scan? */ if (LEVELSCAN (stream)) { /* make sure permitted */ 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,cmd,args); } else mm_log ("Scan not valid on this IMAP server",ERROR); } 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; /* referrals armed? */ if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) { /* yes, convert LIST -> RLIST */ if (!compare_cstring (cmd,"LIST")) cmd = "RLIST"; /* and convert LSUB -> RLSUB */ else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB"; } imap_send (stream,cmd,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 (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */ strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) && !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD")) LOCAL->cap.rfc1176 = NIL; /* must be RFC-1064 */ } LOCAL->prefix = NIL; /* no more prefix */ /* close temporary stream if we made one */ if (stream != st) mail_close (stream); } /* IMAP subscribe to mailbox * Accepts: mail stream * mailbox to add to subscription list * Returns: T on success, NIL on failure */ long imap_subscribe (MAILSTREAM *stream,char *mailbox) { MAILSTREAM *st = stream; long ret = ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ? imap_manage (stream,mailbox,LEVELIMAP4 (stream) ? "Subscribe" : "Subscribe Mailbox",NIL) : NIL; /* toss out temporary stream */ if (st != stream) mail_close (stream); return ret; } /* IMAP unsubscribe to mailbox * Accepts: mail stream * mailbox to delete from manage list * Returns: T on success, NIL on failure */ long imap_unsubscribe (MAILSTREAM *stream,char *mailbox) { MAILSTREAM *st = stream; long ret = ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ? imap_manage (stream,mailbox,LEVELIMAP4 (stream) ? "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL; /* toss out temporary stream */ if (st != stream) mail_close (stream); return ret; } /* IMAP create mailbox * Accepts: mail stream * mailbox name to create * Returns: T on success, NIL on failure */ long imap_create (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Create",NIL); } /* IMAP delete mailbox * Accepts: mail stream * mailbox name to delete * Returns: T on success, NIL on failure */ long imap_delete (MAILSTREAM *stream,char *mailbox) { return imap_manage (stream,mailbox,"Delete",NIL); } /* IMAP rename mailbox * Accepts: mail stream * old mailbox name * new mailbox name * Returns: T on success, NIL 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, NIL on failure */ long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2) { MAILSTREAM *st = stream; IMAPPARSEDREPLY *reply; long ret = NIL; char mbx[MAILTMPLEN],mbx2[MAILTMPLEN]; IMAPARG *args[3],ambx,amb2; imapreferral_t ir = (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx; amb2.text = (void *) mbx2; args[0] = &ambx; args[1] = args[2] = NIL; /* require valid names and open stream */ if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) && (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) && ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) { if (arg2) args[1] = &amb2; /* second arg present? */ if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) && ir && LOCAL->referral) { long code = -1; switch (*command) { /* which command was it? */ case 'S': code = REFSUBSCRIBE; break; case 'U': code = REFUNSUBSCRIBE; break; case 'C': code = REFCREATE; break; case 'D': code = REFDELETE; break; case 'R': code = REFRENAME; break; default: fatal ("impossible referral command"); } if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code))) ret = imap_manage (NIL,mailbox,command,(*command == 'R') ? (mailbox + strlen (mailbox) + 1) : NIL); } mm_log (reply->text,ret ? NIL : ERROR); /* toss out temporary stream */ if (st != stream) mail_close (stream); } return ret; } /* IMAP status * Accepts: mail stream * mailbox name * status flags * Returns: T on success, NIL on failure */ long imap_status (MAILSTREAM *stream,char *mbx,long flags) { IMAPARG *args[3],ambx,aflg; char tmp[MAILTMPLEN]; NETMBX mb; unsigned long i; long ret = NIL; MAILSTREAM *tstream = NIL; /* use given stream if (rev1 or halfopen) and right host */ if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) && mail_usable_network_stream (stream,mbx)) || (stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT)))) return NIL; /* parse mailbox name */ mail_valid_net_parse (mbx,&mb); args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */ ambx.type = ASTRING; ambx.text = (void *) mb.mailbox; if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */ imapreferral_t ir; 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," UIDNEXT"); if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY"); tmp[0] = '('; strcat (tmp,")"); /* send "STATUS mailbox flag" */ if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T; else if ((ir = (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL)) && LOCAL->referral && (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS))) ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL)); } /* IMAP2 way */ else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) { MAILSTATUS status; status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY); status.messages = stream->nmsgs; status.recent = stream->recent; status.unseen = 0; if (flags & SA_UNSEEN) { /* must search to get unseen messages */ /* clear search vector */ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL; if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL))) for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->searched) status.unseen++; } strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox); /* pass status to main program */ mm_status (stream,tmp,&status); ret = T; /* note success */ } if (tstream) mail_close (tstream); return ret; /* success */ } /* IMAP open * Accepts: stream to open * Returns: stream to use on success, NIL on failure */ MAILSTREAM *imap_open (MAILSTREAM *stream) { unsigned long i,j; char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN]; NETMBX mb; IMAPPARSEDREPLY *reply = NIL; imapreferral_t ir = (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); /* return prototype for OP_PROTOTYPE call */ if (!stream) return &imapproto; mail_valid_net_parse (stream->mailbox,&mb); usr[0] = '\0'; /* initially no user name */ if (LOCAL) { /* if stream opened earlier by us */ /* recycle if still alive */ if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) { i = stream->silent; /* temporarily mark silent */ stream->silent = T; /* don't give mm_exists() events */ j = imap_ping (stream); /* learn if stream still alive */ stream->silent = i; /* restore prior state */ if (j) { /* was stream still alive? */ sprintf (tmp,"Reusing connection to %s",net_host (LOCAL->netstream)); if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"", LOCAL->user); if (!stream->silent) mm_log (tmp,(long) NIL); /* unselect if now want halfopen */ if (stream->halfopen) imap_send (stream,"UNSELECT",NIL); } else imap_close (stream,NIL); } else imap_close (stream,NIL); } /* copy flags from name */ if (mb.dbgflag) stream->debug = T; if (mb.readonlyflag) stream->rdonly = T; if (mb.anoflag) stream->anonymous = T; if (mb.secflag) stream->secure = T; if (mb.trysslflag || imap_tryssl) stream->tryssl = T; if (!LOCAL) { /* open new connection if no recycle */ NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL); unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT; unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT; stream->local = /* instantiate localdata */ (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL)); /* assume IMAP2bis server */ LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; /* in case server is a loser */ if (mb.loser) LOCAL->loser = T; /* desirable authenticators */ LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) | (mb.authuser[0] ? AU_AUTHUSER : NIL); /* IMAP connection open logic is more complex than net_open() normally * deals with, because of the simap and rimap hacks. * If the session is anonymous, a specific port is given, or if /ssl or * /tls is set, do net_open() since those conditions override everything * else. */ if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag) reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps", sslport)) ? imap_reply (stream,NIL) : NIL; /* * No overriding conditions, so get the best connection that we can. In * order, attempt to open via simap, tryssl, rimap, and finally TCP. */ /* try simap */ else if ((reply = imap_rimap (stream,"*imap",&mb,usr,tmp)) != NULL); else if (ssld && /* try tryssl if enabled */ (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) && (LOCAL->netstream = net_open_work (ssld,mb.host,"*imaps",sslport,mb.port, (mb.novalidate ? NET_NOVALIDATECERT : 0) | NET_SILENT | NET_TRYSSL))) { if (net_sout (LOCAL->netstream,"",0)) { mb.sslflag = T; reply = imap_reply (stream,NIL); } else { /* flush fake SSL stream */ net_close (LOCAL->netstream); LOCAL->netstream = NIL; } } /* try rimap first, then TCP */ else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) && (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL))) reply = imap_reply (stream,NIL); /* make sure greeting is good */ if (!reply || strcmp (reply->tag,"*") || (strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) { if (reply) mm_log (reply->text,ERROR); return NIL; /* lost during greeting */ } /* if connected and not preauthenticated */ if (LOCAL->netstream && strcmp (reply->key,"PREAUTH")) { sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL); /* get server capabilities */ if (!LOCAL->gotcapability) imap_capability (stream); if (LOCAL->netstream && /* does server support STARTTLS? */ stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag && imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) { mb.tlsflag = T; /* TLS OK, get into TLS at this end */ LOCAL->netstream->dtb = ssld; if (!(LOCAL->netstream->stream = (*stls) (LOCAL->netstream->stream,mb.host, SSL_MTHD(mb) | (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) { /* drat, drop this connection */ if (LOCAL->netstream) net_close (LOCAL->netstream); LOCAL->netstream = NIL; } /* get capabilities now that TLS in effect */ if (LOCAL->netstream) imap_capability (stream); } else if (mb.tlsflag) { /* user specified /tls but can't do it */ mm_log ("Unable to negotiate TLS with this server",ERROR); return NIL; } if (LOCAL->netstream) { /* still in the land of the living? */ if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { /* remote name for authentication */ strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)? net_remotehost (LOCAL->netstream) : net_host (LOCAL->netstream),NETMAXHOST-1); mb.host[NETMAXHOST-1] = '\0'; } /* need new capabilities after login */ LOCAL->gotcapability = NIL; if (!(stream->anonymous ? imap_anon (stream,tmp) : (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) : imap_login (stream,&mb,tmp,usr)))) { /* failed, is there a referral? */ if (mb.tlsflag) LOCAL->tlsflag = T; if (ir && LOCAL->referral && (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) { imap_close (stream,NIL); fs_give ((void **) &stream->mailbox); /* set as new mailbox name to open */ stream->mailbox = s; return imap_open (stream); } return NIL; /* authentication failed */ } else if (ir && LOCAL->referral && (s = (*ir) (stream,LOCAL->referral,REFAUTH))) { imap_close (stream,NIL); fs_give ((void **) &stream->mailbox); stream->mailbox = s; /* set as new mailbox name to open */ /* recurse to log in on real site */ return imap_open (stream); } } } /* get server capabilities again */ if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream); /* save state for future recycling */ if (mb.tlsflag) LOCAL->tlsflag = T; if (mb.tls1) LOCAL->tls1 = T; if (mb.tls1_1) LOCAL->tls1_1 = T; if (mb.tls1_2) LOCAL->tls1_2 = T; if (mb.tls1_3) LOCAL->tls1_3 = T; if (mb.tlssslv23) LOCAL->tlssslv23 = T; if (mb.notlsflag) LOCAL->notlsflag = T; if (mb.sslflag) LOCAL->sslflag = T; if (mb.novalidate) LOCAL->novalidate = T; if (mb.loser) LOCAL->loser = T; } if (LOCAL->netstream) { /* still have a connection? */ stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T; stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff; stream->sequence++; /* bump sequence number */ sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? net_host (LOCAL->netstream) : mb.host); if (!((i = net_port (LOCAL->netstream)) & 0xffff0000)) sprintf (tmp + strlen (tmp),":%lu",i); strcat (tmp,"/imap"); if (LOCAL->tlsflag) strcat (tmp,"/tls"); if (LOCAL->tls1) strcat (tmp,"/tls1"); if (LOCAL->tls1_1) strcat (tmp,"/tls1_1"); if (LOCAL->tls1_2) strcat (tmp,"/tls1_2"); if (LOCAL->tls1_3) strcat (tmp,"/tls1_3"); if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23"); if (LOCAL->notlsflag) strcat (tmp,"/notls"); if (LOCAL->sslflag) strcat (tmp,"/ssl"); if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert"); if (LOCAL->loser) strcat (tmp,"/loser"); if (stream->secure) strcat (tmp,"/secure"); if (stream->rdonly) strcat (tmp,"/readonly"); if (stream->anonymous) strcat (tmp,"/anonymous"); else { /* record user name */ if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr); if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"", LOCAL->user); } strcat (tmp,"}"); if(LEVELID(stream)){ /* Set ID of app */ IDLIST *idapp = (IDLIST *) mail_parameters(NIL, GET_IDPARAMS, NIL); if(idapp && !LOCAL->setid){ imap_setid(stream, idapp); LOCAL->setid++; } } 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] = NIL; stream->nmsgs = 0; if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ? "EXAMINE": "SELECT",args))) { strcat (tmp,mb.mailbox);/* mailbox name */ if (!stream->nmsgs && !stream->silent) mm_log ("Mailbox is empty",(long) NIL); /* note if an INBOX or not */ stream->inbox = !compare_cstring (mb.mailbox,"INBOX"); } else if (ir && LOCAL->referral && (s = (*ir) (stream,LOCAL->referral,REFSELECT))) { imap_close (stream,NIL); fs_give ((void **) &stream->mailbox); stream->mailbox = s; /* set as new mailbox name to open */ return imap_open (stream); } else { mm_log (reply->text,ERROR); if (imap_closeonerror) return NIL; stream->halfopen = T; /* let him keep it half-open */ } } if (stream->halfopen) { /* half-open connection? */ 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 LOCAL->netstream ? stream : NIL; } /* IMAP rimap connect * Accepts: MAIL stream * NETMBX specification * service to use * user name * scratch buffer * Returns: parsed reply if success, else NIL */ IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb, char *usr,char *tmp) { unsigned long i; char c[2]; NETSTREAM *tstream; IMAPPARSEDREPLY *reply = NIL; /* try rimap open */ if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) { /* if success, see if reasonable banner */ if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) { i = 0; /* copy to buffer */ do tmp[i++] = *c; while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') && (*c != '\012') && (i < (MAILTMPLEN-1))); tmp[i] = '\0'; /* tie off */ /* snarfed a valid greeting? */ if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) && (*c == '\012') && !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){ /* parse line as IMAP */ imap_parse_unsolicited (stream,reply); /* make sure greeting is good */ if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) { LOCAL->netstream = tstream; return reply; /* return success */ } } } net_close (tstream); /* failed, punt the temporary netstream */ } return NIL; } /* IMAP log in as anonymous * Accepts: stream to authenticate * scratch buffer * Returns: T on success, NIL on failure */ long imap_anon (MAILSTREAM *stream,char *tmp) { IMAPPARSEDREPLY *reply; char *s = net_localhost (LOCAL->netstream); if (LOCAL->cap.authanon) { char tag[16]; unsigned long i; char *broken = "[CLOSED] IMAP connection broken (anonymous auth)"; sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); /* build command */ sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag); if (!imap_soutr (stream,tmp)) { mm_log (broken,ERROR); return NIL; } if (imap_challenge (stream,&i)) imap_response (stream,NIL,s,strlen (s)); /* get response */ if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken); /* what we wanted? */ if (compare_cstring (reply->tag,tag)) { /* abort if don't have tagged response */ while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag)) imap_soutr (stream,"*"); } } else { IMAPARG *args[2]; IMAPARG ausr; ausr.type = ASTRING; ausr.text = (void *) s; args[0] = &ausr; args[1] = NIL; /* send "LOGIN anonymous " */ reply = imap_send (stream,"LOGIN ANONYMOUS",args); } /* success if reply OK */ if (imap_OK (stream,reply)) return T; mm_log (reply->text,ERROR); return NIL; } /* IMAP authenticate * Accepts: stream to authenticate * parsed network mailbox structure * scratch buffer * place to return user name * Returns: T on success, NIL on failure */ long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) { unsigned long trial,ua,uasaved; int ok; char tag[16]; char *lsterr = NIL, *base; AUTHENTICATOR *at, *atsaved; IMAPPARSEDREPLY *reply; for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua && (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) { if(mb && *mb->auth){ if(!compare_cstring(at->name, mb->auth)) atsaved = at; else{ uasaved = ua; continue; } } if (lsterr) { /* previous authenticator failed? */ sprintf (tmp,"Retrying using %s authentication after %.80s", at->name,lsterr); mm_log (tmp,NIL); fs_give ((void **) &lsterr); } trial = 0; /* initial trial count */ tmp[0] = '\0'; /* no error */ do { /* gensym a new tag */ if (lsterr) { /* previous attempt with this one failed? */ sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr); mm_log (tmp,WARN); fs_give ((void **) &lsterr); } LOCAL->saslcancel = NIL; sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); /* build command */ sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name); base = (at->flags & AU_SINGLE) && LOCAL->cap.sasl_ir ? (char *) tmp : NIL; if (base || imap_soutr (stream,tmp)) { /* report we tried this authenticator */ if(base && stream && stream->debug) mm_dlog (base); /* hide client authentication responses */ if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T; ok = (*at->client) (imap_challenge,imap_response,base,"imap",mb,stream, net_port(LOCAL->netstream),&trial,usr); LOCAL->sensitive = NIL; /* unhide */ /* make sure have a response */ if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag, "[CLOSED] IMAP connection broken (authenticate)"); else if (compare_cstring (reply->tag,tag)) while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag)) imap_soutr (stream,"*"); /* good if SASL ok and success response */ if (ok && imap_OK (stream,reply)) return T; if (!trial) { /* if main program requested cancellation */ mm_log ("IMAP Authentication cancelled",ERROR); return NIL; } /* no error if protocol-initiated cancel */ lsterr = cpystr (reply->text); } } while (LOCAL->netstream && !LOCAL->byeseen && trial && (trial < imap_maxlogintrials)); } if (lsterr) { /* previous authenticator failed? */ if (!LOCAL->saslcancel) { /* don't do this if a cancel */ sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr); mm_log (tmp,ERROR); } fs_give ((void **) &lsterr); } if(mb && *mb->auth){ if(!uasaved) sprintf (tmp,"Client does not support AUTH=%.80s authenticator",mb->auth); else if (!atsaved) sprintf (tmp,"IMAP server does not support AUTH=%.80s authenticator",mb->auth); if (!uasaved || !atsaved) mm_log (tmp,ERROR); } return NIL; /* ran out of authenticators */ } /* IMAP login * Accepts: stream to login * parsed network mailbox structure * scratch buffer of length MAILTMPLEN * place to return user name * Returns: T on success, NIL on failure */ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) { unsigned long trial = 0; IMAPPARSEDREPLY *reply; IMAPARG *args[3]; IMAPARG ausr,apwd; long ret = NIL; char *app_pwd = NIL; if (stream->secure) /* never do LOGIN if want security */ mm_log ("Can't do secure authentication with this server",ERROR); /* never do LOGIN if server disabled it */ else if (LOCAL->cap.logindisabled) mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR); else if (mb->authuser[0]) /* never do LOGIN with /authuser */ mm_log ("Can't do /authuser with this server",ERROR); else { /* OK to try login */ ausr.type = apwd.type = ASTRING; ausr.text = (void *) usr; apwd.text = (void *) pwd; args[0] = &ausr; args[1] = &apwd; args[2] = NIL; do { if(app_pwd) fs_give((void **) &app_pwd); pwd[0] = '\0'; mm_login (mb,usr, &app_pwd,trial++); if(app_pwd){ strncpy(pwd, app_pwd, MAILTMPLEN); pwd[MAILTMPLEN-1] = '\0'; } if (pwd[0]) { /* send login command if have password */ LOCAL->sensitive = T; /* hide this command */ /* send "LOGIN usr pwd" */ if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args))) ret = LONGT; /* success */ else { mm_log (reply->text,WARN); if (!LOCAL->referral && (trial == imap_maxlogintrials)) mm_log ("Too many login failures",ERROR); } LOCAL->sensitive = NIL; /* unhide */ } /* user refused to give password */ else mm_log ("Login aborted",ERROR); } while (!ret && pwd[0] && (trial < imap_maxlogintrials) && LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral); } if(app_pwd) fs_give((void **) &app_pwd); memset((void *) pwd, 0, MAILTMPLEN); return ret; } /* Get challenge to authenticator in binary * Accepts: stream * pointer to returned size * Returns: challenge or NIL if not challenge */ void *imap_challenge (void *s,unsigned long *len) { char tmp[MAILTMPLEN]; void *ret = NIL; MAILSTREAM *stream = (MAILSTREAM *) s; IMAPPARSEDREPLY *reply = NIL; /* get tagged response or challenge */ while (stream && LOCAL->netstream && (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) && !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply); /* parse challenge if have one */ if (stream && LOCAL->netstream && reply && reply->tag && (*reply->tag == '+') && !reply->tag[1] && reply->text && !(ret = rfc822_base64 ((unsigned char *) reply->text, strlen (reply->text),len))) { sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s", (char *) reply->text); mm_log (tmp,ERROR); } return ret; } /* Send authenticator response in BASE64 * Accepts: MAIL stream * string to send * length of string * Returns: T if successful, else NIL */ long imap_response (void *s,char *base,char *response,unsigned long size) { MAILSTREAM *stream = (MAILSTREAM *) s; unsigned long i,j,ret; char *t,*u; if (response) { /* make CRLFless BASE64 string */ if (size) { if(base){ char *s, *v; v = (char *) rfc822_binary ((void *) response,size,&i); t = fs_get((strlen(base) + strlen(v) + 1 + 2)*sizeof(char)); for(s = base, u = t; *s; s++) *u++ = *s; *u++ = ' '; for (s = v,j = 0; j < i; j++) if (s[j] > ' ') *u++ = s[j]; fs_give((void **) &v); } else { 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) mail_dlog (t,LOCAL->sensitive); /* append CRLF */ *u++ = '\015'; *u++ = '\012'; ret = net_sout (LOCAL->netstream,t,u - t); fs_give ((void **) &t); } else ret = imap_soutr (stream,""); } else { /* abort requested */ ret = base ? NIL : imap_soutr (stream,"*"); LOCAL->saslcancel = T; /* mark protocol-requested SASL cancel */ } return ret; } /* IMAP close * Accepts: MAIL stream * option flags */ void imap_close (MAILSTREAM *stream,long options) { THREADER *thr,*t; 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,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL); if (LOCAL->netstream && !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL))) mm_log (reply->text,WARN); } /* close NET connection if still open */ if (LOCAL->netstream) net_close (LOCAL->netstream); LOCAL->netstream = NIL; /* free up memory */ if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata); if (LOCAL->namespace) { mail_free_namespace (&LOCAL->namespace[0]); mail_free_namespace (&LOCAL->namespace[1]); mail_free_namespace (&LOCAL->namespace[2]); fs_give ((void **) &LOCAL->namespace); } if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata); /* flush threaders */ if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) { fs_give ((void **) &t->name); thr = t->next; fs_give ((void **) &t); } if (LOCAL->referral) fs_give ((void **) &LOCAL->referral); if (LOCAL->user) fs_give ((void **) &LOCAL->user); if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); if (LOCAL->reform) fs_give ((void **) &LOCAL->reform); if (LOCAL->id) mail_free_idlist(&LOCAL->id); /* nuke the local data */ fs_give ((void **) &stream->local); } } /* IMAP fetch fast information * Accepts: MAIL stream * sequence * option flags * * Generally, imap_structure is preferred */ void imap_fast (MAILSTREAM *stream,char *sequence,long flags) { IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID); if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR); } /* IMAP fetch flags * Accepts: MAIL stream * sequence * option flags */ void imap_flags (MAILSTREAM *stream,char *sequence,long flags) { /* send "FETCH sequence FLAGS" */ char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,aatt; if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, flags & FT_UID); aseq.type = SEQUENCE; aseq.text = (void *) sequence; aatt.type = ATOM; aatt.text = (void *) "FLAGS"; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) mm_log (reply->text,ERROR); } /* IMAP fetch overview * Accepts: MAIL stream, sequence bits set * pointer to overview return function * Returns: T if successful, NIL otherwise */ long imap_overview (MAILSTREAM *stream,overview_t ofn) { MESSAGECACHE *elt; ENVELOPE *env; OVERVIEW ov; char *s,*t; unsigned long i,start,last,len,slen; if (!LOCAL->netstream) return NIL; /* build overview sequence */ for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i) if ((elt = mail_elt (stream,i))->sequence) { if (!elt->private.msg.env) { if (s) { /* continuing a sequence */ if (i == last + 1) last = i; else { /* end of range */ if (last != start) sprintf (t,":%lu,%lu",last,i); else sprintf (t,",%lu",i); if ((len - (slen = (t += strlen (t)) - s)) < 20) { fs_resize ((void **) &s,len += MAILTMPLEN); t = s + slen; /* relocate current pointer */ } start = last = i; /* begin a new range */ } } else { /* first time, start new buffer */ s = (char *) fs_get (len = MAILTMPLEN); sprintf (s,"%lu",start = last = i); t = s + strlen (s); /* end of buffer */ } } } /* last sequence */ if (last != start) sprintf (t,":%lu",last); if (s) { /* prefetch as needed */ imap_fetch (stream,s,FT_NEEDENV); fs_give ((void **) &s); } ov.optional.lines = 0; /* now overview each message */ ov.optional.xref = NIL; if (ofn) for (i = 1; i <= stream->nmsgs; i++) if (((elt = mail_elt (stream,i))->sequence) && (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) { ov.subject = env->subject; ov.from = env->from; ov.date = env->date; ov.message_id = env->message_id; ov.references = env->references; ov.optional.octets = elt->rfc822_size; (*ofn) (stream,mail_uid (stream,i),&ov,i); } return LONGT; } /* 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 */ ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, long flags) { unsigned long i,j,k,x; char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN]; MESSAGECACHE *elt; ENVELOPE **env; BODY **b; IMAPPARSEDREPLY *reply = NIL; IMAPARG *args[3],aseq,aatt; SEARCHSET *set = LOCAL->lookahead; LOCAL->lookahead = NIL; args[0] = &aseq; args[1] = &aatt; args[2] = NIL; aseq.type = SEQUENCE; aseq.text = (void *) seq; aatt.type = ATOM; aatt.text = NIL; if (flags & FT_UID) /* see if can find msgno from UID */ for (i = 1; i <= stream->nmsgs; i++) if ((elt = mail_elt (stream,i))->private.uid == msgno) { msgno = i; /* found msgno, use it from now on */ flags &= ~FT_UID; /* no longer a UID fetch */ } sprintf (s = seq,"%lu",msgno);/* initial sequence */ if (LEVELIMAP4 (stream) && (flags & FT_UID)) { /* UID fetching is requested and we can't map the UID to a message sequence * number. Assume that the message isn't cached at all. */ if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV + (body ? FT_NEEDBODY : NIL) + (flags & (FT_UID + FT_NOHDRS))))) mm_log (reply->text,ERROR); /* now hunt for this UID */ for (i = 1; i <= stream->nmsgs; i++) if ((elt = mail_elt (stream,i))->private.uid == msgno) { if (body) *body = elt->private.msg.body; return elt->private.msg.env; } if (body) *body = NIL; /* can't find the UID */ return NIL; } elt = mail_elt (stream,msgno);/* get cache pointer */ if (stream->scache) { /* short caching? */ env = &stream->env; /* use temporaries on the stream */ b = &stream->body; if (msgno != stream->msgno){/* flush old poop if a different message */ mail_free_envelope (env); mail_free_body (b); stream->msgno = msgno; /* this is now the current short cache msg */ } } else { /* normal cache */ env = &elt->private.msg.env;/* get envelope and body pointers */ b = &elt->private.msg.body; /* prefetch if don't have envelope */ if (!(flags & FT_NOLOOKAHEAD) && ((!*env || (*env)->incomplete) || (body && !*b && LEVELIMAP2bis (stream)))) { if (set) { /* have a lookahead list? */ MESSAGE *msg; for (k = imap_fetchlookaheadlimit; k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30)); set = set->next) { i = (set->first == 0xffffffff) ? stream->nmsgs : min (set->first,stream->nmsgs); if ((j = (set->last == 0xffffffff) ? stream->nmsgs : min (set->last,stream->nmsgs)) != 0L) { if (i > j) { /* swap the range if backwards */ x = i; i = j; j = x; } /* find first message not msgno or in cache */ while (((i == msgno) || ((msg = &(mail_elt (stream,i)->private.msg))->env && (!body || msg->body))) && (i++ < j)); /* until range or lookahead finished */ while (k && (i <= j)) { /* find first cached message in range */ for (x = i + 1; (x <= j) && !((msg = &(mail_elt (stream,x)->private.msg))->env && (!body || msg->body)); x++); if (i == --x) { /* only one message? */ sprintf (s += strlen (s),",%lu",i++); k--; /* prefetching one message */ } else { /* a range to prefetch */ sprintf (s += strlen (s),",%lu:%lu",i,x); i = 1 + x - i; /* number of messages in this range */ /* still can look ahead some more? */ if ((k = (k > i) ? k - i : 0) != 0) /* yes, scan further in this range */ for (i = x + 2; (i <= j) && ((i == msgno) || ((msg = &(mail_elt (stream,i)->private.msg))->env && (!body || msg->body))); i++); } } } else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) { sprintf (s += strlen (s),",%lu",i); k--; /* prefetching one message */ } } } /* build message number list */ else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++) if (!mail_elt (stream,i)->private.msg.env) { s += strlen (s); /* find string end, see if nearing end */ if ((s - seq) > (MAILTMPLEN - 20)) break; sprintf (s,",%lu",i); /* append message */ for (j = i + 1, k--; /* hunt for last message without an envelope */ k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.msg.env; j++, k--); /* if different, make a range */ if (i != --j) sprintf (s + strlen (s),":%lu",i = j); } } } if (!stream->lock) { /* no-op if stream locked */ /* Build the fetch attributes. Unlike imap_fetch(), this tries not to * fetch data that is already cached. However, since it is based on the * message requested and not on any of the prefetched messages, it can * goof, either by fetching data already cached or not prefetching data * that isn't cached (but was cached in the message requested). * Fortunately, no great harm is done. If it doesn't prefetch the data, * it will get it when the affected message(s) are requested. */ if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID"); else tmp[0] = '\0'; /* initialize command */ /* need envelope? */ if (!*env || (*env)->incomplete) { strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */ if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) { if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s", hdrheader[LOCAL->cap.extlevel], imap_extrahdrs,hdrtrailer); else sprintf (tmp + strlen (tmp)," %s %s", hdrheader[LOCAL->cap.extlevel],hdrtrailer); } } /* need body? */ if (body && !*b && LEVELIMAP2bis (stream)) strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY"); if (!elt->day) strcat (tmp," INTERNALDATE"); if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE"); if (tmp[0]) { /* anything to do? */ tmp[0] = '('; /* make into a list */ strcat (tmp," FLAGS)"); /* always get current flags */ aatt.text = (void *) tmp; /* do the built command */ if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) { /* failed, probably RFC-1176 server */ if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){ aatt.text = (void *) "ALL"; if (imap_OK (stream,reply = imap_send (stream,"FETCH",args))) /* doesn't have body capabilities */ LOCAL->cap.imap2bis = NIL; else mm_log (reply->text,ERROR); } else mm_log (reply->text,ERROR); } } } if (body) { /* wants to return body */ if (!*b && !LEVELIMAP2bis (stream)) { /* simulate body structure fetch for IMAP2 */ *b = mail_initbody (mail_newbody ()); (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type)); ((*b)->parameter = mail_newbody_parameter ())->attribute = cpystr ("CHARSET"); (*b)->parameter->value = cpystr ("US-ASCII"); s = mail_fetch_text (stream,msgno,NIL,&i,flags); (*b)->size.bytes = i; while (i--) if (*s++ == '\n') (*b)->size.lines++; } *body = *b; /* return the body */ } return *env; /* return the envelope */ } /* 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 header * flags * Returns: T on success, NIL on failure */ long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section, unsigned long first,unsigned long last,STRINGLIST *lines, long flags) { int i; char *t,tmp[MAILTMPLEN],partial[40],seq[40]; char *noextend,*nopartial,*nolines,*nopeek,*nononpeek; char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; IMAPPARSEDREPLY *reply; IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg; noextend = nopartial = nolines = nopeek = nononpeek = NIL; /* does searching desire a lookahead? */ if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) && !stream->scache) { sprintf (seq,"%lu:%lu",msgno, (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs)); aseq.type = SEQUENCE; aseq.text = (void *) seq; } else { /* no, do it the easy way */ 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 *) partial; aflg.type = ATOM; aflg.text = (void *) "FLAGS"; args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL; auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL; partial[0] = '\0'; /* initially no partial specifier */ if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */ /* HEADER fetching with special handling? */ if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) { if (lines) { /* want specific header lines? */ aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; aatt.text = (void *) ((flags & FT_NOT) ? "HEADER.FIELDS.NOT" : "HEADER.FIELDS"); args[2] = &alns; args[3] = &acls; } /* must be prefetching */ else aatt.text = (void *) ((flags & FT_PEEK) ? "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" : "(BODY[HEADER] BODY[TEXT])"); } else { /* simple case */ aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; aatt.text = (void *) section; args[2] = &acls; } if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1); } /* IMAP4 did not have: * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0]) * . TEXT body part (can simulate top-level with RFC822.TEXT or * RFC822.TEXT.PEEK) * . MIME body part * . (usable) partial fetching * . (usable) selective header line fetching */ else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */ /* BODY[HEADER] becomes BODY.PEEK[0] */ if (!strcmp (section,"HEADER")) aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ? ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" : "(BODY[0] RFC822.TEXT)") : ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]")); /* BODY[TEXT] becomes RFC822.TEXT */ else if (!strcmp (section,"TEXT")) aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" : "RFC822.TEXT"); else if (!section[0]) /* BODY[] becomes RFC822 */ aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822"); /* nested header */ else if ((t = strstr (section,".HEADER")) != NULL) { aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; args[2] = &acls; /* will need to close section */ aatt.text = (void *) tmp; /* convert .HEADER to .0 */ strncpy (tmp,section,t-section); strcpy (tmp+(t-section),".0"); } else { /* IMAP4 body part */ aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; args[2] = &acls; /* will need to close section */ aatt.text = (void *) section; } if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4"; if (first || last) nopartial = "4"; if (lines) nolines = "4"; } /* IMAP2bis did not have: * . HEADER body part (can simulate peeking top-level with RFC822.HEADER) * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT) * . MIME body part * . partial fetching * . selective header line fetching * . non-peeking header fetching * . peeking body fetching */ /* IMAP2bis compatibility */ else if (LEVELIMAP2bis (stream)) { /* BODY[HEADER] becomes RFC822.HEADER */ if (!strcmp (section,"HEADER")) { aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ? "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER"); if (flags & FT_PEEK) flags &= ~FT_PEEK; else nononpeek = "2bis"; } /* BODY[TEXT] becomes RFC822.TEXT */ else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT"; /* BODY[] becomes RFC822 */ else if (!section[0]) aatt.text = (void *) "RFC822"; else { /* IMAP2bis body part */ aatt.type = BODYTEXT; args[2] = &acls; /* will need to close section */ aatt.text = (void *) section; } if (strstr (section,".HEADER") || strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "2bis"; if (first || last) nopartial = "2bis"; if (lines) nolines = "2bis"; if (flags & FT_PEEK) nopeek = "2bis"; } /* IMAP2 did not have: * . HEADER body part (can simulate peeking top-level with RFC822.HEADER) * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT) * . MIME body part * . multiple body parts (can simulate BODY[1] with RFC822.TEXT) * . partial fetching * . selective header line fetching * . non-peeking header fetching * . peeking body fetching */ else { /* IMAP2 (RFC 1176/1064) compatibility */ /* BODY[HEADER] */ if (!strcmp (section,"HEADER")) { aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ? "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER"); if (flags & FT_PEEK) flags &= ~FT_PEEK; nononpeek = "2"; } /* BODY[TEXT] becomes RFC822.TEXT */ else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT"; /* BODY[1] treated like RFC822.TEXT */ else if (!strcmp (section,"1")) { SIZEDTEXT text; MESSAGECACHE *elt = mail_elt (stream,msgno); /* have a cached RFC822.TEXT? */ if (elt->private.msg.text.text.data) { text.size = elt->private.msg.text.text.size; /* should move instead of copy */ text.data = memcpy (fs_get (text.size+1), elt->private.msg.text.text.data,text.size); (t = (char *) text.data)[text.size] = '\0'; imap_cache (stream,msgno,"1",NIL,&text); return LONGT; /* don't have to do any fetches */ } /* otherwise do RFC822.TEXT */ aatt.text = (void *) "RFC822.TEXT"; } /* BODY[] becomes RFC822 */ else if (!section[0]) aatt.text = (void *) "RFC822"; else noextend = "2"; /* how did we get here? */ if (flags & FT_PEEK) nopeek = "2"; if (first || last) nopartial = "2"; if (lines) nolines = "2"; } /* Report unavailable functionalities. The application can use the helpful * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in * imap4r1.h to avoid triggering these errors. There aren't any workarounds * for these restrictions. */ if (noextend) { sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch", noextend); mm_log (tmp,ERROR); return NIL; /* can't do anything close either */ } if (nopartial) { sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch", nopartial); mm_notify (stream,tmp,WARN); } if (nolines) { sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch", nolines); mm_notify (stream,tmp,WARN); } /* trying to do unsupported peek behavior? */ if ((t = nopeek) || (t = nononpeek)) { /* get most recent \Seen setting */ if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs))) mm_log (reply->text,WARN); /* note current setting of \Seen flag */ if (!(i = mail_elt (stream,msgno)->seen)) { sprintf (tmp,nopeek ? /* only babble if \Seen not set */ "[NOTIMAP4] Simulating peeking fetch in IMAP%s" : "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t); mm_notify (stream,tmp,NIL); } /* send the fetch command */ if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { mm_log (reply->text,ERROR); return NIL; /* failure */ } /* send command if need to reset \Seen */ if (((nopeek && !i && mail_elt (stream,msgno)->seen && (aflg.text = "-FLAGS \\Seen")) || ((nononpeek && !mail_elt (stream,msgno)->seen) && (aflg.text = "+FLAGS \\Seen"))) && !imap_OK (stream,reply = imap_send (stream,"STORE",auxargs))) mm_log (reply->text,WARN); } /* simple case if traditional behavior */ else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { mm_log (reply->text,ERROR); return NIL; /* failure */ } /* simulate BODY[1] return for RFC 1064/1176 */ if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) { SIZEDTEXT text; MESSAGECACHE *elt = mail_elt (stream,msgno); text.size = elt->private.msg.text.text.size; /* should move instead of copy */ text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data, text.size); (t = (char *) text.data)[text.size] = '\0'; imap_cache (stream,msgno,"1",NIL,&text); } return LONGT; } /* IMAP fetch UID * Accepts: MAIL stream * message number * Returns: UID */ unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno) { MESSAGECACHE *elt; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,aatt; char *s,seq[MAILTMPLEN]; unsigned long i,j,k; /* IMAP2 didn't have UIDs */ if (!LEVELIMAP4 (stream)) return msgno; /* do we know its UID yet? */ if (!(elt = mail_elt (stream,msgno))->private.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",msgno); if ((k = imap_uidlookahead) != 0L) {/* build UID list */ for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++) if (!mail_elt (stream,i)->private.uid) { s += strlen (s); /* find string end, see if nearing end */ if ((s - seq) > (MAILTMPLEN - 20)) break; sprintf (s,",%lu",i); /* append message */ for (j = i + 1, k--; /* hunt for last message without a UID */ k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid; j++, k--); /* if different, make a range */ if (i != --j) sprintf (s + strlen (s),":%lu",i = j); } } /* send "FETCH msgno UID" */ if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) mm_log (reply->text,ERROR); } return elt->private.uid; /* return our UID now */ } /* IMAP fetch message number from UID * Accepts: MAIL stream * UID * Returns: message number */ unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid) { IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,aatt; char seq[MAILTMPLEN]; int holes = 0; unsigned long i,msgno; /* IMAP2 didn't have UIDs */ if (!LEVELIMAP4 (stream)) return uid; /* This really should be a binary search, but since there are likely to be * holes in the msgno->UID map it's hard to do. */ for (msgno = 1; msgno <= stream->nmsgs; msgno++) { if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T; else if (i == uid) return msgno; } if (holes) { /* have holes in cache? */ /* yes, have server hunt for UID */ LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0; 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); /* send "UID FETCH uid UID" */ if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args))) mm_log (reply->text,ERROR); if (LOCAL->lastuid.uid) { /* got any results from FETCH? */ if ((LOCAL->lastuid.uid == uid) && /* what, me paranoid? */ (LOCAL->lastuid.msgno <= stream->nmsgs) && (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid)) /* got it the easy way */ return LOCAL->lastuid.msgno; /* sigh, do another linear search... */ for (msgno = 1; msgno <= stream->nmsgs; msgno++) if (mail_elt (stream,msgno)->private.uid == uid) return msgno; } } return 0; /* didn't find the UID anywhere */ } /* IMAP modify flags * Accepts: MAIL stream * sequence * flag(s) * option flags */ void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) { char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE"; IMAPPARSEDREPLY *reply; IMAPARG *args[4],aseq,ascm,aflg; if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, flags & ST_UID); 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] = NIL; /* send "STORE sequence +Flags flag" */ if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) mm_log (reply->text,ERROR); } /* IMAP search for messages * Accepts: MAIL stream * character set * search program * option flags * Returns: T on success, NIL on failure */ long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags) { unsigned long i,j,k; char *s; IMAPPARSEDREPLY *reply; MESSAGECACHE *elt; if(LOCAL->cap.x_gm_ext1 && pgm && pgm->x_gm_ext1) return imap_search_x_gm_ext1(stream, charset, pgm, flags); if ((flags & SE_NOSERVER) || /* if want to do local search */ LOCAL->loser || /* or loser */ (!LEVELIMAP4 (stream) && /* or old server but new functions... */ (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or || pgm->not || pgm->header || pgm->larger || pgm->smaller || pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft || pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to || pgm->message_id || pgm->in_reply_to || pgm->newsgroups || pgm->followup_to || pgm->references)) || (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) { if ((flags & SE_NOLOCAL) || !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) return NIL; } /* do silly ALL or seq-only search locally */ else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) && !(pgm->uid || pgm->or || pgm->not || pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc || pgm->subject || pgm->body || pgm->text || pgm->larger || pgm->smaller || pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->before || pgm->on || pgm->since || pgm->answered || pgm->unanswered || pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft || pgm->flagged || pgm->unflagged || pgm->recent || pgm->old || pgm->seen || pgm->unseen || pgm->keyword || pgm->unkeyword || pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to || pgm->message_id || pgm->newsgroups || pgm->followup_to || pgm->references)) { if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER)) fatal ("impossible mail_search_default() failure"); } else { /* do server-based SEARCH */ char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH"; IMAPARG *args[4],apgm,aatt,achs; SEARCHSET *ss,*set; args[1] = args[2] = args[3] = NIL; apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm; if (charset) { /* optional charset argument requested */ args[0] = &aatt; args[1] = &achs; args[2] = &apgm; aatt.type = ATOM; aatt.text = (void *) "CHARSET"; achs.type = ASTRING; achs.text = (void *) charset; } else args[0] = &apgm; /* no charset argument */ /* tell receiver that these will be UIDs */ LOCAL->uidsearch = (flags & SE_UID) ? T : NIL; reply = imap_send (stream,cmd,args); /* did server barf with that searchpgm? */ if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) && !strcmp (reply->key,"BAD")) { LOCAL->filter = T; /* retry, filtering SEARCH results */ for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->private.filter = NIL; for (set = ss; set; set = set->next) if ((i = set->first) != 0L) { /* single message becomes one-message range */ if (!(j = set->last)) j = i; else if (j < i) { /* swap reversed range */ i = set->last; j = set->first; } while (i <= j) mail_elt (stream,i++)->private.filter = T; } pgm->msgno = NIL; /* and without the searchset */ reply = imap_send (stream,cmd,args); pgm->msgno = ss; /* restore searchset */ LOCAL->filter = NIL; /* turn off filtering */ } LOCAL->uidsearch = NIL; /* do locally if server won't grok */ if (!strcmp (reply->key,"BAD")) { if ((flags & SE_NOLOCAL) || !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) return NIL; } else if (!imap_OK (stream,reply)) { mm_log (reply->text,ERROR); return NIL; } } /* can never pre-fetch with a short cache */ if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) && !stream->scache) { /* only if prefetching permitted */ s = LOCAL->tmp; /* build sequence in temporary buffer */ *s = '\0'; /* initially nothing */ /* search through mailbox */ for (i = 1; k && (i <= stream->nmsgs); ++i) /* for searched messages with no envelope */ if ((elt = mail_elt (stream,i)) && elt->searched && !mail_elt (stream,i)->private.msg.env) { /* prepend with comma if not first time */ if (LOCAL->tmp[0]) *s++ = ','; sprintf (s,"%lu",j = i);/* output message number */ s += strlen (s); /* point at end of string */ k--; /* count one up */ /* search for possible end of range */ while (k && (i < stream->nmsgs) && (elt = mail_elt (stream,i+1))->searched && !elt->private.msg.env) i++,k--; if (i != j) { /* if a range */ sprintf (s,":%lu",i); /* output delimiter and end of range */ s += strlen (s); /* point at end of string */ } if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break; } if (LOCAL->tmp[0]) { /* anything to pre-fetch? */ /* pre-fetch envelopes for the first imap_prefetch number of messages */ if (!imap_OK (stream,reply = imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) + ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL)))) mm_log (reply->text,ERROR); fs_give ((void **) &s); /* flush copy of sequence */ } } return LONGT; } /* IMAP sort messages * Accepts: mail stream * character set * search program * sort program * option flags * Returns: vector of sorted message sequences or NIL if error */ unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, SORTPGM *pgm,long flags) { unsigned long i,j,start,last; unsigned long *ret = NIL; pgm->nmsgs = 0; /* start off with no messages */ /* can use server-based sort? */ if (LEVELSORT (stream) && !(flags & SE_NOSERVER) && (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) { char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT"; IMAPARG *args[4],apgm,achs,aspg; IMAPPARSEDREPLY *reply; SEARCHSET *ss = NIL; SEARCHPGM *tsp = NIL; apgm.type = SORTPROGRAM; apgm.text = (void *) pgm; achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII"); aspg.type = SEARCHPROGRAM; /* did he provide a searchpgm? */ if (!(aspg.text = (void *) spg)) { for (i = 1,start = last = 0; i <= stream->nmsgs; ++i) if (mail_elt (stream,i)->searched) { if (ss) { /* continuing a sequence */ if (i == last + 1) last = i; else { /* end of range */ if (last != start) ss->last = last; (ss = ss->next = mail_newsearchset ())->first = i; start = last = i; /* begin a new range */ } } else { /* first time, start new searchpgm */ (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset (); ss->first = start = last = i; } } /* nothing to sort if no messages */ if (!(aspg.text = (void *) tsp)) return NIL; /* else install last sequence */ if (last != start) ss->last = last; } args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL; /* ask server to do it */ reply = imap_send (stream,cmd,args); if (tsp) { /* was there a temporary searchpgm? */ aspg.text = NIL; /* yes, flush it */ mail_free_searchpgm (&tsp); /* did server barf with that searchpgm? */ if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) { LOCAL->filter = T; /* retry, filtering SORT/THREAD results */ reply = imap_send (stream,cmd,args); LOCAL->filter = NIL; /* turn off filtering */ } } /* do locally if server barfs */ if (!strcmp (reply->key,"BAD")) return (flags & SE_NOLOCAL) ? NIL : imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER); /* server sorted OK? */ else if (imap_OK (stream,reply)) { pgm->nmsgs = LOCAL->sortsize; ret = LOCAL->sortdata; LOCAL->sortdata = NIL; /* mail program is responsible for flushing */ } else mm_log (reply->text,ERROR); } /* not much can do if short caching */ else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags); else { /* try to be a bit more clever */ char *s,*t; unsigned long len; MESSAGECACHE *elt; SORTCACHE **sc; SORTPGM *sp; long ftflags = 0; /* see if need envelopes */ for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) { case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC: ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL); } if (spg) { /* only if a search needs to be done */ int silent = stream->silent; stream->silent = T; /* don't pass up mm_searched() events */ /* search for messages */ mail_search_full (stream,charset,spg,flags & SE_NOSERVER); stream->silent = silent; /* restore silence state */ } /* initialize progress counters */ pgm->nmsgs = pgm->progress.cached = 0; /* pass 1: count messages to sort */ for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i) if ((elt = mail_elt (stream,i))->searched) { pgm->nmsgs++; if (ftflags ? !elt->private.msg.env : !elt->day) { if (s) { /* continuing a sequence */ if (i == last + 1) last = i; else { /* end of range */ if (last != start) sprintf (t,":%lu,%lu",last,i); else sprintf (t,",%lu",i); start = last = i; /* begin a new range */ if ((len - (j = ((t += strlen (t)) - s)) < 20)) { fs_resize ((void **) &s,len += MAILTMPLEN); t = s + j; /* relocate current pointer */ } } } else { /* first time, start new buffer */ s = (char *) fs_get (len = MAILTMPLEN); sprintf (s,"%lu",start = last = i); t = s + strlen (s); /* end of buffer */ } } } /* last sequence */ if (last != start) sprintf (t,":%lu",last); if (s) { /* load cache for all messages being sorted */ imap_fetch (stream,s,ftflags); fs_give ((void **) &s); } if (pgm->nmsgs) { /* pass 2: sort cache */ sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL); sc = mail_sort_loadcache (stream,pgm); /* pass 3: sort messages */ if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags); fs_give ((void **) &sc); /* don't need sort vector any more */ /* also return via callback if requested */ if (sr) (*sr) (stream,ret,pgm->nmsgs); } } return ret; } /* IMAP thread messages * Accepts: mail stream * thread type * character set * search program * option flags * Returns: thread node tree or NIL if error */ THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset, SEARCHPGM *spg,long flags) { THREADER *thr; if (!(flags & SE_NOSERVER) && (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) /* does server have this threader type? */ for (thr = LOCAL->cap.threader; thr; thr = thr->next) if (!compare_cstring (thr->name,type)) return imap_thread_work (stream,type,charset,spg,flags); /* server doesn't support it, do locally */ return (flags & SE_NOLOCAL) ? NIL: mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort); } /* IMAP thread messages worker routine * Accepts: mail stream * thread type * character set * search program * option flags * Returns: thread node tree */ THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset, SEARCHPGM *spg,long flags) { unsigned long i,start,last; char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD"; IMAPARG *args[4],apgm,achs,aspg; IMAPPARSEDREPLY *reply; THREADNODE *ret = NIL; SEARCHSET *ss = NIL; SEARCHPGM *tsp = NIL; apgm.type = ATOM; apgm.text = (void *) type; achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII"); aspg.type = SEARCHPROGRAM; /* did he provide a searchpgm? */ if (!(aspg.text = (void *) spg)) { for (i = 1,start = last = 0; i <= stream->nmsgs; ++i) if (mail_elt (stream,i)->searched) { if (ss) { /* continuing a sequence */ if (i == last + 1) last = i; else { /* end of range */ if (last != start) ss->last = last; (ss = ss->next = mail_newsearchset ())->first = i; start = last =i; /* begin a new range */ } } else { /* first time, start new searchpgm */ (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset (); ss->first = start = last = i; } } /* nothing to sort if no messages */ if (!(aspg.text = (void *) tsp)) return NIL; /* else install last sequence */ if (last != start) ss->last = last; } args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL; /* ask server to do it */ reply = imap_send (stream,cmd,args); if (tsp) { /* was there a temporary searchpgm? */ aspg.text = NIL; /* yes, flush it */ mail_free_searchpgm (&tsp); /* did server barf with that searchpgm? */ if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) { LOCAL->filter = T; /* retry, filtering SORT/THREAD results */ reply = imap_send (stream,cmd,args); LOCAL->filter = NIL; /* turn off filtering */ } } /* do locally if server barfs */ if (!strcmp (reply->key,"BAD")) ret = (flags & SE_NOLOCAL) ? NIL: mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort); /* server threaded OK? */ else if (imap_OK (stream,reply)) { ret = LOCAL->threaddata; LOCAL->threaddata = NIL; /* mail program is responsible for flushing */ } else mm_log (reply->text,ERROR); return ret; } /* IMAP ping mailbox * Accepts: MAIL stream * Returns: T if stream still alive, else NIL */ long imap_ping (MAILSTREAM *stream) { return (LOCAL->netstream && /* send "NOOP" */ imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL; } /* IMAP check mailbox * Accepts: MAIL stream */ void imap_check (MAILSTREAM *stream) { /* send "CHECK" */ IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL); mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR); } /* IMAP expunge mailbox * Accepts: MAIL stream * sequence to expunge if non-NIL * expunge options * Returns: T if success, NIL if failure */ long imap_expunge (MAILSTREAM *stream,char *sequence,long options) { long ret = NIL; IMAPPARSEDREPLY *reply = NIL; if (sequence) { /* wants selective expunging? */ if (options & EX_UID) { /* UID EXPUNGE form? */ if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */ IMAPARG *args[2],aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence; args[0] = &aseq; args[1] = NIL; ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args)); } else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR); } /* otherwise try to make into UID EXPUNGE */ else if (mail_sequence (stream,sequence)) { unsigned long i,j; char *t = (char *) fs_get (IMAPTMPLEN); char *s = t; /* search through mailbox */ for (*s = '\0', i = 1; i <= stream->nmsgs; ++i) if (mail_elt (stream,i)->sequence) { if (t[0]) *s++ = ','; /* prepend with comma if not first time */ sprintf (s,"%lu",mail_uid (stream,j = i)); s += strlen (s); /* point at end of string */ /* search for possible end of range */ while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++; if (i != j) { /* output end of range */ sprintf (s,":%lu",mail_uid (stream,i)); s += strlen (s); /* point at end of string */ } if ((s - t) > (IMAPTMPLEN - 50)) { mm_log ("Excessively complex sequence",ERROR); return NIL; } } /* now do as UID EXPUNGE */ ret = imap_expunge (stream,t,EX_UID); fs_give ((void **) &t); } } /* ordinary EXPUNGE */ else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL)); if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR); return ret; } /* IMAP copy message(s) * Accepts: MAIL stream * sequence * destination mailbox * option flags * Returns: T if successful else NIL */ long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags) { char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY"; char *s; long ret = NIL; IMAPPARSEDREPLY *reply; IMAPARG *args[3],aseq,ambx; imapreferral_t ir = (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); mailproxycopy_t pc = (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, flags & CP_UID); aseq.type = SEQUENCE; aseq.text = (void *) sequence; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[0] = &aseq; args[1] = &ambx; args[2] = NIL; /* note mailbox in case APPENDUID */ LOCAL->appendmailbox = mailbox; /* send "COPY sequence mailbox" */ ret = imap_OK (stream,reply = imap_send (stream,cmd,args)); LOCAL->appendmailbox = NIL; /* no longer appending */ if (ret) { /* success, delete messages if move */ if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted", ST_SET + ((flags&CP_UID) ? ST_UID : NIL)); } /* failed, do referral action if any */ else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) && (s = (*ir) (stream,LOCAL->referral,REFCOPY))) ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL)); /* otherwise issue error message */ else mm_log (reply->text,ERROR); return ret; } /* IMAP mail append message from stringstruct * Accepts: MAIL stream * destination mailbox * append callback * data for callback * Returns: T if append successful, else NIL */ long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) { MAILSTREAM *st = stream; IMAPARG *args[3],ambx,amap; IMAPPARSEDREPLY *reply = NIL; APPENDDATA map; char tmp[MAILTMPLEN]; long debug = stream ? stream->debug : NIL; long ret = NIL; imapreferral_t ir = (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); /* mailbox must be good */ if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) { /* create a stream if given one no good */ if ((stream && LOCAL && LOCAL->netstream) || (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT | (debug ? OP_DEBUG : NIL)))) { /* note mailbox in case APPENDUID */ LOCAL->appendmailbox = mailbox; /* use multi-append? */ if (LEVELMULTIAPPEND (stream)) { ambx.type = ASTRING; ambx.text = (void *) tmp; amap.type = MULTIAPPEND; amap.text = (void *) ↦ map.af = af; map.data = data; args[0] = &ambx; args[1] = &amap; args[2] = NIL; /* success if OK */ ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args)); LOCAL->appendmailbox = NIL; } /* do succession of single appends */ else while ((*af) (stream,data,&map.flags,&map.date,&map.message) && map.message && (ret = imap_OK (stream,reply = imap_append_single (stream,tmp,map.flags, map.date,map.message)))); LOCAL->appendmailbox = NIL; /* don't do referrals if success or no reply */ if (ret || !reply) mailbox = NIL; /* otherwise generate referral */ else if (!(mailbox = (ir && LOCAL->referral) ? (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL)) mm_log (reply->text,ERROR); /* close temporary stream */ if (st != stream) stream = mail_close (stream); if (mailbox) /* chase referral if any */ ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date, map.message,&map,debug); } else mm_log ("Can't access server for append",ERROR); } return ret; /* return */ } /* IMAP mail append message referral retry * Accepts: destination mailbox * temporary buffer * append callback * data for callback * flags from previous attempt * date from previous attempt * message stringstruct from previous attempt * options (currently non-zero to set OP_DEBUG) * Returns: T if append successful, else NIL */ long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data, char *flags,char *date,STRING *message, APPENDDATA *map,long options) { MAILSTREAM *stream; IMAPARG *args[3],ambx,amap; IMAPPARSEDREPLY *reply; imapreferral_t ir = (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL); /* barf if bad mailbox */ while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) { /* create a stream if given one no good */ if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT | (options ? OP_DEBUG : NIL)))) { sprintf (tmp,"Can't access referral server: %.80s",mailbox); mm_log (tmp,ERROR); return NIL; } /* got referral server, use multi-append? */ if (LEVELMULTIAPPEND (stream)) { ambx.type = ASTRING; ambx.text = (void *) tmp; amap.type = MULTIAPPENDREDO; amap.text = (void *) map; args[0] = &ambx; args[1] = &amap; args[2] = NIL; /* do multiappend on referral site */ if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) { mail_close (stream); /* multiappend OK, close stream */ return LONGT; /* all done */ } } /* do multiple single appends */ else while (imap_OK (stream,reply = imap_append_single (stream,tmp,flags,date,message))) if (!((*af) (stream,data,&flags,&date,&message) && message)) { mail_close (stream); /* last message, close stream */ return LONGT; /* all done */ } /* generate error if no nested referral */ if (!(mailbox = (ir && LOCAL->referral) ? (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL)) mm_log (reply->text,ERROR); mail_close (stream); /* close previous referral stream */ } return NIL; /* bogus mailbox */ } /* IMAP append single message * Accepts: mail stream * destination mailbox * initial flags * internal date * stringstruct of message to append * Returns: reply from append */ IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox, char *flags,char *date,STRING *message) { MESSAGECACHE elt; IMAPARG *args[5],ambx,aflg,adat,amsg; IMAPPARSEDREPLY *reply; char tmp[MAILTMPLEN]; int i; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[i = 0] = &ambx; if (flags) { aflg.type = FLAGS; aflg.text = (void *) flags; args[++i] = &aflg; } if (date) { /* ensure date in INTERNALDATE format */ if (!mail_parse_date (&elt,date)) { /* flush previous reply */ if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); /* build new fake reply */ LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*"); LOCAL->reply.key = "BAD"; LOCAL->reply.text = "Bad date in append"; return &LOCAL->reply; } adat.type = ASTRING; adat.text = (void *) (date = mail_date (tmp,&elt)); args[++i] = &adat; } amsg.type = LITERAL; amsg.text = (void *) message; args[++i] = &amsg; args[++i] = NIL; /* easy if IMAP4[rev1] */ if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args); else { /* try the IMAP2bis way */ args[1] = &amsg; args[2] = NIL; reply = imap_send (stream,"APPEND",args); } return reply; } /* 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 (NIL,GET_CACHE,NIL); /* 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)) != NULL) imap_gc_body (elt->private.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 (BODY *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); /* MESSAGE/RFC822? */ else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"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->header.text.data) fs_give ((void **) &body->nested.msg->header.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->header.text.size = body->nested.msg->text.text.size = 0; } } } /* IMAP get capabilities * Accepts: mail stream */ void imap_capability (MAILSTREAM *stream) { THREADER *thr,*t; LOCAL->gotcapability = NIL; /* flush any previous capabilities */ /* request new capabilities */ imap_send (stream,"CAPABILITY",NIL); if (!LOCAL->gotcapability) { /* did server get any? */ /* no, flush threaders just in case */ if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) { fs_give ((void **) &t->name); thr = t->next; fs_give ((void **) &t); } /* zap most capabilities */ memset (&LOCAL->cap,0,sizeof (LOCAL->cap)); /* assume IMAP2bis server if failure */ LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; } } /* IMAP set ACL * Accepts: mail stream * mailbox name * authentication identifier * new access rights * Returns: T on success, NIL on failure */ long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights) { IMAPARG *args[4],ambx,aid,art; ambx.type = aid.type = art.type = ASTRING; ambx.text = (void *) mailbox; aid.text = (void *) id; art.text = (void *) rights; args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL; return imap_acl_work (stream,"SETACL",args); } /* IMAP delete ACL * Accepts: mail stream * mailbox name * authentication identifier * Returns: T on success, NIL on failure */ long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id) { IMAPARG *args[3],ambx,aid; ambx.type = aid.type = ASTRING; ambx.text = (void *) mailbox; aid.text = (void *) id; args[0] = &ambx; args[1] = &aid; args[2] = NIL; return imap_acl_work (stream,"DELETEACL",args); } /* IMAP get ACL * Accepts: mail stream * mailbox name * Returns: T on success with data returned via callback, NIL on failure */ long imap_getacl (MAILSTREAM *stream,char *mailbox) { IMAPARG *args[2],ambx; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[0] = &ambx; args[1] = NIL; return imap_acl_work (stream,"GETACL",args); } /* IMAP list rights * Accepts: mail stream * mailbox name * authentication identifier * Returns: T on success with data returned via callback, NIL on failure */ long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id) { IMAPARG *args[3],ambx,aid; ambx.type = aid.type = ASTRING; ambx.text = (void *) mailbox; aid.text = (void *) id; args[0] = &ambx; args[1] = &aid; args[2] = NIL; return imap_acl_work (stream,"LISTRIGHTS",args); } /* IMAP my rights * Accepts: mail stream * mailbox name * Returns: T on success with data returned via callback, NIL on failure */ long imap_myrights (MAILSTREAM *stream,char *mailbox) { IMAPARG *args[2],ambx; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[0] = &ambx; args[1] = NIL; return imap_acl_work (stream,"MYRIGHTS",args); } /* IMAP ACL worker routine * Accepts: mail stream * command * command arguments * Returns: T on success, NIL on failure */ long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]) { long ret = NIL; if (LEVELACL (stream)) { /* send command */ IMAPPARSEDREPLY *reply; if (imap_OK (stream,reply = imap_send (stream,command,args))) ret = LONGT; else mm_log (reply->text,ERROR); } else mm_log ("ACL not available on this IMAP server",ERROR); return ret; } /* IMAP set quota * Accepts: mail stream * quota root name * resource limit list as a stringlist * Returns: T on success with data returned via callback, NIL on failure */ long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits) { long ret = NIL; if (LEVELQUOTA (stream)) { /* send "SETQUOTA" */ IMAPPARSEDREPLY *reply; IMAPARG *args[3],aqrt,alim; aqrt.type = ASTRING; aqrt.text = (void *) qroot; alim.type = SNLIST; alim.text = (void *) limits; args[0] = &aqrt; args[1] = &alim; args[2] = NIL; if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args))) ret = LONGT; else mm_log (reply->text,ERROR); } else mm_log ("Quota not available on this IMAP server",ERROR); return ret; } IDLIST *imap_parse_idlist (char *text) { IDLIST *ret = NULL; char *s; char tmp[MAILTMPLEN]; if(text == NULL) return NULL; for(s = text; *s == ' '; s++); /* move past spaces */ if(*s == '(') s++; if(*s++ == '"'){ char *t; for(t = s; *t && *t != '"'; t++); if(*t == '"'){ ret = fs_get(sizeof(IDLIST)); *t = '\0'; ret->name = cpystr(s); *t = '"'; for(s = t+1; *s == ' '; s++); /* move past spaces */ if(*s++ == '"'){ for(t = s; *t && *t != '"'; t++); if(*t == '"'){ *t = '\0'; ret->value = cpystr(s); *t++ = '"'; ret->next = imap_parse_idlist(t); } else { sprintf(tmp,"ID value not found for name %.80s, at %.80s", ret->name, s); fs_give((void **)&ret->name); fs_give((void **)&ret); mm_log (tmp, NIL); /* this is an technically an error */ } } else { /* failed!, quit */ sprintf(tmp,"ID name \"%.80s\" has no value", ret->name); fs_give((void **)&ret->name); fs_give((void **)&ret); mm_log (tmp, NIL); /* this is an technically an error */ } } } return ret; } long imap_setid (MAILSTREAM *stream, IDLIST *idlist) { long ret = NIL; if (LEVELID (stream)) { /* send "ID (params)" */ IMAPPARSEDREPLY *reply; IMAPARG *args[2],aqrt; IDLIST *list; char *qroot, *p; long len = 0L; if(idlist == NULL) return ret; for (list = idlist; list != NULL; list = list->next) len += strlen(list->name) + strlen(list->value) + 6; if(len > 0){ len += 1L; /* in case there is only one field */ qroot = fs_get(len+1); memset((void *)&qroot[0], 0, len+1); p = qroot; for (list = idlist; list != NULL; list = list->next){ sprintf(p, " \"%s\" \"%s\"", list->name, list->value); p += strlen(p); } *p = ')'; qroot[0] = '('; aqrt.type = ATOM; aqrt.text = (void *) qroot; args[0] = &aqrt; args[1] = NIL; if (imap_OK (stream,reply = imap_send (stream,"ID",args))) ret = LONGT; else mm_log (reply->text,ERROR); if(qroot) fs_give((void **) &qroot); } else mm_log("Empty or malformed ID list", ERROR); } else mm_log ("ID capability not available on this IMAP server",ERROR); return ret; } /* IMAP get quota * Accepts: mail stream * quota root name * Returns: T on success with data returned via callback, NIL on failure */ long imap_getquota (MAILSTREAM *stream,char *qroot) { long ret = NIL; if (LEVELQUOTA (stream)) { /* send "GETQUOTA" */ IMAPPARSEDREPLY *reply; IMAPARG *args[2],aqrt; aqrt.type = ASTRING; aqrt.text = (void *) qroot; args[0] = &aqrt; args[1] = NIL; if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args))) ret = LONGT; else mm_log (reply->text,ERROR); } else mm_log ("Quota not available on this IMAP server",ERROR); return ret; } /* IMAP get quota root * Accepts: mail stream * mailbox name * Returns: T on success with data returned via callback, NIL on failure */ long imap_getquotaroot (MAILSTREAM *stream,char *mailbox) { long ret = NIL; if (LEVELQUOTA (stream)) { /* send "GETQUOTAROOT" */ IMAPPARSEDREPLY *reply; IMAPARG *args[2],ambx; ambx.type = ASTRING; ambx.text = (void *) mailbox; args[0] = &ambx; args[1] = NIL; if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args))) ret = LONGT; else mm_log (reply->text,ERROR); } else mm_log ("Quota not available on this IMAP server",ERROR); return ret; } /* Internal routines */ /* IMAP send command * Accepts: MAIL stream * command * argument list * Returns: parsed reply */ #define CMDBASE LOCAL->tmp /* command base */ IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]) { IMAPPARSEDREPLY *reply; IMAPARG *arg,**arglst; SORTPGM *spg; STRINGLIST *list; SIZEDTEXT st; APPENDDATA *map; sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL); size_t i; void *a; char c,*s,*t,tag[10]; stream->unhealthy = NIL; /* make stream healthy again */ /* gensym a new tag */ sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); if (!LOCAL->netstream) /* make sure have a session */ return imap_fake (stream,tag,"[CLOSED] IMAP connection lost"); mail_lock (stream); /* lock up the stream */ if (sc) /* tell client sending a command */ (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") && compare_cstring (cmd,"STORE") && compare_cstring (cmd,"SEARCH")) ? NIL : SC_EXPUNGEDEFERRED)); /* ignore referral from previous command */ if (LOCAL->referral) fs_give ((void **) &LOCAL->referral); sprintf (CMDBASE,"%s %s",tag,cmd); s = CMDBASE + strlen (CMDBASE); if ((arglst = args) != NULL) while ((arg = *arglst++) != NULL) { *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? */ st.size = strlen ((char *) (st.data = (unsigned char *) arg->text)); if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) != NULL) return reply; break; case LITERAL: /* literal, as a stringstruct */ if ((reply = imap_send_literal (stream,tag,&s,arg->text)) != NULL) return reply; break; case LIST: /* list of strings */ list = (STRINGLIST *) arg->text; c = '('; /* open paren */ do { /* for each list item */ *s++ = c; /* write prefix character */ if ((reply = imap_send_astring (stream,tag,&s,&list->text,NIL, CMDBASE+MAXCOMMAND)) != NULL) return reply; c = ' '; /* prefix character for subsequent strings */ } while ((list = list->next) != NULL); *s++ = ')'; /* close list */ break; case SEARCHPROGRAM: /* search program */ if ((reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text, CMDBASE+MAXCOMMAND)) != NULL) return reply; break; case SORTPROGRAM: /* search program */ c = '('; /* open paren */ for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) { *s++ = c; /* write prefix */ if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++); switch (spg->function) { case SORTDATE: for (t = "DATE"; *t; *s++ = *t++); break; case SORTARRIVAL: for (t = "ARRIVAL"; *t; *s++ = *t++); break; case SORTFROM: for (t = "FROM"; *t; *s++ = *t++); break; case SORTSUBJECT: for (t = "SUBJECT"; *t; *s++ = *t++); break; case SORTTO: for (t = "TO"; *t; *s++ = *t++); break; case SORTCC: for (t = "CC"; *t; *s++ = *t++); break; case SORTSIZE: for (t = "SIZE"; *t; *s++ = *t++); break; default: fatal ("Unknown sort program function in imap_send()!"); } c = ' '; /* prefix character for subsequent items */ } *s++ = ')'; /* close list */ break; case BODYTEXT: /* body section */ for (t = "BODY["; *t; *s++ = *t++); for (t = (char *) arg->text; *t; *s++ = *t++); break; case BODYPEEK: /* body section */ 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++); break; case SEQUENCE: /* sequence */ if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND) while (*t) *s++ = *t++; /* easy case */ else { mail_unlock (stream); /* unlock stream */ a = arg->text; /* save original sequence pointer */ arg->type = ATOM; /* make recursive call be faster */ do { /* break up into multiple commands */ if (i <= MAXCOMMAND) {/* final part? */ reply = imap_send (stream,cmd,args); i = 0; /* and mark as done */ } else { /* still needs to be split further */ if (!(t = strchr (t + MAXCOMMAND - 30,',')) || ((t - (char *) arg->text) > MAXCOMMAND)) fatal ("impossible over-long sequence"); *t = '\0'; /* tie off sequence at point of split*/ /* recurse to do this part */ reply = imap_send (stream,cmd,args); *t++ = ','; /* restore the comma in case something cares */ /* punt if error */ if (!imap_OK (stream,reply)) break; /* calculate size of remaining sequence */ i -= (t - (char *) arg->text); /* point to new remaining sequence */ arg->text = (void *) t; } } while (i); arg->type = SEQUENCE; /* restore in case something cares */ arg->text = a; return reply; /* return result */ } break; case LISTMAILBOX: /* astring with wildcards */ st.size = strlen ((char *) (st.data = (unsigned char *) arg->text)); if ((reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND)) != NULL) return reply; break; case MULTIAPPEND: /* append multiple messages */ /* get package pointer */ map = (APPENDDATA *) arg->text; if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)|| !map->message) { STRING es; INIT (&es,mail_string,"",0); return (reply = imap_send_literal (stream,tag,&s,&es)) ? reply : imap_fake (stream,tag,"Server zero-length literal error"); } case MULTIAPPENDREDO: /* redo multiappend */ /* get package pointer */ map = (APPENDDATA *) arg->text; do { /* make sure date valid if given */ char datetmp[MAILTMPLEN]; MESSAGECACHE elt; STRING es; if (!map->date || mail_parse_date (&elt,map->date)) { if ((t = map->flags) != NULL) { /* flags given? */ if (*t != '(') { *s++ = '('; /* wrap parens around string */ while (*t) *s++ = *t++; *s++ = ')'; /* wrap parens around string */ } else while (*t) *s++ = *t++; *s++ = ' '; /* delimit with space */ } if (map->date) { /* date given? */ st.size = strlen ((char *) (st.data = (unsigned char *) mail_date (datetmp,&elt))); if ((reply = imap_send_astring (stream,tag,&s,&st,NIL, CMDBASE+MAXCOMMAND)) != NULL) return reply; *s++ = ' '; /* delimit with space */ } if ((reply = imap_send_literal (stream,tag,&s,map->message)) != NULL) return reply; /* get next message */ if ((*map->af) (stream,map->data,&map->flags,&map->date, &map->message)) { /* have a message, delete next in command */ if (map->message) *s++ = ' '; continue; /* loop back for next message */ } } /* bad date or need to abort */ INIT (&es,mail_string,"",0); return (reply = imap_send_literal (stream,tag,&s,&es)) ? reply : imap_fake (stream,tag,"Server zero-length literal error"); break; /* exit the loop */ } while (map->message); break; case SNLIST: /* list of string/number pairs */ list = (STRINGLIST *) arg->text; c = '('; /* open paren */ do { /* for each list item */ *s++ = c; /* write prefix character */ if (list) { /* sigh, QUOTA has bizarre syntax! */ for (t = (char *) list->text.data; *t; *s++ = *t++); sprintf (s," %lu",list->text.size); s += strlen (s); c = ' '; /* prefix character for subsequent strings */ } } while ((list = list->next) != NULL); *s++ = ')'; /* close list */ break; default: fatal ("Unknown argument type in imap_send()!"); } } /* send the command */ reply = imap_sout (stream,tag,CMDBASE,&s); mail_unlock (stream); /* unlock stream */ return reply; } /* IMAP send atom-string * Accepts: MAIL stream * reply tag * pointer to current position pointer of output bigbuf * atom-string to output * flag if list_wildcards allowed * maximum to write as atom or qstring * Returns: error reply or NIL if success */ IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s, SIZEDTEXT *as,long wildok,char *limit) { unsigned long j; char c; STRING st; /* default to atom unless empty or loser */ int qflag = (as->size && !LOCAL->loser) ? NIL : T; /* in case needed */ INIT (&st,mail_string,(void *) as->data,as->size); /* always write literal if no space */ if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st); for (j = 0; j < as->size; j++) switch (c = as->data[j]) { default: /* all other characters */ if (!(c & 0x80)) { /* must not be 8bit */ if (c <= ' ') qflag = T; /* must quote if a CTL */ break; } case '\0': /* not a CHAR */ case '\012': case '\015': /* not a TEXT-CHAR */ case '"': case '\\': /* quoted-specials (IMAP2 required this) */ return imap_send_literal (stream,tag,s,&st); case '*': case '%': /* list_wildcards */ if (wildok) break; /* allowed if doing the wild thing */ /* atom_specials */ case '(': case ')': case '{': case ' ': case 0x7f: #if 0 case '"': case '\\': /* quoted-specials (could work in IMAP4) */ #endif qflag = T; /* must use quoted string format */ break; } if (qflag) *(*s)++ = '"'; /* write open quote */ for (j = 0; j < as->size; j++) *(*s)++ = as->data[j]; if (qflag) *(*s)++ = '"'; /* write close quote */ return NIL; } /* 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 NIL if success */ IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s, STRING *st) { IMAPPARSEDREPLY *reply; unsigned long i = SIZE (st); unsigned long j; sprintf (*s,"{%lu}",i); /* write literal count */ *s += strlen (*s); /* size of literal count */ /* send the command */ reply = imap_sout (stream,tag,CMDBASE,s); if (strcmp (reply->tag,"+")) {/* prompt for more data? */ mail_unlock (stream); /* no, give up */ return reply; } while (i) { /* dump the text */ if (st->cursize) { /* if text to do in this chunk */ /* RFC 3501 technically forbids NULs in literals. Normally, the * delivering MTA would take care of MIME converting the message text * so that it is NUL-free. If it doesn't, then we have the choice of * either violating IMAP by sending NULs, corrupting the data, or going * to lots of work to do MIME conversion in the IMAP server. * * No current stringstruct driver objects to having its buffer patched. * If this ever changes, it will be necessary to change this kludge. */ /* patch NULs to C1 control */ for (j = 0; j < st->cursize; ++j) if (!st->curpos[j]) st->curpos[j] = 0x80; if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) { mail_unlock (stream); return imap_fake (stream,tag,"[CLOSED] 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 NIL; /* success */ } #define ADD_STRING(X, Y) { \ if(remain > 0){ \ sprintf (u, (X), (Y)); \ len = strlen(u); \ if(len < remain){ \ remain -= len; \ strncpy(t, u, strlen(u)); \ t[strlen(u)] = '\0'; \ t += strlen (t); \ } \ } \ } long imap_search_x_gm_ext1 (MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long flags) { char *cmd = (flags & SE_UID) ? "UID SEARCH X-GM-RAW" : "SEARCH X-GM-RAW"; char *t, s[MAILTMPLEN+1], u[MAILTMPLEN]; IMAPARG *args[4],apgm; IMAPPARSEDREPLY *reply; unsigned long i,j,k; MESSAGECACHE *elt; size_t remain = sizeof(s) - 1, len; u[0] = s[0] = '\0'; t = s; args[1] = args[2] = args[3] = NIL; if(pgm->x_gm_ext1) ADD_STRING(" %s", pgm->x_gm_ext1->text.data); #if 0 /* maybe later */ if (pgm->larger) ADD_STRING(" larger:%lu", pgm->larger); if (pgm->smaller) ADD_STRING(" smaller:%lu", pgm->smaller); if (pgm->deleted) ADD_STRING(" %s", "in:trash"); if (pgm->undeleted) ADD_STRING(" %s", "-in:trash"); if (pgm->draft) ADD_STRING(" %s", "in:drafts"); if (pgm->undraft) ADD_STRING(" %s", "-in:drafts"); if (pgm->flagged) ADD_STRING(" %s", "is:starred"); if (pgm->unflagged) ADD_STRING(" %s", "-is:starred"); if (pgm->seen) ADD_STRING(" %s", "-is:unread"); if (pgm->unseen) ADD_STRING(" %s", "is:unread"); if (pgm->keyword){ STRINGLIST *sl; for(sl = pgm->keyword; remain > 0 && sl; sl = sl->next) ADD_STRING(" label:%s", sl->text.data); } if (pgm->unkeyword){ STRINGLIST *sl; for(sl = pgm->unkeyword; remain > 0 && sl; sl = sl->next) ADD_STRING(" -label:%s", sl->text.data); } if (pgm->sentbefore){ unsigned short date = pgm->sentbefore; sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), (date >> 5) & 0xf, date & 0x1f); ADD_STRING(" before:%s", v); } if (pgm->sentsince){ unsigned short date = pgm->sentsince; sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), (date >> 5) & 0xf, date & 0x1f); ADD_STRING(" older:%s", v); } if (pgm->before){ unsigned short date = pgm->before; sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), (date >> 5) & 0xf, date & 0x1f); ADD_STRING(" before:%s", v); } if (pgm->since){ unsigned short date = pgm->since; sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), (date >> 5) & 0xf, date & 0x1f); ADD_STRING(" before:%s", v); } if (pgm->older){ sprintf(v, "%dd", pgm->older/86400); ADD_STRING(" older_than:%s", v); } if (pgm->younger){ sprintf(v, "%dd", pgm->younger/86400); ADD_STRING(" newer_than:%s", v); } if(pgm->bcc){ STRINGLIST *sl; ADD_STRING("%s", pgm->bcc->next ? " {" : " "); for(sl = pgm->bcc; remain > 0 && sl; sl = sl->next){ ADD_STRING("bcc:%s", sl->text.data); ADD_STRING("%s", sl->next ? " " : "}"); } } if(pgm->cc){ STRINGLIST *sl; ADD_STRING("%s", pgm->cc->next ? " {" : " "); for(sl = pgm->cc; remain > 0 && sl; sl = sl->next){ ADD_STRING("cc:%s", sl->text.data); ADD_STRING("%s", sl->next ? " " : "}"); } } if(pgm->from){ STRINGLIST *sl; ADD_STRING("%s", pgm->from->next ? " {" : " "); for(sl = pgm->from; remain > 0 && sl; sl = sl->next){ ADD_STRING("from:%s", sl->text.data); ADD_STRING("%s", sl->next ? " " : (pgm->from->next ? "}" : "")); } } if(pgm->to){ STRINGLIST *sl; ADD_STRING("%s", pgm->to->next ? " {" : " "); for(sl = pgm->to; remain > 0 && sl; sl = sl->next){ ADD_STRING("to:%s", sl->text.data); ADD_STRING("%s", sl->next ? " " : (pgm->to->next ? "}" : "")); } } if(pgm->subject){ STRINGLIST *sl; ADD_STRING("%s", pgm->subject->next ? " {" : " "); for(sl = pgm->subject; remain > 0 && sl; sl = sl->next){ ADD_STRING("subject:(%s)", sl->text.data); ADD_STRING("%s", sl->next ? " " : (pgm->subject->next ? "}" : "")); } } if(pgm->body){ STRINGLIST *sl; ADD_STRING("%s", pgm->body->next ? " {" : " "); for(sl = pgm->body; remain > 0 && sl; sl = sl->next){ ADD_STRING(" %s", sl->text.data); ADD_STRING("%s", sl->next ? " " : (pgm->body->next ? "}" : "")); } } if (mail_valid_net_parse (stream->mailbox,&mb)){ p = strchr(mb.mailbox, '/'); ADD_STRING(" in:%s", p ? p+1 : "inbox"); } #endif /* maybe later */ s[0] = '\"'; strcat(t, "\""); apgm.type = ATOM; apgm.text = (void *) s; args[0] = &apgm; args[1] = NIL; LOCAL->uidsearch = (flags & SE_UID) ? T : NIL; reply = imap_send (stream,cmd,args); LOCAL->uidsearch = NIL; /* do locally if server won't grok */ if (!strcmp (reply->key,"BAD")) { if ((flags & SE_NOLOCAL) || !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) return NIL; } else if (!imap_OK (stream,reply)) { mm_log (reply->text,ERROR); return NIL; } /* can never pre-fetch with a short cache */ if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) && !stream->scache) { /* only if prefetching permitted */ t = LOCAL->tmp; /* build sequence in temporary buffer */ *t = '\0'; /* initially nothing */ /* search through mailbox */ for (i = 1; k && (i <= stream->nmsgs); ++i) /* for searched messages with no envelope */ if ((elt = mail_elt (stream,i)) && elt->searched && !mail_elt (stream,i)->private.msg.env) { /* prepend with comma if not first time */ if (LOCAL->tmp[0]) *t++ = ','; sprintf (t,"%lu",j = i);/* output message number */ t += strlen (t); /* point at end of string */ k--; /* count one up */ /* search for possible end of range */ while (k && (i < stream->nmsgs) && (elt = mail_elt (stream,i+1))->searched && !elt->private.msg.env) i++,k--; if (i != j) { /* if a range */ sprintf (t,":%lu",i); /* output delimiter and end of range */ t += strlen (t); /* point at end of string */ } if ((t - LOCAL->tmp) > (IMAPTMPLEN - 50)) break; } if (LOCAL->tmp[0]) { /* anything to pre-fetch? */ /* pre-fetch envelopes for the first imap_prefetch number of messages */ if (!imap_OK (stream,reply = imap_fetch (stream,t = cpystr (LOCAL->tmp),FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) + ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL)))) mm_log (reply->text,ERROR); fs_give ((void **) &t); /* flush copy of sequence */ } } return LONGT; } /* IMAP send search program * Accepts: MAIL stream * reply tag * base pointer if trimming needed * pointer to current position pointer of output bigbuf * search program to output * pointer to limit guideline * Returns: error reply or NIL if success */ IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base, char **s,SEARCHPGM *pgm,char *limit) { IMAPPARSEDREPLY *reply; SEARCHHEADER *hdr; SEARCHOR *pgo; SEARCHPGMLIST *pgl; char *t; /* trim if called recursively */ if (base) *s = imap_send_spgm_trim (base,*s,NIL); base = *s; /* this is the new base */ /* default searchpgm */ for (t = "ALL"; *t; *(*s)++ = *t++); if (!pgm) return NIL; /* done if NIL searchpgm */ if ((pgm->msgno && /* message sequences */ (pgm->msgno->next || /* trim away first:last */ (pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) && (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) || (pgm->uid && (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit)))) return reply; /* 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,base,s," KEYWORD ",pgm->keyword, limit))) || (pgm->unkeyword && (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ", pgm->unkeyword,limit)))) 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); if (pgm->older) { sprintf (*s," OLDER %lu",pgm->older); *s += strlen (*s); } if (pgm->younger) { sprintf (*s," YOUNGER %lu",pgm->younger); *s += strlen (*s); } /* search texts */ if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ", pgm->bcc,limit))) || (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc, limit))) || (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ", pgm->from,limit))) || (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to, limit)))) return reply; if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ", pgm->subject,limit))) || (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ", pgm->body,limit))) || (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ", pgm->text,limit)))) return reply; /* Note that these criteria are not supported by IMAP and have to be emulated */ if ((pgm->return_path && (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ", pgm->return_path,limit))) || (pgm->sender && (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ", pgm->sender,limit))) || (pgm->reply_to && (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ", pgm->reply_to,limit))) || (pgm->in_reply_to && (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ", pgm->in_reply_to,limit))) || (pgm->message_id && (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ", pgm->message_id,limit))) || (pgm->newsgroups && (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ", pgm->newsgroups,limit))) || (pgm->followup_to && (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ", pgm->followup_to,limit))) || (pgm->references && (reply = imap_send_slist (stream,tag,base,s," HEADER References ", pgm->references,limit)))) return reply; /* all other headers */ if ((hdr = pgm->header) != NULL) do { *s = imap_send_spgm_trim (base,*s," HEADER "); if ((reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) != NULL) return reply; *(*s)++ = ' '; if ((reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) != NULL) return reply; } while ((hdr = hdr->next) != NULL); for (pgo = pgm->or; pgo; pgo = pgo->next) { *s = imap_send_spgm_trim (base,*s," OR ("); if ((reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit)) != NULL) return reply; for (t = ") ("; *t; *(*s)++ = *t++); if ((reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) != NULL) return reply; *(*s)++ = ')'; } for (pgl = pgm->not; pgl; pgl = pgl->next) { *s = imap_send_spgm_trim (base,*s," NOT ("); if ((reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit)) != NULL) return reply; *(*s)++ = ')'; } /* trim if needed */ *s = imap_send_spgm_trim (base,*s,NIL); return NIL; /* search program written OK */ } /* Write new text and trim extraneous "ALL" from searchpgm * Accepts: pointer to start of searchpgm or NIL * current end pointer * new text to write or NIL * Returns: new end pointer, trimmed if needed */ char *imap_send_spgm_trim (char *base,char *s,char *text) { char *t; /* write new text */ if (text) for (t = text; *t; *s++ = *t++); /* need to trim? */ if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') && (base[2] == 'L') && (base[3] == ' ')) { memmove (base,t,s - t); /* yes, blat down remaining text */ s -= 4; /* and reduce current pointer */ } return s; /* return new end pointer */ } /* IMAP send search set * Accepts: MAIL stream * current command tag * base pointer if trimming needed * pointer to current position pointer of output bigbuf * search set to output * message prefix * maximum output pointer * Returns: NIL if success, error reply if error */ IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base, char **s,SEARCHSET *set,char *prefix, char *limit) { IMAPPARSEDREPLY *reply; STRING st; char c,*t; char *start = *s; /* trim and write prefix */ *s = imap_send_spgm_trim (base,*s,prefix); /* run down search list */ for (c = NIL; set && (*s < limit); set = set->next, c = ',') { if (c) *(*s)++ = c; /* write delimiter and first value */ if (set->first == 0xffffffff) *(*s)++ = '*'; else { sprintf (*s,"%lu",set->first); *s += strlen (*s); } /* have a second value? */ if (set->last && (set->first != set->last)) { *(*s)++ = ':'; /* write delimiter and second value */ if (set->last == 0xffffffff) *(*s)++ = '*'; else { sprintf (*s,"%lu",set->last); *s += strlen (*s); } } } if (set) { /* insert "OR" in front of incomplete set */ memmove (start + 3,start,*s - start); memcpy (start," OR",3); *s += 3; /* point to end of buffer */ /* write glue that is equivalent to ALL */ for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++); /* but broken by a literal */ INIT (&st,mail_string,(void *) "FOO",3); if ((reply = imap_send_literal (stream,tag,s,&st)) != NULL) return reply; *(*s)++ = ')'; /* close glue */ if ((reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) != NULL) return reply; *(*s)++ = ')'; /* close second OR argument */ } return NIL; } /* IMAP send search list * Accepts: MAIL stream * reply tag * base pointer if trimming needed * pointer to current position pointer of output bigbuf * name of search list * search list to output * maximum output pointer * Returns: NIL if success, error reply if error */ IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base, char **s,char *name,STRINGLIST *list, char *limit) { IMAPPARSEDREPLY *reply; do { *s = imap_send_spgm_trim (base,*s,name); base = NIL; /* no longer need trimming */ reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit); } 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 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'; mail_dlog (base,LOCAL->sensitive); } *(*s)++ = '\015'; /* append CRLF */ *(*s)++ = '\012'; **s = '\0'; reply = net_sout (LOCAL->netstream,base,*s - base) ? imap_reply (stream,tag) : imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)"); *s = base; /* restart buffer */ return reply; } /* IMAP send null-terminated string to sender * Accepts: MAIL stream * string * Returns: T if success, else NIL */ long imap_soutr (MAILSTREAM *stream,char *string) { long ret; unsigned long i; char *s; if (stream->debug) mm_dlog (string); sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1), "%s\015\012",string); ret = net_sout (LOCAL->netstream,s,i); fs_give ((void **) &s); return ret; } /* 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; while (LOCAL->netstream) { /* parse reply from server */ if ((reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) != NULL) { /* continuation ready? */ if (!strcmp (reply->tag,"+")) return reply; /* untagged data? */ else if (!strcmp (reply->tag,"*")) { imap_parse_unsolicited (stream,reply); if (!tag) return reply; /* return if just wanted greeting */ } else { /* tagged data */ if (tag && !compare_cstring (tag,reply->tag)) return reply; /* report bogon */ sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s", (char *) reply->tag,(char *) reply->key,(char *) reply->text); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } } return imap_fake (stream,tag, "[CLOSED] IMAP connection broken (server response)"); } /* 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) { char *r; if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); /* init fields in case error */ LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL; if (!(LOCAL->reply.line = text)) { /* NIL text means the stream died */ if (LOCAL->netstream) net_close (LOCAL->netstream); LOCAL->netstream = NIL; return NIL; } if (stream->debug) mm_dlog (LOCAL->reply.line); if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) { mm_notify (stream,"IMAP server sent a blank line",WARN); stream->unhealthy = T; return NIL; } /* non-continuation replies */ if (strcmp (LOCAL->reply.tag,"+")) { /* parse key */ if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) { /* determine what is missing */ sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s", (char *) LOCAL->reply.tag); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; return NIL; /* can't parse this text */ } ucase (LOCAL->reply.key); /* canonicalize key to upper */ /* get text as well, allow empty text */ if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r))) 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 = strtok_r (NIL,"\n",&r))) LOCAL->reply.text = ""; } return &LOCAL->reply; /* return parsed reply */ } /* IMAP fake reply when stream determined to be dead * Accepts: MAIL stream * tag * text of fake reply (must start with "[CLOSED]") * Returns: parsed reply */ IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text) { mm_notify (stream,text,BYE); /* send bye alert */ if (LOCAL->netstream) net_close (LOCAL->netstream); LOCAL->netstream = NIL; /* farewell, dear NET stream... */ /* flush previous reply */ if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); /* build new fake reply */ LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*"); LOCAL->reply.key = "NO"; LOCAL->reply.text = text; return &LOCAL->reply; /* return parsed reply */ } /* 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) { long ret = NIL; /* OK - operation succeeded */ if (!strcmp (reply->key,"OK")) { imap_parse_response (stream,reply->text,NIL,NIL); ret = T; } /* NO - operation failed */ else if (!strcmp (reply->key,"NO")) imap_parse_response (stream,reply->text,WARN,NIL); else { /* BAD - operation rejected */ if (!strcmp (reply->key,"BAD")) { imap_parse_response (stream,reply->text,ERROR,NIL); sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text); } /* bad protocol received */ else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s", (char *) reply->key,(char *) reply->text); mm_log (LOCAL->tmp,ERROR); /* either way, this is not good */ } return ret; } /* IMAP parse and act upon unsolicited reply * Accepts: MAIL stream * parsed reply */ void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply) { unsigned long i = 0; unsigned long j,msgno; unsigned char *s,*t; char *r; /* see if key is a number */ if (isdigit (*reply->key)) { msgno = strtoul (reply->key,(char **) &s,10); if (*s) { /* better be nothing after number */ sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s", (char *) reply->key); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; return; } if (!reply->text) { /* better be some data */ mm_notify (stream,"Missing message data",WARN); stream->unhealthy = T; return; } /* get message data type, canonicalize upper */ s = ucase (strtok_r (reply->text," ",&r)); t = strtok_r (NIL,"\n",&r); /* and locate the text after it */ /* now take the action */ /* change in size of mailbox */ if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs)) mail_exists (stream,msgno); else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs)) mail_recent (stream,msgno); else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) { mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT); if (elt) imap_gc_body (elt->private.msg.body); /* notify upper level */ mail_expunged (stream,msgno); } else if (t && (!strcmp (s,"FETCH") || !strcmp (s,"STORE")) && msgno && (msgno <= stream->nmsgs)) { char *prop; GETS_DATA md; ENVELOPE **e; MESSAGECACHE *elt = mail_elt (stream,msgno); ENVELOPE *env = NIL; imapenvelope_t ie = (imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL); ++t; /* skip past open parenthesis */ /* parse Lisp-form property list */ while ((prop = (strtok_r (t," )",&r))) && (t = strtok_r (NIL,"\n",&r))) { INIT_GETS (md,stream,elt->msgno,NIL,0,0); e = NIL; /* not pointing at any envelope yet */ /* canonicalize property, parse it */ if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t); else if (!strcmp (prop,"INTERNALDATE") && (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) { if (!mail_parse_date (elt,s)) { sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; /* slam in default so we don't try again */ mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000"); } fs_give ((void **) &s); } /* unique identifier */ else if (!strcmp (prop,"UID")) { LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10); LOCAL->lastuid.msgno = elt->msgno; } else if (!strcmp (prop,"ENVELOPE")) { if (stream->scache) { /* short cache, flush old stuff */ mail_free_body (&stream->body); stream->msgno = elt->msgno; e = &stream->env; /* get pointer to envelope */ } else e = &elt->private.msg.env; imap_parse_envelope (stream,e,&t,reply); } else if (!strncmp (prop,"BODY",4)) { if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) { BODY **body; if (stream->scache){/* short cache, flush old stuff */ if (stream->msgno != msgno) { mail_free_envelope (&stream->env); sprintf (LOCAL->tmp,"Body received for %lu but current is %lu", msgno,stream->msgno); stream->msgno = msgno; } /* get pointer to body */ body = &stream->body; } else body = &elt->private.msg.body; /* flush any prior body */ mail_free_body (body); /* instantiate and parse a new body */ imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply); } else if (prop[4] == '[') { STRINGLIST *stl = NIL; SIZEDTEXT text; /* will want to return envelope data */ if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") || !strcmp (md.what,"0]")) e = stream->scache ? &stream->env : &elt->private.msg.env; LOCAL->tmp[0] ='\0';/* no errors yet */ /* found end of section? */ if (!(s = strchr (md.what,']'))) { /* skip leading nesting */ for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++); /* better be one of these */ if (strncmp (s,"HEADER.FIELDS",13) && (!s[13] || strcmp (s+13,".NOT"))) sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what); /* get list of headers */ else if (!(stl = imap_parse_stringlist (stream,&t,reply))) sprintf (LOCAL->tmp,"Bogus header field list: %.80s", (char *) t); else if (*t != ']') sprintf (LOCAL->tmp,"Unterminated header section: %.80s", (char *) t); /* point after the text */ else if ((t = strchr (s = t,' ')) != NULL) *t++ = '\0'; } if (s && !LOCAL->tmp[0]) { *s++ = '\0'; /* tie off section specifier */ if (*s == '<') { /* partial specifier? */ md.first = strtoul (s+1,(char **) &s,10) + 1; if (*s++ != '>') /* make sure properly terminated */ sprintf (LOCAL->tmp,"Unterminated partial data: %.80s", (char *) s-1); } if (!LOCAL->tmp[0] && *s) sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s); } if (LOCAL->tmp[0]) { /* got any errors? */ mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; mail_free_stringlist (&stl); } else { /* parse text from server */ text.data = (unsigned char *) imap_parse_string (stream,&t,reply, ((md.what[0] && (md.what[0] != 'H')) || md.first || md.last) ? &md : NIL, &text.size,NIL); /* all done if partial */ if (md.first || md.last) mail_free_stringlist (&stl); /* otherwise register it in the cache */ else imap_cache (stream,msgno,md.what,stl,&text); } fs_give ((void **) &md.what); } else { sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } /* one of the RFC822 props? */ else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){ SIZEDTEXT text; if (!prop[6]) { /* cache full message */ md.what = ""; text.data = (unsigned char *) imap_parse_string (stream,&t,reply,&md,&text.size,NIL); imap_cache (stream,msgno,md.what,NIL,&text); } else if (!strcmp (prop+7,"SIZE")) elt->rfc822_size = strtoul (t,(char **) &t,10); /* legacy properties */ else if (!strcmp (prop+7,"HEADER")) { text.data = (unsigned char *) imap_parse_string (stream,&t,reply,NIL,&text.size,NIL); imap_cache (stream,msgno,"HEADER",NIL,&text); e = stream->scache ? &stream->env : &elt->private.msg.env; } else if (!strcmp (prop+7,"TEXT")) { md.what = "TEXT"; text.data = (unsigned char *) imap_parse_string (stream,&t,reply,&md,&text.size,NIL); imap_cache (stream,msgno,md.what,NIL,&text); } else { sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } else { sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } if (e && *e) env = *e; /* note envelope if we got one */ } if (prop) { sprintf (LOCAL->tmp,"Missing data for property: %.80s",prop); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } /* do callback if requested */ if (ie && env) (*ie) (stream,msgno,env); } /* obsolete response to COPY */ else if (strcmp (s,"COPY")) { sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } else if (!strcmp (reply->key,"FLAGS") && reply->text && (*reply->text == '(') && (s = strtok_r (reply->text+1," )",&r))) do if (*s != '\\') { for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] && compare_cstring (s,stream->user_flags[i]); i++); if (i > NUSERFLAGS) { sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s", (char *) s); mm_notify (stream,LOCAL->tmp,WARN); } else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s); } while ((s = strtok_r (NIL," )",&r)) != NULL); else if (!strcmp (reply->key,"SEARCH")) { /* only do something if have text */ if (reply->text && (t = strtok_r (reply->text," ",&r))) do if ((i = strtoul (t,NIL,10)) != 0L) { /* UIDs always passed to main program */ if (LOCAL->uidsearch) mm_searched (stream,i); /* should be a msgno then */ else if ((i <= stream->nmsgs) && (!LOCAL->filter || mail_elt (stream,i)->private.filter)) { mail_elt (stream,i)->searched = T; if (!stream->silent) mm_searched (stream,i); } } while ((t = strtok_r (NIL," ",&r)) != NULL); } else if (!strcmp (reply->key,"SORT")) { sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL); LOCAL->sortsize = 0; /* initialize sort data */ if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata); LOCAL->sortdata = (unsigned long *) fs_get ((stream->nmsgs + 1) * sizeof (unsigned long)); /* only do something if have text */ if (reply->text && (t = strtok_r (reply->text," ",&r))) { do if ((i = atol (t)) && (LOCAL->filter ? mail_elt (stream,i)->searched : T)) LOCAL->sortdata[LOCAL->sortsize++] = i; while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs)); } LOCAL->sortdata[LOCAL->sortsize] = 0; /* also return via callback if requested */ if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize); } else if (!strcmp (reply->key,"THREAD")) { threadresults_t tr = (threadresults_t) mail_parameters (NIL,GET_THREADRESULTS,NIL); if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata); if ((s = reply->text) != NULL) { LOCAL->threaddata = imap_parse_thread (stream,&s); if (tr) (*tr) (stream,LOCAL->threaddata); if (s && *s) { sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } } else if (!strcmp (reply->key,"STATUS") && reply->text) { MAILSTATUS status; unsigned char *txt = reply->text; if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt && (*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) && (s - txt)) { *s = '\0'; /* tie off status data */ /* initialize data block */ status.flags = status.messages = status.recent = status.unseen = status.uidnext = status.uidvalidity = 0; while (*txt && (s = strchr (txt,' '))) { *s++ = '\0'; /* tie off status attribute name */ /* get attribute value */ i = strtoul (s,(char **) &s,10); if (!compare_cstring (txt,"MESSAGES")) { status.flags |= SA_MESSAGES; status.messages = i; } else if (!compare_cstring (txt,"RECENT")) { status.flags |= SA_RECENT; status.recent = i; } else if (!compare_cstring (txt,"UNSEEN")) { status.flags |= SA_UNSEEN; status.unseen = i; } else if (!compare_cstring (txt,"UIDNEXT")) { status.flags |= SA_UIDNEXT; status.uidnext = i; } else if (!compare_cstring (txt,"UIDVALIDITY")) { status.flags |= SA_UIDVALIDITY; status.uidvalidity = i; } /* next attribute */ txt = (*s == ' ') ? s + 1 : s; } if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) < IMAPTMPLEN) { strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t); /* pass status to main program */ mm_status (stream,LOCAL->tmp,&status); } } if (t) fs_give ((void **) &t); } else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) && reply->text && (*reply->text == '(') && (s = strchr (reply->text,')')) && (s[1] == ' ')) { char delimiter = '\0'; *s++ = '\0'; /* tie off attribute list */ /* parse attribute list */ if ((t = strtok_r (reply->text+1," ",&r)) != NULL) do { if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS; else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT; else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED; else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED; else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN; else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN; else if (!compare_cstring (t,"\\All")) i |= LATT_ALL; else if (!compare_cstring (t,"\\Archive")) i |= LATT_ARCHIVE; else if (!compare_cstring (t,"\\Drafts")) i |= LATT_DRAFTS; else if (!compare_cstring (t,"\\Flagged")) i |= LATT_FLAGGED; else if (!compare_cstring (t,"\\Junk")) i |= LATT_JUNK; else if (!compare_cstring (t,"\\Sent")) i |= LATT_SENT; else if (!compare_cstring (t,"\\Trash")) i |= LATT_TRASH; /* ignore extension flags */ } while ((t = strtok_r (NIL," ",&r)) != NULL); 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 */ } /* parse the mailbox name */ if ((t = imap_parse_astring (stream,&s,reply,&j)) != NULL) { /* prepend prefix if requested */ if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN)) sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t); else s = t; /* otherwise just mailbox name */ /* pass data to main program */ if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i); else mm_list (stream,delimiter,s,i); fs_give ((void **) &t); /* flush mailbox name */ } } else if (!strcmp (reply->key,"NAMESPACE")) { if (LOCAL->namespace) { mail_free_namespace (&LOCAL->namespace[0]); mail_free_namespace (&LOCAL->namespace[1]); mail_free_namespace (&LOCAL->namespace[2]); } else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *)); if ((s = reply->text) != NULL) { /* parse namespace results */ LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply); LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply); LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply); if (s && *s) { sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } else { mm_notify (stream,"Missing namespace list",WARN); stream->unhealthy = T; } } else if (!strcmp (reply->key,"ACL") && (s = reply->text) && (t = imap_parse_astring (stream,&s,reply,NIL))) { getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL); if (s && (*s++ == ' ')) { ACLLIST *al = mail_newacllist (); ACLLIST *ac = al; do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) && s && (*s++ == ' ')) ac->rights = imap_parse_astring (stream,&s,reply,NIL); while (ac->rights && s && (*s == ' ') && s++ && (ac = ac->next = mail_newacllist ())); if (!ac->rights || (s && *s)) { sprintf (LOCAL->tmp,"Invalid ACL identifier/rights for %.80s", (char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else if (ar) (*ar) (stream,t,al); mail_free_acllist (&al); /* clean up */ } /* no optional rights */ else if (ar) (*ar) (stream,t,NIL); fs_give ((void **) &t); /* free mailbox name */ } else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) && (t = imap_parse_astring (stream,&s,reply,NIL))) { listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL); char *id,*r; if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){ if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) { if (s && (*s++ == ' ')) { STRINGLIST *rl = mail_newstringlist (); STRINGLIST *rc = rl; do rc->text.data = (unsigned char *) imap_parse_astring (stream,&s,reply,&rc->text.size); while (rc->text.data && s && (*s == ' ') && s++ && (rc = rc->next = mail_newstringlist ())); if (!rc->text.data || (s && *s)) { sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s", (char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else if (lr) (*lr) (stream,t,id,r,rl); /* clean up */ mail_free_stringlist (&rl); } /* no optional rights */ else if (lr) (*lr) (stream,t,id,r,NIL); fs_give ((void **) &r); /* free rights */ } else { sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } fs_give ((void **) &id); /* free identifier */ } else { sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifier for %.80s",(char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } fs_give ((void **) &t); /* free mailbox name */ } else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) && (t = imap_parse_astring (stream,&s,reply,NIL))) { myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL); char *r; if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) { if (s && *s) { sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else if (mr) (*mr) (stream,t,r); fs_give ((void **) &r); /* free rights */ } else { sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } fs_give ((void **) &t); /* free mailbox name */ } /* this response has a bizarre syntax! */ else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) && (t = imap_parse_astring (stream,&s,reply,NIL))) { /* in case error */ sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t); if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) { quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL); QUOTALIST *ql = NIL; QUOTALIST *qc; /* parse non-empty quota resource list */ if (*s != ')') for (ql = qc = mail_newquotalist (); T; qc = qc->next = mail_newquotalist ()) { if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s && (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) { if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10); else if ((t = strchr (s,' ')) != NULL) t = s; if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){ if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10); else if ((t = strpbrk (s," )")) != NULL) t = s; /* another resource follows? */ if (*s == ' ') continue; /* end of resource list? */ if ((*s == ')') && !s[1]) { if (qt) (*qt) (stream,t,ql); break; /* all done */ } } } /* something bad happened */ mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; break; /* parse failed */ } /* all done with quota resource list now */ if (ql) mail_free_quotalist (&ql); } else { mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } fs_give ((void **) &t); /* free root name */ } else if (!strcmp (reply->key,"ID") && (s = reply->text)){ if(compare_cstring (s,"NIL")) LOCAL->id = imap_parse_idlist(s); } else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) && (t = imap_parse_astring (stream,&s,reply,NIL))) { sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t); if (s && (*s++ == ' ')) { quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL); STRINGLIST *rl = mail_newstringlist (); STRINGLIST *rc = rl; do rc->text.data = (unsigned char *) imap_parse_astring (stream,&s,reply,&rc->text.size); while (rc->text.data && *s && (*s++ == ' ') && (rc = rc->next = mail_newstringlist ())); if (!rc->text.data || (s && *s)) { mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else if (qr) (*qr) (stream,t,rl); /* clean up */ mail_free_stringlist (&rl); } else { mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } fs_give ((void **) &t); } else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) imap_parse_response (stream,reply->text,NIL,T); else if (!strcmp (reply->key,"NO")) imap_parse_response (stream,reply->text,WARN,T); else if (!strcmp (reply->key,"BAD")) imap_parse_response (stream,reply->text,ERROR,T); else if (!strcmp (reply->key,"BYE")) { LOCAL->byeseen = T; /* note that a BYE seen */ imap_parse_response (stream,reply->text,BYE,T); } else if (!strcmp (reply->key,"CAPABILITY") && reply->text) imap_parse_capabilities (stream,reply->text); else if (!strcmp (reply->key,"MAILBOX") && reply->text) { if (LOCAL->prefix && ((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN)) sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text); else t = reply->text; mm_list (stream,NIL,t,NIL); } else { sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s", (char *) reply->key); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } } /* Parse human-readable response text * Accepts: mail stream * text * error level for mm_notify() * non-NIL if want mm_notify() event even if no response code */ void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy) { char *s,*t,*r; size_t i; unsigned long j; MESSAGECACHE *elt; copyuid_t cu; appenduid_t au; SEARCHSET *source = NIL; SEARCHSET *dest = NIL; if (text && (*text == '[') && (t = strchr (s = text + 1,']')) && ((i = t - s) < IMAPTMPLEN)) { LOCAL->tmp[i] = '\0'; /* make mungable copy of text code */ if ((s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) != NULL) *s++ = '\0'; if (s) { /* have argument? */ ntfy = NIL; /* suppress mm_notify if normal SELECT data */ if (!compare_cstring (t,"CAPABILITY")) imap_parse_capabilities(stream,s); else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') && (t[i-1] == ')')) { t[i-1] = '\0'; /* tie off flags */ stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL; stream->perm_user_flags = NIL; if ((s = strtok_r (s+1," ",&r)) != NULL) do { if (*s == '\\') { /* system flags */ if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T; else if (!compare_cstring (s,"\\Deleted")) stream->perm_deleted = T; else if (!compare_cstring (s,"\\Flagged")) stream->perm_flagged = T; else if (!compare_cstring (s,"\\Answered")) stream->perm_answered = T; else if (!compare_cstring (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_r (NIL," ",&r)) != NULL); } else if (!compare_cstring (t,"UIDVALIDITY") && (j = strtoul (s,NIL,10))){ /* do this in separate if because of ntfy */ if (j != stream->uid_validity) { mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); stream->uid_validity = j; /* purge any UIDs in cache */ for (j = 1; j <= stream->nmsgs; j++) if ((elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT)) != NULL) elt->private.uid = 0; } } else if (!compare_cstring (t,"UIDNEXT")) stream->uid_last = strtoul (s,NIL,10) - 1; else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) && !compare_cstring (t,"COPYUID") && (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) && isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') && (source = mail_parse_set (s,&s)) && (*s++ == ' ') && (dest = mail_parse_set (s,&s)) && !*s) (*cu) (stream,LOCAL->appendmailbox,j,source,dest); else if (j && !compare_cstring (t,"APPENDUID") && (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) && isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') && (dest = mail_parse_set (s,&s)) && !*s) (*au) (LOCAL->appendmailbox,j,dest); else { /* all other response code events */ ntfy = T; /* must mm_notify() */ if (!compare_cstring (t,"REFERRAL")) LOCAL->referral = cpystr (t + 9); } mail_free_searchset (&source); mail_free_searchset (&dest); } else { /* no arguments */ if (!compare_cstring (t,"UIDNOTSTICKY")) { ntfy = NIL; stream->uid_nosticky = T; } else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T; else if (!compare_cstring (t,"READ-WRITE")) stream->rdonly = NIL; else if (!compare_cstring (t,"PARSE") && !errflg) errflg = PARSE; } } /* give event to main program */ if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg); } /* Parse a namespace * Accepts: mail stream * current text pointer * parsed reply * Returns: namespace list, text pointer updated */ NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply) { NAMESPACE *ret = NIL; NAMESPACE *nam = NIL; NAMESPACE *prev = NIL; PARAMETER *par = NIL; if (*txtptr) { /* only if argument given */ /* ignore leading space */ while (**txtptr == ' ') ++*txtptr; switch (**txtptr) { case 'N': /* if NIL */ case 'n': ++*txtptr; /* bump past "N" */ ++*txtptr; /* bump past "I" */ ++*txtptr; /* bump past "L" */ break; case '(': ++*txtptr; /* skip past open paren */ while (**txtptr == '(') { ++*txtptr; /* skip past open paren */ prev = nam; /* note previous if any */ nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0, sizeof (NAMESPACE)); if (!ret) ret = nam; /* if first time note first namespace */ /* if previous link new block to it */ if (prev) prev->next = nam; nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL); /* ignore whitespace */ while (**txtptr == ' ') ++*txtptr; switch (**txtptr) { /* parse delimiter */ case 'N': case 'n': *txtptr += 3; /* bump past "NIL" */ break; case '"': if (*++*txtptr == '\\') nam->delimiter = *++*txtptr; else nam->delimiter = **txtptr; *txtptr += 2; /* bump past character and closing quote */ break; default: sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; *txtptr = NIL; /* stop parse */ return ret; } while (**txtptr == ' '){/* append new parameter to tail */ if (nam->param) par = par->next = mail_newbody_parameter (); else nam->param = par = mail_newbody_parameter (); if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL, NIL,NIL))) { mm_notify (stream,"Missing namespace extension attribute",WARN); stream->unhealthy = T; par->attribute = cpystr ("UNKNOWN"); } /* skip space */ while (**txtptr == ' ') ++*txtptr; if (**txtptr == '(') {/* have value list? */ char *att = par->attribute; ++*txtptr; /* yes */ do { /* parse each value */ if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL, NIL,LONGT))) { sprintf (LOCAL->tmp, "Missing value for namespace attribute %.80s",att); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; par->value = cpystr ("UNKNOWN"); } /* is there another value? */ if (**txtptr == ' ') par = par->next = mail_newbody_parameter (); } while (!par->value); } else { sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s", par->attribute); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; par->value = cpystr ("UNKNOWN"); } } if (**txtptr == ')') ++*txtptr; else { /* missing trailing paren */ sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; return ret; } } if (**txtptr == ')') { /* expected trailing paren? */ ++*txtptr; /* got it! */ break; } default: sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; *txtptr = NIL; /* stop parse now */ break; } } return ret; } /* Parse a thread node list * Accepts: mail stream * current text pointer * Returns: thread node list, text pointer updated */ THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr) { char *s; THREADNODE *ret = NIL; /* returned tree */ THREADNODE *last = NIL; /* last branch in this tree */ THREADNODE *parent = NIL; /* parent of current node */ THREADNODE *cur; /* current node */ while (**txtptr == '(') { /* see a thread? */ ++*txtptr; /* skip past open paren */ while (**txtptr != ')') { /* parse thread */ if (**txtptr == '(') { /* thread branch */ cur = imap_parse_thread (stream,txtptr); /* add to parent */ if (parent) parent = parent->next = cur; else { /* no parent, create dummy */ if (last) last = last->branch = mail_newthreadnode (NIL); /* new tree */ else ret = last = mail_newthreadnode (NIL); /* add to dummy parent */ last->next = parent = cur; } } /* threaded message number */ else if (isdigit (*(s = *txtptr)) && ((cur = mail_newthreadnode (NIL))->num = strtoul (*txtptr,(char **) txtptr,10))) { if (LOCAL->filter && !mail_elt (stream,cur->num)->searched) cur->num = NIL; /* make dummy if filtering and not searched */ /* add to parent */ if (parent) parent = parent->next = cur; /* no parent, start new thread */ else if (last) last = last->branch = parent = cur; /* create new tree */ else ret = last = parent = cur; } else { /* anything else is a bogon */ char tmp[MAILTMPLEN]; sprintf (tmp,"Bogus thread member: %.80s",s); mm_notify (stream,tmp,WARN); stream->unhealthy = T; return ret; } /* skip past any space */ if (**txtptr == ' ') ++*txtptr; } ++*txtptr; /* skip past end of thread */ parent = NIL; /* close this thread */ } return ret; /* return parsed thread */ } /* Parse RFC822 message header * Accepts: MAIL stream * envelope to parse into * header as sized text * stringlist if partial header */ void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr, STRINGLIST *stl) { ENVELOPE *nenv; /* parse what we can from this header */ rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL, net_host (LOCAL->netstream),stream->dtb->flags); if (*env) { /* need to merge this header into envelope? */ if (!(*env)->newsgroups) { /* need Newsgroups? */ (*env)->newsgroups = nenv->newsgroups; (*env)->ngpathexists = nenv->ngpathexists; 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; } if (!(*env)->sparep) { /* need spare pointer? */ (*env)->sparep = nenv->sparep; nenv->sparep = NIL; } mail_free_envelope (&nenv); (*env)->imapenvonly = NIL; /* have complete envelope now */ } /* otherwise set it to this envelope */ else (*env = nenv)->incomplete = stl ? T : NIL; } /* 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, unsigned char **txtptr,IMAPPARSEDREPLY *reply) { ENVELOPE *oenv = *env; char c = **txtptr; /* grab first character */ /* ignore leading spaces */ while (c == ' ') c = *++*txtptr; if (c) ++*txtptr; /* skip past first character */ 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,LONGT); (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); (*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, LONGT); (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); if (oenv) { /* need to merge old envelope? */ (*env)->newsgroups = oenv->newsgroups; oenv->newsgroups = NIL; (*env)->ngpathexists = oenv->ngpathexists; (*env)->followup_to = oenv->followup_to; oenv->followup_to = NIL; (*env)->references = oenv->references; oenv->references = NIL; mail_free_envelope(&oenv);/* free old envelope */ } /* have IMAP envelope components only */ else (*env)->imapenvonly = T; if (**txtptr != ')') { sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else ++*txtptr; /* skip past delimiter */ break; case 'N': /* if NIL */ case 'n': ++*txtptr; /* bump past "I" */ ++*txtptr; /* bump past "L" */ break; default: sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; 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,unsigned char **txtptr, IMAPPARSEDREPLY *reply) { ADDRESS *adr = NIL; char c = **txtptr; /* sniff at first character */ /* ignore leading spaces */ while (c == ' ') c = *++*txtptr; if (c) ++*txtptr; /* skip past open paren */ switch (c) { case '(': /* if envelope S-expression */ adr = imap_parse_address (stream,txtptr,reply); if (**txtptr != ')') { sprintf (LOCAL->tmp,"Junk at end of address list: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else ++*txtptr; /* skip past delimiter */ break; case 'N': /* if NIL */ case 'n': ++*txtptr; /* bump past "I" */ ++*txtptr; /* bump past "L" */ break; default: sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; 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,unsigned char **txtptr, IMAPPARSEDREPLY *reply) { long ingroup = 0; 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,LONGT); adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); if (**txtptr != ')') { /* handle trailing paren */ sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } 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 (!adr->mailbox) { /* end of group? */ /* decrement group if all looks well */ if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup; else { if (ingroup) { /* in a group? */ sprintf (LOCAL->tmp,/* yes, must be bad syntax */ "Junk in end of group: pn=%.80s al=%.80s dn=%.80s", adr->personal ? adr->personal : "", adr->adl ? adr->adl : "", adr->host ? adr->host : ""); mm_notify (stream,LOCAL->tmp,WARN); } else mm_notify (stream,"End of group encountered when not in group", WARN); stream->unhealthy = T; mail_free_address (&adr); adr = prev; prev = NIL; } } else if (!adr->host) { /* start of group? */ if (adr->personal || adr->adl) { sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s", adr->personal ? adr->personal : "", adr->adl ? adr->adl : ""); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; mail_free_address (&adr); adr = prev; prev = NIL; } else ++ingroup; /* in a group now */ } if (adr) { /* good address */ if (!ret) ret = adr; /* if first time note first adr */ /* if previous link new block to it */ if (prev) prev->next = adr; /* flush bogus personal name */ if (LOCAL->loser && adr->personal && strchr (adr->personal,'@')) fs_give ((void **) &adr->personal); } } break; case 'N': /* if NIL */ case 'n': *txtptr += 3; /* bump past NIL */ break; default: sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; break; } return ret; } /* IMAP parse flags * Accepts: current message cache * current text pointer * * Updates text pointer */ void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt, unsigned char **txtptr) { char *flag; char c = '\0'; struct { /* old flags */ unsigned int valid : 1; unsigned int seen : 1; unsigned int deleted : 1; unsigned int flagged : 1; unsigned int answered : 1; unsigned int draft : 1; unsigned long user_flags; } old; old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged; old.answered = elt->answered; old.draft = elt->draft; old.user_flags = elt->user_flags; 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->draft = elt->recent = NIL; do { /* parse list of flags */ /* point at a flag */ while (*(flag = ++*txtptr) == ' '); /* scan for end of flag */ while (**txtptr && (**txtptr != ' ') && (**txtptr != ')')) ++*txtptr; c = **txtptr; /* save delimiter */ **txtptr = '\0'; /* tie off flag */ if (!*flag) break; /* null flag */ /* if starts with \ must be sys flag */ else if (*flag == '\\') { if (!compare_cstring (flag,"\\Seen")) elt->seen = T; else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T; else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T; else if (!compare_cstring (flag,"\\Answered")) elt->answered = T; else if (!compare_cstring (flag,"\\Recent")) elt->recent = T; else if (!compare_cstring (flag,"\\Draft")) elt->draft = T; } /* otherwise user flag */ else elt->user_flags |= imap_parse_user_flag (stream,flag); } while (c && (c != ')')); if (c) ++*txtptr; /* bump past delimiter */ else { mm_notify (stream,"Unterminated flags list",WARN); stream->unhealthy = T; } if (!old.valid || (old.seen != elt->seen) || (old.deleted != elt->deleted) || (old.flagged != elt->flagged) || (old.answered != elt->answered) || (old.draft != elt->draft) || (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno); } /* IMAP parse user flag * Accepts: MAIL stream * flag name * Returns: flag bit position */ unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag) { long i; /* sniff through all user flags */ for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i]) if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i); return (unsigned long) 0; /* not found */ } /* IMAP parse atom-string * Accepts: MAIL stream * current text pointer * parsed reply * returned string length * Returns: string * * Updates text pointer */ unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply,unsigned long *len) { unsigned long i; unsigned char c,*s,*ret; /* ignore leading spaces */ for (c = **txtptr; c == ' '; c = *++*txtptr); switch (c) { case '"': /* quoted string? */ case '{': /* literal? */ ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL); break; default: /* must be atom */ for (c = *(s = *txtptr); /* find end of atom */ c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') && (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80); c = *++*txtptr); if ((i = *txtptr - s) != 0L) { /* atom ends at atom_special */ if (len) *len = i; /* return length of atom */ ret = strncpy ((char *) fs_get (i + 1),s,i); ret[i] = '\0'; /* tie off string */ } else { /* no atom found */ sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; if (len) *len = 0; ret = NIL; } break; } return ret; } /* IMAP parse string * Accepts: MAIL stream * current text pointer * parsed reply * mailgets data * returned string length * filter newline flag * Returns: string * * Updates text pointer */ unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr, IMAPPARSEDREPLY *reply,GETS_DATA *md, unsigned long *len,long flags) { char *st; char *string = NIL; unsigned long i,j,k; int bogon = NIL; unsigned char c = **txtptr; /* sniff at first character */ mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL); readprogress_t rp = (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL); /* ignore leading spaces */ while (c == ' ') c = *++*txtptr; if (c) st = ++*txtptr; /* remember start of string */ switch (c) { case '"': /* if quoted string */ i = 0; /* initial byte count */ /* search for end of string */ for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) { /* backslash quotes next character */ if (c == '\\') c = *++*txtptr; /* CHAR8 not permitted in quoted string */ if (!bogon && (bogon = (c & 0x80))) { sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x", (unsigned int) c); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else if (!c) { /* NUL not permitted either */ mm_notify (stream,"Unterminated quoted string",WARN); stream->unhealthy = T; if (len) *len = 0; /* punt, since may be at end of string */ return NIL; } } ++*txtptr; /* bump past delimiter */ string = (char *) fs_get ((size_t) i + 1); 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 */ if (md && mg) { /* have special routine to slurp string? */ STRING bs; if (md->first) { /* partial fetch? */ md->first--; /* restore origin octet */ md->last = i; /* number of octets that we got */ } INIT (&bs,mail_string,string,i); (*mg) (mail_read,&bs,i,md); } break; case 'N': /* if NIL */ case 'n': ++*txtptr; /* bump past "I" */ ++*txtptr; /* bump past "L" */ if (len) *len = 0; break; case '{': /* if literal string */ if (!isdigit (**txtptr)) { sprintf (LOCAL->tmp,"Invalid server literal length %.80s",*txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; /* read and discard */ i = 0; } /* get size of string */ else if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) { sprintf (LOCAL->tmp,"Absurd server literal length %lu",i); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; /* read and discard */ for (j = IMAPTMPLEN - 1; i; i -= j) { if (j > i) j = i; net_getbuffer (LOCAL->netstream,j,LOCAL->tmp); } } 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,LOCAL->netstream,i,md); } else { /* must slurp into free storage */ string = (char *) fs_get ((size_t) i + 1); *string = '\0'; /* init in case getbuffer fails */ /* get the literal */ if (rp) for (k = 0; (j = min ((long) MAILTMPLEN,(long) i)) != 0L; i -= j) { net_getbuffer (LOCAL->netstream,j,string + k); (*rp) (md,k += j); } else net_getbuffer (LOCAL->netstream,i,string); } fs_give ((void **) &reply->line); if (flags && string) /* need to filter newlines? */ for (st = string; (st = strpbrk (st,"\015\012\011")) != NULL; *st++ = ' '); /* get new reply text line */ if (!(reply->line = net_getline (LOCAL->netstream))) reply->line = cpystr (""); 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,(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; if (len) *len = 0; break; } return (unsigned char *) string; } /* Register text in IMAP cache * Accepts: MAIL stream * message number * IMAP segment specifier * header string list (if a HEADER section specifier) * sized text to register * Returns: non-zero if cache non-empty */ long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg, STRINGLIST *stl,SIZEDTEXT *text) { char *t,tmp[MAILTMPLEN]; unsigned long i; BODY *b; SIZEDTEXT *ret; STRINGLIST *stc; MESSAGECACHE *elt = mail_elt (stream,msgno); /* top-level header never does mailgets */ if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") || !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) { ret = &elt->private.msg.header.text; if (text) { /* don't do this if no text */ if (ret->data) fs_give ((void **) &ret->data); mail_free_stringlist (&elt->private.msg.lines); elt->private.msg.lines = stl; /* prevent cache reuse of .NOT */ if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.')) for (stc = stl; stc; stc = stc->next) stc->text.size = 0; if (stream->scache) { /* short caching puts it in the stream */ if (stream->msgno != msgno) { /* flush old stuff */ mail_free_envelope (&stream->env); mail_free_body (&stream->body); stream->msgno = msgno; } imap_parse_header (stream,&stream->env,text,stl); } /* regular caching */ else imap_parse_header (stream,&elt->private.msg.env,text,stl); } } /* top level text */ else if (!strcmp (seg,"TEXT")) { ret = &elt->private.msg.text.text; if (text && ret->data) fs_give ((void **) &ret->data); } else if (!*seg) { /* full message */ ret = &elt->private.msg.full.text; if (text && ret->data) fs_give ((void **) &ret->data); } else { /* nested, find non-contents specifier */ for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++); if (*t) *t++ = '\0'; /* tie off section from data specifier */ if (!(b = mail_body (stream,msgno,seg))) { sprintf (tmp,"Unknown section number: %.80s",seg); mm_notify (stream,tmp,WARN); stream->unhealthy = T; return NIL; } if (*t) { /* if a non-numberic subpart */ if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) && (!strcmp (t,"HEADER") || !strcmp (t,"0") || !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) { ret = &b->nested.msg->header.text; if (text) { if (ret->data) fs_give ((void **) &ret->data); mail_free_stringlist (&b->nested.msg->lines); b->nested.msg->lines = stl; /* prevent cache reuse of .NOT */ if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.')) for (stc = stl; stc; stc = stc->next) stc->text.size = 0; imap_parse_header (stream,&b->nested.msg->env,text,stl); } } else if (i && !strcmp (t,"TEXT")) { ret = &b->nested.msg->text.text; if (text && ret->data) fs_give ((void **) &ret->data); } /* otherwise it must be MIME */ else if (!strcmp (t,"MIME")) { ret = &b->mime.text; if (text && ret->data) fs_give ((void **) &ret->data); } else { sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t); mm_notify (stream,tmp,WARN); stream->unhealthy = T; return NIL; } } else { /* ordinary contents */ ret = &b->contents.text; if (text && ret->data) fs_give ((void **) &ret->data); } } if (text) { /* update cache if requested */ ret->data = text->data; ret->size = text->size; } return ret->data ? LONGT : NIL; } /* 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,BODY *body, unsigned char **txtptr,IMAPPARSEDREPLY *reply) { int i; char *s; PART *part = NIL; char c = **txtptr; /* grab first character */ /* ignore leading spaces */ while (c == ' ') c = *++*txtptr; if (c) ++*txtptr; /* skip past first character */ switch (c) { /* dispatch on first character */ case '(': /* body structure list */ if (**txtptr == '(') { /* multipart body? */ body->type= TYPEMULTIPART;/* yes, set its type */ do { /* instantiate new body part */ 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); } while (**txtptr == '(');/* for each body part */ if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) ucase (body->subtype); else { mm_notify (stream,"Missing multipart subtype",WARN); stream->unhealthy = T; body->subtype = cpystr (rfc822_default_subtype (body->type)); } if (**txtptr == ' ') /* multipart parameters */ body->parameter = imap_parse_body_parameter (stream,txtptr,reply); if (**txtptr == ' ') { /* disposition */ imap_parse_disposition (stream,body,txtptr,reply); if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP; } if (**txtptr == ' ') { /* language */ body->language = imap_parse_language (stream,txtptr,reply); if (LOCAL->cap.extlevel < BODYEXTLANG) LOCAL->cap.extlevel = BODYEXTLANG; } if (**txtptr == ' ') { /* location */ body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC; } while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); if (**txtptr != ')') { /* validate ending */ sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } 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 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) { ucase (s); /* application always gets uppercase form */ for (i = 0; /* look in existing table */ (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++); if (i <= TYPEMAX) { /* only if found a slot */ body->type = i; /* set body type */ if (!body_types[i]) { /* assign empty slot */ body_types[i] = s; s = NIL; /* don't free this string */ } } if (s) fs_give ((void **) &s); } if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) ucase (body->subtype); /* parse subtype */ else { mm_notify (stream,"Missing body subtype",WARN); stream->unhealthy = T; body->subtype = cpystr (rfc822_default_subtype (body->type)); } body->parameter = imap_parse_body_parameter (stream,txtptr,reply); body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL, LONGT); if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) { ucase (s); /* application always gets uppercase form */ for (i = 0; /* search for body encoding */ (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]); i++); if (i > ENCMAX) body->encoding = ENCOTHER; else { /* only if found a slot */ body->encoding = i; /* set body encoding */ /* assign empty slot */ if (!body_encodings[i]) { body_encodings[i] = s; s = NIL; /* don't free this string */ } } if (s) fs_give ((void **) &s); } /* parse size of contents in bytes */ body->size.bytes = strtoul (*txtptr,(char **) txtptr,10); switch (body->type) { /* possible extra stuff */ case TYPEMESSAGE: /* message envelope and body */ /* non MESSAGE/RFC822 is basic type */ if (strcmp (body->subtype,"RFC822")) break; { /* make certain server sends an envelope */ ENVELOPE *env = NIL; imap_parse_envelope (stream,&env,txtptr,reply); if (!env) { mm_notify (stream,"Missing body message envelope",WARN); stream->unhealthy = T; fs_give ((void **) &body->subtype); body->subtype = cpystr ("RFC822_MISSING_ENVELOPE"); break; } (body->nested.msg = mail_newmsg ())->env = env; } body->nested.msg->body = mail_newbody (); imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply); /* drop into text case */ case TYPETEXT: /* size in lines */ body->size.lines = strtoul (*txtptr,(char **) txtptr,10); break; default: /* otherwise nothing special */ break; } if (**txtptr == ' ') { /* extension data - md5 */ body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5; } if (**txtptr == ' ') { /* disposition */ imap_parse_disposition (stream,body,txtptr,reply); if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP; } if (**txtptr == ' ') { /* language */ body->language = imap_parse_language (stream,txtptr,reply); if (LOCAL->cap.extlevel < BODYEXTLANG) LOCAL->cap.extlevel = BODYEXTLANG; } if (**txtptr == ' ') { /* location */ body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC; } while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); if (**txtptr != ')') { /* validate ending */ sprintf (LOCAL->tmp,"Junk at end of body part: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } else ++*txtptr; /* skip past delimiter */ } 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",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; 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, unsigned char **txtptr, IMAPPARSEDREPLY *reply) { PARAMETER *ret = NIL; PARAMETER *par = NIL; char c,*s; /* ignore leading spaces */ while ((c = *(*txtptr)++) == ' '); if (c == '(') do { /* parse parameter list */ /* 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, LONGT))) { mm_notify (stream,"Missing parameter attribute",WARN); stream->unhealthy = T; par->attribute = cpystr ("UNKNOWN"); } if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){ sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; 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; case '\0': mm_notify (stream,"Unterminated parameter list", WARN); stream->unhealthy = T; break; default: sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; break; } } while (c && (c != ')')); /* 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, (char *) (*txtptr) - 1); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } 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,BODY *body, unsigned char **txtptr,IMAPPARSEDREPLY *reply) { switch (*++*txtptr) { case '(': ++*txtptr; /* skip open paren */ body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL, LONGT); body->disposition.parameter = imap_parse_body_parameter (stream,txtptr,reply); if (**txtptr != ')') { /* validate ending */ sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s", (char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; } 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",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; /* 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,unsigned 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,LONGT)) != NULL) { (ret = mail_newstringlist ())->text.data = (unsigned char *) 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,unsigned char **txtptr, IMAPPARSEDREPLY *reply) { STRINGLIST *stl = NIL; STRINGLIST *stc = NIL; unsigned char *t = *txtptr; /* parse the list */ if (*t++ == '(') while (*t != ')') { if (stl) stc = stc->next = mail_newstringlist (); else stc = stl = mail_newstringlist (); /* parse astring */ if (!(stc->text.data = imap_parse_astring (stream,&t,reply,&stc->text.size))) { sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; mail_free_stringlist (&stl); break; } else if (*t == ' ') ++t; /* another token follows */ } 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,unsigned char **txtptr, IMAPPARSEDREPLY *reply) { unsigned long i,j; switch (*++*txtptr) { /* action depends upon first character */ case '(': while (**txtptr && (**txtptr != ')')) imap_parse_extension (stream,txtptr,reply); if (**txtptr) ++*txtptr; /* bump past closing parenthesis */ break; case '"': /* if quoted string */ while ((*++*txtptr != '"') && **txtptr) if (**txtptr == '\\') ++*txtptr; if (**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,(char **) txtptr,10)) != 0L) do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1), LOCAL->tmp); while (i -= j); /* get new reply text line */ if (!(reply->line = net_getline (LOCAL->netstream))) reply->line = cpystr (""); 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,(char **) txtptr,10); break; default: sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr); mm_notify (stream,LOCAL->tmp,WARN); stream->unhealthy = T; /* try to skip to next space */ while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')')); break; } } /* IMAP parse capabilities * Accepts: MAIL stream * capability list */ void imap_parse_capabilities (MAILSTREAM *stream,char *t) { char *s,*r; unsigned long i; THREADER *thr,*th; if (!LOCAL->gotcapability) { /* need to save previous capabilities? */ /* no, flush threaders */ if ((thr = LOCAL->cap.threader) != NULL) while ((th = thr) != NULL) { fs_give ((void **) &th->name); thr = th->next; fs_give ((void **) &th); } /* zap capabilities */ memset (&LOCAL->cap,0,sizeof (LOCAL->cap)); LOCAL->gotcapability = T; /* flag that capabilities arrived */ } for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) { if (!compare_cstring (t,"IMAP4")) LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; else if (!compare_cstring (t,"IMAP4rev1")) LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T; else if (!compare_cstring (t,"IMAP2bis")) LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T; else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T; else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T; else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T; else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T; else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T; else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T; else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T; else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T; else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T; else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T; else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T; else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T; else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T; else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T; else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T; else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T; else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T; else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T; else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T; else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T; else if (!compare_cstring (t,"X-GM-EXT-1")) LOCAL->cap.x_gm_ext1 = T; else if (((t[0] == 'S') || (t[0] == 's')) && ((t[1] == 'O') || (t[1] == 'o')) && ((t[2] == 'R') || (t[2] == 'r')) && ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T; /* capability with value? */ else if ((s = strchr (t,'=')) != NULL) { *s++ = '\0'; /* separate token from value */ if (!compare_cstring (t,"THREAD") && !LOCAL->loser) { THREADER *thread = (THREADER *) fs_get (sizeof (THREADER)); thread->name = cpystr (s); thread->dispatch = NIL; thread->next = LOCAL->cap.threader; LOCAL->cap.threader = thread; } else if (!compare_cstring (t,"AUTH")) { if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) && (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i); else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T; } } /* ignore other capabilities */ } /* disable LOGIN if PLAIN also advertised */ if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) && (LOCAL->cap.auth & (1 << i)) && (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth &= ~(1 << i); } /* IMAP load cache * Accepts: MAIL stream * sequence * flags * Returns: parsed reply from fetch */ IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags) { int i = 2; char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH" : "FETCH"; IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl; if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, flags & FT_UID); args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence; args[1] = &aarg; aarg.type = ATOM; aenv.type = ATOM; aenv.text = (void *) "ENVELOPE"; ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel]; axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs; ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer; abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE"; atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)"; if (LEVELIMAP4 (stream)) { /* include UID if IMAP4 or IMAP4rev1 */ aarg.text = (void *) "(UID"; if (flags & FT_NEEDENV) { /* if need envelopes */ args[i++] = &aenv; /* include envelope */ /* extra header poop if IMAP4rev1 */ if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) { args[i++] = &ahhr; /* header header */ if (axtr.text) args[i++] = &axtr; args[i++] = &ahtr; /* header trailer */ } /* fetch body if requested */ if (flags & FT_NEEDBODY) args[i++] = &abdy; } args[i++] = &atrl; /* fetch trailer */ } /* easy if IMAP2 */ else aarg.text = (void *) (flags & FT_NEEDENV) ? ((flags & FT_NEEDBODY) ? "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" : "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST"; args[i] = NIL; /* tie off command */ return imap_send (stream,cmd,args); } /* Reform sequence for losing server that doesn't handle ranges right * Accepts: MAIL stream * sequence * non-zero if UID * Returns: sequence */ char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags) { unsigned long i,j,star; char *s,*t,*tl,*rs; /* can't win if empty */ if (!stream->nmsgs) return sequence; /* get highest possible range value */ star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs; /* flush old reformed sequence */ if (LOCAL->reform) fs_give ((void **) &LOCAL->reform); rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence)); for (s = sequence; (t = strpbrk (s,",:")) != NULL; ) switch (*t++) { case ',': /* single message */ strncpy (rs,s,i = t - s); /* copy string up to that point */ rs += i; /* advance destination pointer */ s += i; /* and source */ break; case ':': /* message range */ i = (*s == '*') ? star : strtoul (s,NIL,10); if (*t == '*') { /* range ends with star */ j = star; tl = t+1; } else { /* numeric range end */ j = strtoul (t,(char **) &tl,10); if (!tl) tl = t + strlen (t); } if (i <= j) { /* if first less than second */ if (*tl) tl++; /* skip past end of range if present */ strncpy (rs,s,i = tl - s);/* copy string up to that point */ rs += i; /* advance destination and source pointers */ s += i; } else { /* here's the workaround for losing servers */ strncpy (rs,t,i = tl - t);/* swap the order */ rs[i] = ':'; /* delimit */ strncpy (rs+i+1,s,j = (t-1) - s); rs += i + 1 + j; /* advance destination pointer */ if (*tl) *rs++ = *tl++; /* write trailing delimiter if present */ s = tl; /* advance source pointer */ } } if (*s) strcpy (rs,s); /* write remainder of sequence */ else *rs = '\0'; /* tie off string */ return LOCAL->reform; } /* IMAP return host name * Accepts: MAIL stream * Returns: host name */ char *imap_host (MAILSTREAM *stream) { if (stream->dtb != &imapdriver) fatal ("imap_host called on non-IMAP stream!"); /* return host name on stream if open */ return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) : ".NO-IMAP-CONNECTION."; } /* IMAP return IMAP capability structure * Accepts: MAIL stream * Returns: IMAP capability structure */ IMAPCAP *imap_cap (MAILSTREAM *stream) { if (stream->dtb != &imapdriver) fatal ("imap_cap called on non-IMAP stream!"); return &LOCAL->cap; /* return capability structure */ }