From 148a5844b7023d80889caf95b7eca582c4f99e0a Mon Sep 17 00:00:00 2001 From: Unknown <> Date: Wed, 20 Feb 2008 00:34:44 +0000 Subject: [PATCH] add files for 2008-02-20T00:34:44Z --- src/c-client/imap4r1.c | 5670 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5670 insertions(+) create mode 100644 src/c-client/imap4r1.c diff --git a/src/c-client/imap4r1.c b/src/c-client/imap4r1.c new file mode 100644 index 0000000..157cb58 --- /dev/null +++ b/src/c-client/imap4r1.c @@ -0,0 +1,5670 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 June 1988 + * Last Edited: 19 February 2008 + * + * 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 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 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 */ +} 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 *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); +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); + +/* 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; + 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]; + /* 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 (&sdb)) do if (imap_valid (s) && pmatch (s,mbx)) + mm_lsub (stream,NIL,s,NIL); + while (s = sm_read (&sdb)); /* until no more subscriptions */ +} + +/* 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)); + 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, + (mb.tlssslv23 ? NIL : NET_TLSCLIENT) | + (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 (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.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->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 (!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,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; + int ok; + char tag[16]; + char *lsterr = NIL; + AUTHENTICATOR *at; + IMAPPARSEDREPLY *reply; + for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua && + (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) { + 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); + if (imap_soutr (stream,tmp)) { + /* hide client authentication responses */ + if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T; + ok = (*at->client) (imap_challenge,imap_response,"imap",mb,stream, + &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); + } + 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; + 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 { + pwd[0] = 0; /* prompt user for password */ + mm_login (mb,usr,pwd,trial++); + 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); + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + 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 *response,unsigned long size) +{ + MAILSTREAM *stream = (MAILSTREAM *) s; + unsigned long i,j,ret; + char *t,*u; + if (response) { /* make CRLFless BASE64 string */ + if (size) { + 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 = 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) while (t = thr) { + 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); + /* 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)) { + 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) + /* 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")) { + 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) {/* 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 ((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) { + /* 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 *s = LOCAL->tmp; + /* search through mailbox */ + for (s = '\0',i = 1; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->sequence) { + /* prepend with comma if not first time */ + if (LOCAL->tmp[0]) *s++ = ','; + 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 - LOCAL->tmp) > (IMAPTMPLEN - 50)) { + mm_log ("Excessively complex sequence",ERROR); + return NIL; + } + } + /* now do as UID EXPUNGE */ + ret = imap_expunge (stream,s = cpystr (s),EX_UID); + fs_give ((void **) &s); + } + } + /* 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)) + 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) while (t = thr) { + 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 identifer + * 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 identifer + * 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 identifer + * 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; +} + +/* 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) while (arg = *arglst++) { + *s++ = ' '; /* delimit argument with space */ + switch (arg->type) { + case ATOM: /* atom */ + for (t = (char *) arg->text; *t; *s++ = *t++); + break; + case NUMBER: /* number */ + sprintf (s,"%lu",(unsigned long) arg->text); + s += strlen (s); + break; + case FLAGS: /* flag list as a single string */ + if (*(t = (char *) arg->text) != '(') { + *s++ = '('; /* wrap parens around string */ + while (*t) *s++ = *t++; + *s++ = ')'; /* wrap parens around string */ + } + else while (*t) *s++ = *t++; + break; + case ASTRING: /* atom or string, must be literal? */ + st.size = strlen ((char *) (st.data = (unsigned char *) arg->text)); + if (reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) + return reply; + break; + case LITERAL: /* literal, as a stringstruct */ + if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply; + break; + + case LIST: /* list of strings */ + list = (STRINGLIST *) arg->text; + c = '('; /* 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)) return reply; + c = ' '; /* prefix character for subsequent strings */ + } + while (list = list->next); + *s++ = ')'; /* close list */ + break; + case SEARCHPROGRAM: /* search program */ + if (reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text, + CMDBASE+MAXCOMMAND)) + 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)) + 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) { /* 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)) return reply; + *s++ = ' '; /* delimit with space */ + } + if (reply = imap_send_literal (stream,tag,&s,map->message)) + 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); + *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 */ +} + +/* 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) do { + *s = imap_send_spgm_trim (base,*s," HEADER "); + if (reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) + return reply; + *(*s)++ = ' '; + if (reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) + return reply; + } while (hdr = hdr->next); + 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)) + return reply; + for (t = ") ("; *t; *(*s)++ = *t++); + if (reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) + 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)) + 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)) return reply; + *(*s)++ = ')'; /* close glue */ + if (reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) + 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))) { + /* 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)); + /* and locate the text after it */ + t = strtok_r (NIL,"\n",&r); + /* 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 ((!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,' ')) *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 */ + } + /* 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)); + 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)) { + /* 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)); + } + 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) { + 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[1]) { + *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)) 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; + /* ignore extension flags */ + } + while (t = strtok_r (NIL," ",&r)); + 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)) { + /* 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) { /* 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 identifer/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 identifer 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,' ')) t = s; + if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){ + if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10); + else if (t = strpbrk (s," )")) 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,"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),' ')) *s++ = '\0'; + if (s) { /* have argument? */ + ntfy = NIL; /* suppress mm_notify if normal SELECT data */ + if (!compare_cstring (t,"UIDVALIDITY") && + ((j = strtoul (s,NIL,10)) != 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)) + elt->private.uid = 0; + } + else if (!compare_cstring (t,"UIDNEXT")) + stream->uid_last = strtoul (s,NIL,10) - 1; + 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)) 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)); + } + + else if (!compare_cstring (t,"CAPABILITY")) + imap_parse_capabilities (stream,s); + 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 pase 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; + 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)++); + 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)->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; + ++*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; + while (c != ')') { /* parse list of flags */ + /* point at a flag */ + while (*(flag = ++*txtptr) == ' '); + /* scan for end of flag */ + while (**txtptr != ' ' && **txtptr != ')') ++*txtptr; + c = **txtptr; /* save delimiter */ + **txtptr = '\0'; /* tie off flag */ + if (!*flag) break; /* null flag */ + /* 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); + } + ++*txtptr; /* bump past delimiter */ + 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) { /* 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; + 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 */ + /* get size of string */ + 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 */ + do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1), + LOCAL->tmp); + while (i -= j); + } + 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); 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"); *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)++); + 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)) + 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)) { + 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]) fs_give ((void **) &s); + else body_types[i]=s; /* assign empty slot */ + } + } + if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) + 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)) { + 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 */ + if (body_encodings[i]) fs_give ((void **) &s); + /* assign empty slot */ + else body_encodings[i] = 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; + 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)++) == ' '); + /* parse parameter list */ + if (c == '(') while (c != ')') { + /* append new parameter to tail */ + if (ret) par = par->next = mail_newbody_parameter (); + else ret = par = mail_newbody_parameter (); + if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL, + 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; + default: + sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } + } + /* empty parameter, must be NIL */ + else if (((c == 'N') || (c == 'n')) && + ((*(s = *txtptr) == 'I') || (*s == 'i')) && + ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2; + else { + sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c, + (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)) { + (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 != ')') imap_parse_extension (stream,txtptr,reply); + ++*txtptr; /* bump past closing parenthesis */ + break; + case '"': /* if quoted string */ + while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr; + ++*txtptr; /* bump past closing quote */ + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "N" */ + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + case '{': /* get size of literal */ + ++*txtptr; /* bump past open squiggle */ + if (i = strtoul (*txtptr,(char **) txtptr,10)) 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) while (th = thr) { + 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 (((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,'=')) { + *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,",:"); ) 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 */ +} -- 2.40.0