1 /* ========================================================================
2  * Copyright 1988-2008 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
16  *
17  * Author:	Mark Crispin
18  *		UW Technology
19  *		University of Washington
20  *		Seattle, WA  98195
21  *		Internet: MRC@CAC.Washington.EDU
22  *
23  * Date:	15 June 1988
24  * Last Edited:	8 May 2008
25  *
26  * This original version of this file is
27  * Copyright 1988 Stanford University
28  * and was developed in the Symbolic Systems Resources Group of the Knowledge
29  * Systems Laboratory at Stanford University in 1987-88, and was funded by the
30  * Biomedical Research Technology Program of the National Institutes of Health
31  * under grant number RR-00785.
32  */
33 
34 
35 #include <ctype.h>
36 #include <stdio.h>
37 #include <time.h>
38 #include "c-client.h"
39 #include "imap4r1.h"
40 
41 /* Parameters */
42 
43 #define IMAPLOOKAHEAD 20	/* envelope lookahead */
44 #define IMAPUIDLOOKAHEAD 1000	/* UID lookahead */
45 #define IMAPTCPPORT (long) 143	/* assigned TCP contact port */
46 #define IMAPSSLPORT (long) 993	/* assigned SSL TCP contact port */
47 #define MAXCOMMAND 1000		/* RFC 2683 guideline for cmd line length */
48 #define IDLETIMEOUT (long) 30	/* defined in RFC 3501 */
49 #define MAXSERVERLIT 0x7ffffffe	/* maximum server literal size
50 				 * must be smaller than 4294967295
51 				 */
52 
53 
54 /* Parsed reply message from imap_reply */
55 
56 typedef struct imap_parsed_reply {
57   unsigned char *line;		/* original reply string pointer */
58   unsigned char *tag;		/* command tag this reply is for */
59   unsigned char *key;		/* reply keyword */
60   unsigned char *text;		/* subsequent text */
61 } IMAPPARSEDREPLY;
62 
63 
64 #define IMAPTMPLEN 16*MAILTMPLEN
65 
66 
67 /* IMAP4 I/O stream local data */
68 
69 typedef struct imap_local {
70   NETSTREAM *netstream;		/* TCP I/O stream */
71   IMAPPARSEDREPLY reply;	/* last parsed reply */
72   MAILSTATUS *stat;		/* status to fill in */
73   IMAPCAP cap;			/* server capabilities */
74   char *appendmailbox;		/* mailbox being appended to */
75   unsigned int uidsearch : 1;	/* UID searching */
76   unsigned int byeseen : 1;	/* saw a BYE response */
77 				/* got implicit capabilities */
78   unsigned int gotcapability : 1;
79   unsigned int sensitive : 1;	/* sensitive data in progress */
80   unsigned int tlsflag : 1;	/* TLS session */
81   unsigned int tlssslv23 : 1;	/* TLS using SSLv23 client method */
82   unsigned int notlsflag : 1;	/* TLS not used in session */
83   unsigned int sslflag : 1;	/* SSL session */
84   unsigned int novalidate : 1;	/* certificate not validated */
85   unsigned int filter : 1;	/* filter SEARCH/SORT/THREAD results */
86   unsigned int loser : 1;	/* server is a loser */
87   unsigned int saslcancel : 1;	/* SASL cancelled by protocol */
88   long authflags;		/* required flags for authenticators */
89   unsigned long sortsize;	/* sort return data size */
90   unsigned long *sortdata;	/* sort return data */
91   struct {
92     unsigned long uid;		/* last UID returned */
93     unsigned long msgno;	/* last msgno returned */
94   } lastuid;
95   NAMESPACE **namespace;	/* namespace return data */
96   THREADNODE *threaddata;	/* thread return data */
97   char *referral;		/* last referral */
98   char *prefix;			/* find prefix */
99   char *user;			/* logged-in user */
100   char *reform;			/* reformed sequence */
101   char tmp[IMAPTMPLEN];		/* temporary buffer */
102   SEARCHSET *lookahead;		/* fetch lookahead */
103 } IMAPLOCAL;
104 
105 
106 /* Convenient access to local data */
107 
108 #define LOCAL ((IMAPLOCAL *) stream->local)
109 
110 /* Arguments to imap_send() */
111 
112 typedef struct imap_argument {
113   int type;			/* argument type */
114   void *text;			/* argument text */
115 } IMAPARG;
116 
117 
118 /* imap_send() argument types */
119 
120 #define ATOM 0
121 #define NUMBER 1
122 #define FLAGS 2
123 #define ASTRING 3
124 #define LITERAL 4
125 #define LIST 5
126 #define SEARCHPROGRAM 6
127 #define SORTPROGRAM 7
128 #define BODYTEXT 8
129 #define BODYPEEK 9
130 #define BODYCLOSE 10
131 #define SEQUENCE 11
132 #define LISTMAILBOX 12
133 #define MULTIAPPEND 13
134 #define SNLIST 14
135 #define MULTIAPPENDREDO 15
136 
137 
138 /* Append data */
139 
140 typedef struct append_data {
141   append_t af;
142   void *data;
143   char *flags;
144   char *date;
145   STRING *message;
146 } APPENDDATA;
147 
148 /* Function prototypes */
149 
150 DRIVER *imap_valid (char *name);
151 void *imap_parameters (long function,void *value);
152 void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
153 void imap_list (MAILSTREAM *stream,char *ref,char *pat);
154 void imap_lsub (MAILSTREAM *stream,char *ref,char *pat);
155 void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
156 		     char *contents);
157 long imap_subscribe (MAILSTREAM *stream,char *mailbox);
158 long imap_unsubscribe (MAILSTREAM *stream,char *mailbox);
159 long imap_create (MAILSTREAM *stream,char *mailbox);
160 long imap_delete (MAILSTREAM *stream,char *mailbox);
161 long imap_rename (MAILSTREAM *stream,char *old,char *newname);
162 long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2);
163 long imap_status (MAILSTREAM *stream,char *mbx,long flags);
164 MAILSTREAM *imap_open (MAILSTREAM *stream);
165 IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
166 			     char *usr,char *tmp);
167 long imap_anon (MAILSTREAM *stream,char *tmp);
168 long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr);
169 long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr);
170 void *imap_challenge (void *stream,unsigned long *len);
171 long imap_response (void *stream,char *s,unsigned long size);
172 void imap_close (MAILSTREAM *stream,long options);
173 void imap_fast (MAILSTREAM *stream,char *sequence,long flags);
174 void imap_flags (MAILSTREAM *stream,char *sequence,long flags);
175 long imap_overview (MAILSTREAM *stream,overview_t ofn);
176 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
177 			  long flags);
178 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
179 		   unsigned long first,unsigned long last,STRINGLIST *lines,
180 		   long flags);
181 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno);
182 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid);
183 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
184 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
185 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
186 			  SORTPGM *pgm,long flags);
187 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
188 			 SEARCHPGM *spg,long flags);
189 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
190 			      SEARCHPGM *spg,long flags);
191 long imap_ping (MAILSTREAM *stream);
192 void imap_check (MAILSTREAM *stream);
193 long imap_expunge (MAILSTREAM *stream,char *sequence,long options);
194 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
195 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
196 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
197 			   char *flags,char *date,STRING *message,
198 			   APPENDDATA *map,long options);
199 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
200 				     char *flags,char *date,STRING *message);
201 
202 void imap_gc (MAILSTREAM *stream,long gcflags);
203 void imap_gc_body (BODY *body);
204 void imap_capability (MAILSTREAM *stream);
205 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]);
206 
207 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]);
208 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s);
209 long imap_soutr (MAILSTREAM *stream,char *string);
210 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
211 				    SIZEDTEXT *as,long wildok,char *limit);
212 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
213 				    STRING *st);
214 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
215 				 char **s,SEARCHPGM *pgm,char *limit);
216 char *imap_send_spgm_trim (char *base,char *s,char *text);
217 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
218 				 char **s,SEARCHSET *set,char *prefix,
219 				 char *limit);
220 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
221 				  char **s,char *name,STRINGLIST *list,
222 				  char *limit);
223 void imap_send_sdate (char **s,char *name,unsigned short date);
224 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag);
225 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text);
226 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text);
227 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
228 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
229 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy);
230 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
231 				 IMAPPARSEDREPLY *reply);
232 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr);
233 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
234 			STRINGLIST *stl);
235 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
236 			  unsigned char **txtptr,IMAPPARSEDREPLY *reply);
237 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
238 			     IMAPPARSEDREPLY *reply);
239 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
240 			     IMAPPARSEDREPLY *reply);
241 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
242 		       unsigned char **txtptr);
243 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag);
244 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
245 			  IMAPPARSEDREPLY *reply,unsigned long *len);
246 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
247 				  IMAPPARSEDREPLY *reply,GETS_DATA *md,
248 				  unsigned long *len,long flags);
249 void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr,
250 		      IMAPPARSEDREPLY *reply);
251 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
252 				unsigned char **txtptr,IMAPPARSEDREPLY *reply);
253 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
254 				      unsigned char **txtptr,
255 				      IMAPPARSEDREPLY *reply);
256 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
257 			     unsigned char **txtptr,IMAPPARSEDREPLY *reply);
258 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
259 				 IMAPPARSEDREPLY *reply);
260 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
261 				   IMAPPARSEDREPLY *reply);
262 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
263 			   IMAPPARSEDREPLY *reply);
264 void imap_parse_capabilities (MAILSTREAM *stream,char *t);
265 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags);
266 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags);
267 
268 /* Driver dispatch used by MAIL */
269 
270 DRIVER imapdriver = {
271   "imap",			/* driver name */
272 				/* driver flags */
273   DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN,
274   (DRIVER *) NIL,		/* next driver */
275   imap_valid,			/* mailbox is valid for us */
276   imap_parameters,		/* manipulate parameters */
277   imap_scan,			/* scan mailboxes */
278   imap_list,			/* find mailboxes */
279   imap_lsub,			/* find subscribed mailboxes */
280   imap_subscribe,		/* subscribe to mailbox */
281   imap_unsubscribe,		/* unsubscribe from mailbox */
282   imap_create,			/* create mailbox */
283   imap_delete,			/* delete mailbox */
284   imap_rename,			/* rename mailbox */
285   imap_status,			/* status of mailbox */
286   imap_open,			/* open mailbox */
287   imap_close,			/* close mailbox */
288   imap_fast,			/* fetch message "fast" attributes */
289   imap_flags,			/* fetch message flags */
290   imap_overview,		/* fetch overview */
291   imap_structure,		/* fetch message envelopes */
292   NIL,				/* fetch message header */
293   NIL,				/* fetch message body */
294   imap_msgdata,			/* fetch partial message */
295   imap_uid,			/* unique identifier */
296   imap_msgno,			/* message number */
297   imap_flag,			/* modify flags */
298   NIL,				/* per-message modify flags */
299   imap_search,			/* search for message based on criteria */
300   imap_sort,			/* sort messages */
301   imap_thread,			/* thread messages */
302   imap_ping,			/* ping mailbox to see if still alive */
303   imap_check,			/* check for new messages */
304   imap_expunge,			/* expunge deleted messages */
305   imap_copy,			/* copy messages to another mailbox */
306   imap_append,			/* append string message to mailbox */
307   imap_gc			/* garbage collect stream */
308 };
309 
310 				/* prototype stream */
311 MAILSTREAM imapproto = {&imapdriver};
312 
313 				/* driver parameters */
314 static unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
315 static long imap_lookahead = IMAPLOOKAHEAD;
316 static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
317 static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD;
318 static long imap_defaultport = 0;
319 static long imap_sslport = 0;
320 static long imap_tryssl = NIL;
321 static long imap_prefetch = IMAPLOOKAHEAD;
322 static long imap_closeonerror = NIL;
323 static imapenvelope_t imap_envelope = NIL;
324 static imapreferral_t imap_referral = NIL;
325 static char *imap_extrahdrs = NIL;
326 
327 				/* constants */
328 static char *hdrheader[] = {
329   "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
330   "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
331   "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
332   "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
333   "BODY.PEEK[HEADER.FIELDS (Newsgroups"
334 };
335 static char *hdrtrailer ="Followup-To References)]";
336 
337 /* IMAP validate mailbox
338  * Accepts: mailbox name
339  * Returns: our driver if name is valid, NIL otherwise
340  */
341 
imap_valid(char * name)342 DRIVER *imap_valid (char *name)
343 {
344   return mail_valid_net (name,&imapdriver,NIL,NIL);
345 }
346 
347 
348 /* IMAP manipulate driver parameters
349  * Accepts: function code
350  *	    function-dependent value
351  * Returns: function-dependent return value
352  */
353 
imap_parameters(long function,void * value)354 void *imap_parameters (long function,void *value)
355 {
356   switch ((int) function) {
357   case GET_NAMESPACE:
358     if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace &&
359 	!((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace)
360       imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL);
361     value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace;
362     break;
363   case GET_THREADERS:
364     value = (void *)
365       ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader;
366     break;
367   case SET_FETCHLOOKAHEAD:	/* must use pointer from GET_FETCHLOOKAHEAD */
368     fatal ("SET_FETCHLOOKAHEAD not permitted");
369   case GET_FETCHLOOKAHEAD:
370     value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead;
371     break;
372   case SET_MAXLOGINTRIALS:
373     imap_maxlogintrials = (long) value;
374     break;
375   case GET_MAXLOGINTRIALS:
376     value = (void *) imap_maxlogintrials;
377     break;
378   case SET_LOOKAHEAD:
379     imap_lookahead = (long) value;
380     break;
381   case GET_LOOKAHEAD:
382     value = (void *) imap_lookahead;
383     break;
384   case SET_UIDLOOKAHEAD:
385     imap_uidlookahead = (long) value;
386     break;
387   case GET_UIDLOOKAHEAD:
388     value = (void *) imap_uidlookahead;
389     break;
390 
391   case SET_IMAPPORT:
392     imap_defaultport = (long) value;
393     break;
394   case GET_IMAPPORT:
395     value = (void *) imap_defaultport;
396     break;
397   case SET_SSLIMAPPORT:
398     imap_sslport = (long) value;
399     break;
400   case GET_SSLIMAPPORT:
401     value = (void *) imap_sslport;
402     break;
403   case SET_PREFETCH:
404     imap_prefetch = (long) value;
405     break;
406   case GET_PREFETCH:
407     value = (void *) imap_prefetch;
408     break;
409   case SET_CLOSEONERROR:
410     imap_closeonerror = (long) value;
411     break;
412   case GET_CLOSEONERROR:
413     value = (void *) imap_closeonerror;
414     break;
415   case SET_IMAPENVELOPE:
416     imap_envelope = (imapenvelope_t) value;
417     break;
418   case GET_IMAPENVELOPE:
419     value = (void *) imap_envelope;
420     break;
421   case SET_IMAPREFERRAL:
422     imap_referral = (imapreferral_t) value;
423     break;
424   case GET_IMAPREFERRAL:
425     value = (void *) imap_referral;
426     break;
427   case SET_IMAPEXTRAHEADERS:
428     imap_extrahdrs = (char *) value;
429     break;
430   case GET_IMAPEXTRAHEADERS:
431     value = (void *) imap_extrahdrs;
432     break;
433   case SET_IMAPTRYSSL:
434     imap_tryssl = (long) value;
435     break;
436   case GET_IMAPTRYSSL:
437     value = (void *) imap_tryssl;
438     break;
439   case SET_FETCHLOOKAHEADLIMIT:
440     imap_fetchlookaheadlimit = (long) value;
441     break;
442   case GET_FETCHLOOKAHEADLIMIT:
443     value = (void *) imap_fetchlookaheadlimit;
444     break;
445 
446   case SET_IDLETIMEOUT:
447     fatal ("SET_IDLETIMEOUT not permitted");
448   case GET_IDLETIMEOUT:
449     value = (void *) IDLETIMEOUT;
450     break;
451   default:
452     value = NIL;		/* error case */
453     break;
454   }
455   return value;
456 }
457 
458 /* IMAP scan mailboxes
459  * Accepts: mail stream
460  *	    reference
461  *	    pattern to search
462  *	    string to scan
463  */
464 
imap_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)465 void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
466 {
467   imap_list_work (stream,"SCAN",ref,pat,contents);
468 }
469 
470 
471 /* IMAP list mailboxes
472  * Accepts: mail stream
473  *	    reference
474  *	    pattern to search
475  */
476 
imap_list(MAILSTREAM * stream,char * ref,char * pat)477 void imap_list (MAILSTREAM *stream,char *ref,char *pat)
478 {
479   imap_list_work (stream,"LIST",ref,pat,NIL);
480 }
481 
482 
483 /* IMAP list subscribed mailboxes
484  * Accepts: mail stream
485  *	    reference
486  *	    pattern to search
487  */
488 
imap_lsub(MAILSTREAM * stream,char * ref,char * pat)489 void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
490 {
491   void *sdb = NIL;
492   char *s,mbx[MAILTMPLEN];
493 				/* do it on the server */
494   imap_list_work (stream,"LSUB",ref,pat,NIL);
495   if (*pat == '{') {		/* if remote pattern, must be IMAP */
496     if (!imap_valid (pat)) return;
497     ref = NIL;			/* good IMAP pattern, punt reference */
498   }
499 				/* if remote reference, must be valid IMAP */
500   if (ref && (*ref == '{') && !imap_valid (ref)) return;
501 				/* kludgy application of reference */
502   if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
503   else strcpy (mbx,pat);
504 
505   if (s = sm_read (&sdb)) do if (imap_valid (s) && pmatch (s,mbx))
506     mm_lsub (stream,NIL,s,NIL);
507   while (s = sm_read (&sdb));	/* until no more subscriptions */
508 }
509 
510 /* IMAP find list of mailboxes
511  * Accepts: mail stream
512  *	    list command
513  *	    reference
514  *	    pattern to search
515  *	    string to scan
516  */
517 
imap_list_work(MAILSTREAM * stream,char * cmd,char * ref,char * pat,char * contents)518 void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
519 		     char *contents)
520 {
521   MAILSTREAM *st = stream;
522   int pl;
523   char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN];
524   IMAPARG *args[4],aref,apat,acont;
525   if (ref && *ref) {		/* have a reference? */
526     if (!(imap_valid (ref) &&	/* make sure valid IMAP name and open stream */
527 	  ((stream && LOCAL && LOCAL->netstream) ||
528 	   (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return;
529 				/* calculate prefix length */
530     pl = strchr (ref,'}') + 1 - ref;
531     strncpy (prefix,ref,pl);	/* build prefix */
532     prefix[pl] = '\0';		/* tie off prefix */
533     ref += pl;			/* update reference */
534   }
535   else {
536     if (!(imap_valid (pat) &&	/* make sure valid IMAP name and open stream */
537 	  ((stream && LOCAL && LOCAL->netstream) ||
538 	   (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return;
539 				/* calculate prefix length */
540     pl = strchr (pat,'}') + 1 - pat;
541     strncpy (prefix,pat,pl);	/* build prefix */
542     prefix[pl] = '\0';		/* tie off prefix */
543     pat += pl;			/* update reference */
544   }
545   LOCAL->prefix = prefix;	/* note prefix */
546   if (contents) {		/* want to do a scan? */
547     if (LEVELSCAN (stream)) {	/* make sure permitted */
548       args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
549       aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
550       apat.type = LISTMAILBOX; apat.text = (void *) pat;
551       acont.type = ASTRING; acont.text = (void *) contents;
552       imap_send (stream,cmd,args);
553     }
554     else mm_log ("Scan not valid on this IMAP server",ERROR);
555   }
556 
557   else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */
558     args[0] = &aref; args[1] = &apat; args[2] = NIL;
559     aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
560     apat.type = LISTMAILBOX; apat.text = (void *) pat;
561 				/* referrals armed? */
562     if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) {
563 				/* yes, convert LIST -> RLIST */
564       if (!compare_cstring (cmd,"LIST")) cmd = "RLIST";
565 				/* and convert LSUB -> RLSUB */
566       else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB";
567     }
568     imap_send (stream,cmd,args);
569   }
570   else if (LEVEL1176 (stream)) {/* convert to IMAP2 format wildcard */
571 				/* kludgy application of reference */
572     if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
573     else strcpy (mbx,pat);
574     for (s = mbx; *s; s++) if (*s == '%') *s = '*';
575     args[0] = &apat; args[1] = NIL;
576     apat.type = LISTMAILBOX; apat.text = (void *) mbx;
577     if (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
578 	  strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) &&
579 	!strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
580       LOCAL->cap.rfc1176 = NIL;	/* must be RFC-1064 */
581   }
582   LOCAL->prefix = NIL;		/* no more prefix */
583 				/* close temporary stream if we made one */
584   if (stream != st) mail_close (stream);
585 }
586 
587 /* IMAP subscribe to mailbox
588  * Accepts: mail stream
589  *	    mailbox to add to subscription list
590  * Returns: T on success, NIL on failure
591  */
592 
imap_subscribe(MAILSTREAM * stream,char * mailbox)593 long imap_subscribe (MAILSTREAM *stream,char *mailbox)
594 {
595   MAILSTREAM *st = stream;
596   long ret = ((stream && LOCAL && LOCAL->netstream) ||
597 	      (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
598 		imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
599 			     "Subscribe" : "Subscribe Mailbox",NIL) : NIL;
600 				/* toss out temporary stream */
601   if (st != stream) mail_close (stream);
602   return ret;
603 }
604 
605 
606 /* IMAP unsubscribe to mailbox
607  * Accepts: mail stream
608  *	    mailbox to delete from manage list
609  * Returns: T on success, NIL on failure
610  */
611 
imap_unsubscribe(MAILSTREAM * stream,char * mailbox)612 long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
613 {
614   MAILSTREAM *st = stream;
615   long ret = ((stream && LOCAL && LOCAL->netstream) ||
616 	      (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
617 		imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
618 			     "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL;
619 				/* toss out temporary stream */
620   if (st != stream) mail_close (stream);
621   return ret;
622 }
623 
624 /* IMAP create mailbox
625  * Accepts: mail stream
626  *	    mailbox name to create
627  * Returns: T on success, NIL on failure
628  */
629 
imap_create(MAILSTREAM * stream,char * mailbox)630 long imap_create (MAILSTREAM *stream,char *mailbox)
631 {
632   return imap_manage (stream,mailbox,"Create",NIL);
633 }
634 
635 
636 /* IMAP delete mailbox
637  * Accepts: mail stream
638  *	    mailbox name to delete
639  * Returns: T on success, NIL on failure
640  */
641 
imap_delete(MAILSTREAM * stream,char * mailbox)642 long imap_delete (MAILSTREAM *stream,char *mailbox)
643 {
644   return imap_manage (stream,mailbox,"Delete",NIL);
645 }
646 
647 
648 /* IMAP rename mailbox
649  * Accepts: mail stream
650  *	    old mailbox name
651  *	    new mailbox name
652  * Returns: T on success, NIL on failure
653  */
654 
imap_rename(MAILSTREAM * stream,char * old,char * newname)655 long imap_rename (MAILSTREAM *stream,char *old,char *newname)
656 {
657   return imap_manage (stream,old,"Rename",newname);
658 }
659 
660 /* IMAP manage a mailbox
661  * Accepts: mail stream
662  *	    mailbox to manipulate
663  *	    command to execute
664  *	    optional second argument
665  * Returns: T on success, NIL on failure
666  */
667 
imap_manage(MAILSTREAM * stream,char * mailbox,char * command,char * arg2)668 long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
669 {
670   MAILSTREAM *st = stream;
671   IMAPPARSEDREPLY *reply;
672   long ret = NIL;
673   char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
674   IMAPARG *args[3],ambx,amb2;
675   imapreferral_t ir =
676     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
677   ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
678   amb2.text = (void *) mbx2;
679   args[0] = &ambx; args[1] = args[2] = NIL;
680 				/* require valid names and open stream */
681   if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) &&
682       (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) &&
683       ((stream && LOCAL && LOCAL->netstream) ||
684        (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
685     if (arg2) args[1] = &amb2;	/* second arg present? */
686     if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) &&
687 	ir && LOCAL->referral) {
688       long code = -1;
689       switch (*command) {	/* which command was it? */
690       case 'S': code = REFSUBSCRIBE; break;
691       case 'U': code = REFUNSUBSCRIBE; break;
692       case 'C': code = REFCREATE; break;
693       case 'D': code = REFDELETE; break;
694       case 'R': code = REFRENAME; break;
695       default:
696 	fatal ("impossible referral command");
697       }
698       if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code)))
699 	ret = imap_manage (NIL,mailbox,command,(*command == 'R') ?
700 			   (mailbox + strlen (mailbox) + 1) : NIL);
701     }
702     mm_log (reply->text,ret ? NIL : ERROR);
703 				/* toss out temporary stream */
704     if (st != stream) mail_close (stream);
705   }
706   return ret;
707 }
708 
709 /* IMAP status
710  * Accepts: mail stream
711  *	    mailbox name
712  *	    status flags
713  * Returns: T on success, NIL on failure
714  */
715 
imap_status(MAILSTREAM * stream,char * mbx,long flags)716 long imap_status (MAILSTREAM *stream,char *mbx,long flags)
717 {
718   IMAPARG *args[3],ambx,aflg;
719   char tmp[MAILTMPLEN];
720   NETMBX mb;
721   unsigned long i;
722   long ret = NIL;
723   MAILSTREAM *tstream = NIL;
724 				/* use given stream if (rev1 or halfopen) and
725 				   right host */
726   if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) &&
727 	 mail_usable_network_stream (stream,mbx)) ||
728 	(stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))))
729     return NIL;
730 				/* parse mailbox name */
731   mail_valid_net_parse (mbx,&mb);
732   args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
733   ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
734   if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */
735     imapreferral_t ir;
736     aflg.type = FLAGS; aflg.text = (void *) tmp;
737     args[1] = &aflg; args[2] = NIL;
738     tmp[0] = tmp[1] = '\0';	/* build flag list */
739     if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
740     if (flags & SA_RECENT) strcat (tmp," RECENT");
741     if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
742     if (flags & SA_UIDNEXT) strcat (tmp," UIDNEXT");
743     if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY");
744     tmp[0] = '(';
745     strcat (tmp,")");
746 				/* send "STATUS mailbox flag" */
747     if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
748     else if ((ir = (imapreferral_t)
749 	      mail_parameters (stream,GET_IMAPREFERRAL,NIL)) &&
750 	     LOCAL->referral &&
751 	     (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS)))
752       ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL));
753   }
754 
755 				/* IMAP2 way */
756   else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) {
757     MAILSTATUS status;
758     status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY);
759     status.messages = stream->nmsgs;
760     status.recent = stream->recent;
761     status.unseen = 0;
762     if (flags & SA_UNSEEN) {	/* must search to get unseen messages */
763 				/* clear search vector */
764       for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
765       if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL)))
766 	for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
767 	  if (mail_elt (stream,i)->searched) status.unseen++;
768     }
769     strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox);
770 				/* pass status to main program */
771     mm_status (stream,tmp,&status);
772     ret = T;			/* note success */
773   }
774   if (tstream) mail_close (tstream);
775   return ret;			/* success */
776 }
777 
778 /* IMAP open
779  * Accepts: stream to open
780  * Returns: stream to use on success, NIL on failure
781  */
782 
imap_open(MAILSTREAM * stream)783 MAILSTREAM *imap_open (MAILSTREAM *stream)
784 {
785   unsigned long i,j;
786   char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN];
787   NETMBX mb;
788   IMAPPARSEDREPLY *reply = NIL;
789   imapreferral_t ir =
790     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
791 				/* return prototype for OP_PROTOTYPE call */
792   if (!stream) return &imapproto;
793   mail_valid_net_parse (stream->mailbox,&mb);
794   usr[0] = '\0';		/* initially no user name */
795   if (LOCAL) {			/* if stream opened earlier by us */
796 				/* recycle if still alive */
797     if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) {
798       i = stream->silent;	/* temporarily mark silent */
799       stream->silent = T;	/* don't give mm_exists() events */
800       j = imap_ping (stream);	/* learn if stream still alive */
801       stream->silent = i;	/* restore prior state */
802       if (j) {			/* was stream still alive? */
803 	sprintf (tmp,"Reusing connection to %s",net_host (LOCAL->netstream));
804 	if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
805 				  LOCAL->user);
806 	if (!stream->silent) mm_log (tmp,(long) NIL);
807 				/* unselect if now want halfopen */
808 	if (stream->halfopen) imap_send (stream,"UNSELECT",NIL);
809       }
810       else imap_close (stream,NIL);
811     }
812     else imap_close (stream,NIL);
813   }
814 				/* copy flags from name */
815   if (mb.dbgflag) stream->debug = T;
816   if (mb.readonlyflag) stream->rdonly = T;
817   if (mb.anoflag) stream->anonymous = T;
818   if (mb.secflag) stream->secure = T;
819   if (mb.trysslflag || imap_tryssl) stream->tryssl = T;
820 
821   if (!LOCAL) {			/* open new connection if no recycle */
822     NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
823     unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
824     unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT;
825     stream->local =		/* instantiate localdata */
826       (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL));
827 				/* assume IMAP2bis server */
828     LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
829 				/* in case server is a loser */
830     if (mb.loser) LOCAL->loser = T;
831 				/* desirable authenticators */
832     LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) |
833       (mb.authuser[0] ? AU_AUTHUSER : NIL);
834     /* IMAP connection open logic is more complex than net_open() normally
835      * deals with, because of the simap and rimap hacks.
836      * If the session is anonymous, a specific port is given, or if /ssl or
837      * /tls is set, do net_open() since those conditions override everything
838      * else.
839      */
840     if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag)
841       reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps",
842 					    sslport)) ?
843 	imap_reply (stream,NIL) : NIL;
844     /*
845      * No overriding conditions, so get the best connection that we can.  In
846      * order, attempt to open via simap, tryssl, rimap, and finally TCP.
847      */
848 				/* try simap */
849     else if (reply = imap_rimap (stream,"*imap",&mb,usr,tmp));
850     else if (ssld &&		/* try tryssl if enabled */
851 	     (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) &&
852 	     (LOCAL->netstream =
853 	      net_open_work (ssld,mb.host,"*imaps",sslport,mb.port,
854 			     (mb.novalidate ? NET_NOVALIDATECERT : 0) |
855 			     NET_SILENT | NET_TRYSSL))) {
856       if (net_sout (LOCAL->netstream,"",0)) {
857 	mb.sslflag = T;
858 	reply = imap_reply (stream,NIL);
859       }
860       else {			/* flush fake SSL stream */
861 	net_close (LOCAL->netstream);
862 	LOCAL->netstream = NIL;
863       }
864     }
865 				/* try rimap first, then TCP */
866     else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) &&
867 	     (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL)))
868       reply = imap_reply (stream,NIL);
869 				/* make sure greeting is good */
870     if (!reply || strcmp (reply->tag,"*") ||
871 	(strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) {
872       if (reply) mm_log (reply->text,ERROR);
873       return NIL;		/* lost during greeting */
874     }
875 
876 				/* if connected and not preauthenticated */
877     if (LOCAL->netstream && strcmp (reply->key,"PREAUTH")) {
878       sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
879 				/* get server capabilities */
880       if (!LOCAL->gotcapability) imap_capability (stream);
881       if (LOCAL->netstream &&	/* does server support STARTTLS? */
882 	  stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag &&
883 	  imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) {
884 	mb.tlsflag = T;		/* TLS OK, get into TLS at this end */
885 	LOCAL->netstream->dtb = ssld;
886 	if (!(LOCAL->netstream->stream =
887 	      (*stls) (LOCAL->netstream->stream,mb.host,
888 		       (mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
889 		       (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) {
890 				/* drat, drop this connection */
891 	  if (LOCAL->netstream) net_close (LOCAL->netstream);
892 	  LOCAL->netstream = NIL;
893 	}
894 				/* get capabilities now that TLS in effect */
895 	if (LOCAL->netstream) imap_capability (stream);
896       }
897       else if (mb.tlsflag) {	/* user specified /tls but can't do it */
898 	mm_log ("Unable to negotiate TLS with this server",ERROR);
899 	return NIL;
900       }
901       if (LOCAL->netstream) {	/* still in the land of the living? */
902 	if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
903 				/* remote name for authentication */
904 	  strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)?
905 		   net_remotehost (LOCAL->netstream) :
906 		   net_host (LOCAL->netstream),NETMAXHOST-1);
907 	  mb.host[NETMAXHOST-1] = '\0';
908 	}
909 				/* need new capabilities after login */
910 	LOCAL->gotcapability = NIL;
911 	if (!(stream->anonymous ? imap_anon (stream,tmp) :
912 	      (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) :
913 	       imap_login (stream,&mb,tmp,usr)))) {
914 				/* failed, is there a referral? */
915 	  if (ir && LOCAL->referral &&
916 	      (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) {
917 	    imap_close (stream,NIL);
918 	    fs_give ((void **) &stream->mailbox);
919 				/* set as new mailbox name to open */
920 	    stream->mailbox = s;
921 	    return imap_open (stream);
922 	  }
923 	  return NIL;		/* authentication failed */
924 	}
925 	else if (ir && LOCAL->referral &&
926 		 (s = (*ir) (stream,LOCAL->referral,REFAUTH))) {
927 	  imap_close (stream,NIL);
928 	  fs_give ((void **) &stream->mailbox);
929 	  stream->mailbox = s;	/* set as new mailbox name to open */
930 				/* recurse to log in on real site */
931 	  return imap_open (stream);
932 	}
933       }
934     }
935 				/* get server capabilities again */
936     if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream);
937 				/* save state for future recycling */
938     if (mb.tlsflag) LOCAL->tlsflag = T;
939     if (mb.tlssslv23) LOCAL->tlssslv23 = T;
940     if (mb.notlsflag) LOCAL->notlsflag = T;
941     if (mb.sslflag) LOCAL->sslflag = T;
942     if (mb.novalidate) LOCAL->novalidate = T;
943     if (mb.loser) LOCAL->loser = T;
944   }
945 
946   if (LOCAL->netstream) {	/* still have a connection? */
947     stream->perm_seen = stream->perm_deleted = stream->perm_answered =
948       stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T;
949     stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff;
950     stream->sequence++;		/* bump sequence number */
951     sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
952 	     net_host (LOCAL->netstream) : mb.host);
953     if (!((i = net_port (LOCAL->netstream)) & 0xffff0000))
954       sprintf (tmp + strlen (tmp),":%lu",i);
955     strcat (tmp,"/imap");
956     if (LOCAL->tlsflag) strcat (tmp,"/tls");
957     if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
958     if (LOCAL->notlsflag) strcat (tmp,"/notls");
959     if (LOCAL->sslflag) strcat (tmp,"/ssl");
960     if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
961     if (LOCAL->loser) strcat (tmp,"/loser");
962     if (stream->secure) strcat (tmp,"/secure");
963     if (stream->rdonly) strcat (tmp,"/readonly");
964     if (stream->anonymous) strcat (tmp,"/anonymous");
965     else {			/* record user name */
966       if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr);
967       if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
968 				LOCAL->user);
969     }
970     strcat (tmp,"}");
971 
972     if (!stream->halfopen) {	/* wants to open a mailbox? */
973       IMAPARG *args[2];
974       IMAPARG ambx;
975       ambx.type = ASTRING;
976       ambx.text = (void *) mb.mailbox;
977       args[0] = &ambx; args[1] = NIL;
978       stream->nmsgs = 0;
979       if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
980 					     "EXAMINE": "SELECT",args))) {
981 	strcat (tmp,mb.mailbox);/* mailbox name */
982 	if (!stream->nmsgs && !stream->silent)
983 	  mm_log ("Mailbox is empty",(long) NIL);
984 				/* note if an INBOX or not */
985 	stream->inbox = !compare_cstring (mb.mailbox,"INBOX");
986       }
987       else if (ir && LOCAL->referral &&
988 	       (s = (*ir) (stream,LOCAL->referral,REFSELECT))) {
989 	imap_close (stream,NIL);
990 	fs_give ((void **) &stream->mailbox);
991 	stream->mailbox = s;	/* set as new mailbox name to open */
992 	return imap_open (stream);
993       }
994       else {
995 	mm_log (reply->text,ERROR);
996 	if (imap_closeonerror) return NIL;
997 	stream->halfopen = T;	/* let him keep it half-open */
998       }
999     }
1000     if (stream->halfopen) {	/* half-open connection? */
1001       strcat (tmp,"<no_mailbox>");
1002 				/* make sure dummy message counts */
1003       mail_exists (stream,(long) 0);
1004       mail_recent (stream,(long) 0);
1005     }
1006     fs_give ((void **) &stream->mailbox);
1007     stream->mailbox = cpystr (tmp);
1008   }
1009 				/* success if stream open */
1010   return LOCAL->netstream ? stream : NIL;
1011 }
1012 
1013 /* IMAP rimap connect
1014  * Accepts: MAIL stream
1015  *	    NETMBX specification
1016  *	    service to use
1017  *	    user name
1018  *	    scratch buffer
1019  * Returns: parsed reply if success, else NIL
1020  */
1021 
imap_rimap(MAILSTREAM * stream,char * service,NETMBX * mb,char * usr,char * tmp)1022 IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
1023 			     char *usr,char *tmp)
1024 {
1025   unsigned long i;
1026   char c[2];
1027   NETSTREAM *tstream;
1028   IMAPPARSEDREPLY *reply = NIL;
1029 				/* try rimap open */
1030   if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) {
1031 				/* if success, see if reasonable banner */
1032     if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
1033       i = 0;			/* copy to buffer */
1034       do tmp[i++] = *c;
1035       while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
1036 	     (*c != '\012') && (i < (MAILTMPLEN-1)));
1037       tmp[i] = '\0';		/* tie off */
1038 				/* snarfed a valid greeting? */
1039       if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) &&
1040 	  (*c == '\012') &&
1041 	  !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){
1042 				/* parse line as IMAP */
1043 	imap_parse_unsolicited (stream,reply);
1044 				/* make sure greeting is good */
1045 	if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
1046 	  LOCAL->netstream = tstream;
1047 	  return reply;		/* return success */
1048 	}
1049       }
1050     }
1051     net_close (tstream);	/* failed, punt the temporary netstream */
1052   }
1053   return NIL;
1054 }
1055 
1056 /* IMAP log in as anonymous
1057  * Accepts: stream to authenticate
1058  *	    scratch buffer
1059  * Returns: T on success, NIL on failure
1060  */
1061 
imap_anon(MAILSTREAM * stream,char * tmp)1062 long imap_anon (MAILSTREAM *stream,char *tmp)
1063 {
1064   IMAPPARSEDREPLY *reply;
1065   char *s = net_localhost (LOCAL->netstream);
1066   if (LOCAL->cap.authanon) {
1067     char tag[16];
1068     unsigned long i;
1069     char *broken = "[CLOSED] IMAP connection broken (anonymous auth)";
1070     sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1071 				/* build command */
1072     sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag);
1073     if (!imap_soutr (stream,tmp)) {
1074       mm_log (broken,ERROR);
1075       return NIL;
1076     }
1077     if (imap_challenge (stream,&i)) imap_response (stream,s,strlen (s));
1078 				/* get response */
1079     if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken);
1080 				/* what we wanted? */
1081     if (compare_cstring (reply->tag,tag)) {
1082 				/* abort if don't have tagged response */
1083       while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1084 	imap_soutr (stream,"*");
1085     }
1086   }
1087   else {
1088     IMAPARG *args[2];
1089     IMAPARG ausr;
1090     ausr.type = ASTRING;
1091     ausr.text = (void *) s;
1092     args[0] = &ausr; args[1] = NIL;
1093 				/* send "LOGIN anonymous <host>" */
1094     reply = imap_send (stream,"LOGIN ANONYMOUS",args);
1095   }
1096 				/* success if reply OK */
1097   if (imap_OK (stream,reply)) return T;
1098   mm_log (reply->text,ERROR);
1099   return NIL;
1100 }
1101 
1102 /* IMAP authenticate
1103  * Accepts: stream to authenticate
1104  *	    parsed network mailbox structure
1105  *	    scratch buffer
1106  *	    place to return user name
1107  * Returns: T on success, NIL on failure
1108  */
1109 
imap_auth(MAILSTREAM * stream,NETMBX * mb,char * tmp,char * usr)1110 long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
1111 {
1112   unsigned long trial,ua;
1113   int ok;
1114   char tag[16];
1115   char *lsterr = NIL;
1116   AUTHENTICATOR *at;
1117   IMAPPARSEDREPLY *reply;
1118   for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua &&
1119        (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
1120     if (lsterr) {		/* previous authenticator failed? */
1121       sprintf (tmp,"Retrying using %s authentication after %.80s",
1122 	       at->name,lsterr);
1123       mm_log (tmp,NIL);
1124       fs_give ((void **) &lsterr);
1125     }
1126     trial = 0;			/* initial trial count */
1127     tmp[0] = '\0';		/* no error */
1128     do {			/* gensym a new tag */
1129       if (lsterr) {		/* previous attempt with this one failed? */
1130 	sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
1131 	mm_log (tmp,WARN);
1132 	fs_give ((void **) &lsterr);
1133       }
1134       LOCAL->saslcancel = NIL;
1135       sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1136 				/* build command */
1137       sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name);
1138       if (imap_soutr (stream,tmp)) {
1139 				/* hide client authentication responses */
1140 	if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T;
1141 	ok = (*at->client) (imap_challenge,imap_response,"imap",mb,stream,
1142 			    &trial,usr);
1143 	LOCAL->sensitive = NIL;	/* unhide */
1144 				/* make sure have a response */
1145 	if (!(reply = &LOCAL->reply)->tag)
1146 	  reply = imap_fake (stream,tag,
1147 			     "[CLOSED] IMAP connection broken (authenticate)");
1148 	else if (compare_cstring (reply->tag,tag))
1149 	  while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1150 	    imap_soutr (stream,"*");
1151 				/* good if SASL ok and success response */
1152 	if (ok && imap_OK (stream,reply)) return T;
1153 	if (!trial) {		/* if main program requested cancellation */
1154 	  mm_log ("IMAP Authentication cancelled",ERROR);
1155 	  return NIL;
1156 	}
1157 				/* no error if protocol-initiated cancel */
1158 	lsterr = cpystr (reply->text);
1159       }
1160     }
1161     while (LOCAL->netstream && !LOCAL->byeseen && trial &&
1162 	   (trial < imap_maxlogintrials));
1163   }
1164   if (lsterr) {			/* previous authenticator failed? */
1165     if (!LOCAL->saslcancel) {	/* don't do this if a cancel */
1166       sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr);
1167       mm_log (tmp,ERROR);
1168     }
1169     fs_give ((void **) &lsterr);
1170   }
1171   return NIL;			/* ran out of authenticators */
1172 }
1173 
1174 /* IMAP login
1175  * Accepts: stream to login
1176  *	    parsed network mailbox structure
1177  *	    scratch buffer of length MAILTMPLEN
1178  *	    place to return user name
1179  * Returns: T on success, NIL on failure
1180  */
1181 
imap_login(MAILSTREAM * stream,NETMBX * mb,char * pwd,char * usr)1182 long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
1183 {
1184   unsigned long trial = 0;
1185   IMAPPARSEDREPLY *reply;
1186   IMAPARG *args[3];
1187   IMAPARG ausr,apwd;
1188   long ret = NIL;
1189   if (stream->secure)		/* never do LOGIN if want security */
1190     mm_log ("Can't do secure authentication with this server",ERROR);
1191 				/* never do LOGIN if server disabled it */
1192   else if (LOCAL->cap.logindisabled)
1193     mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR);
1194   else if (mb->authuser[0])	/* never do LOGIN with /authuser */
1195     mm_log ("Can't do /authuser with this server",ERROR);
1196   else {			/* OK to try login */
1197     ausr.type = apwd.type = ASTRING;
1198     ausr.text = (void *) usr;
1199     apwd.text = (void *) pwd;
1200     args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
1201     do {
1202       pwd[0] = 0;		/* prompt user for password */
1203       mm_login (mb,usr,pwd,trial++);
1204       if (pwd[0]) {		/* send login command if have password */
1205 	LOCAL->sensitive = T;	/* hide this command */
1206 				/* send "LOGIN usr pwd" */
1207 	if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args)))
1208 	  ret = LONGT;		/* success */
1209 	else {
1210 	  mm_log (reply->text,WARN);
1211 	  if (!LOCAL->referral && (trial == imap_maxlogintrials))
1212 	    mm_log ("Too many login failures",ERROR);
1213 	}
1214 	LOCAL->sensitive = NIL;	/* unhide */
1215       }
1216 				/* user refused to give password */
1217       else mm_log ("Login aborted",ERROR);
1218     } while (!ret && pwd[0] && (trial < imap_maxlogintrials) &&
1219 	     LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral);
1220   }
1221   memset (pwd,0,MAILTMPLEN);	/* erase password */
1222   return ret;
1223 }
1224 
1225 /* Get challenge to authenticator in binary
1226  * Accepts: stream
1227  *	    pointer to returned size
1228  * Returns: challenge or NIL if not challenge
1229  */
1230 
imap_challenge(void * s,unsigned long * len)1231 void *imap_challenge (void *s,unsigned long *len)
1232 {
1233   char tmp[MAILTMPLEN];
1234   void *ret = NIL;
1235   MAILSTREAM *stream = (MAILSTREAM *) s;
1236   IMAPPARSEDREPLY *reply = NIL;
1237 				/* get tagged response or challenge */
1238   while (stream && LOCAL->netstream &&
1239 	 (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) &&
1240 	 !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
1241 				/* parse challenge if have one */
1242   if (stream && LOCAL->netstream && reply && reply->tag &&
1243       (*reply->tag == '+') && !reply->tag[1] && reply->text &&
1244       !(ret = rfc822_base64 ((unsigned char *) reply->text,
1245 			     strlen (reply->text),len))) {
1246     sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s",
1247 	     (char *) reply->text);
1248     mm_log (tmp,ERROR);
1249   }
1250   return ret;
1251 }
1252 
1253 
1254 /* Send authenticator response in BASE64
1255  * Accepts: MAIL stream
1256  *	    string to send
1257  *	    length of string
1258  * Returns: T if successful, else NIL
1259  */
1260 
imap_response(void * s,char * response,unsigned long size)1261 long imap_response (void *s,char *response,unsigned long size)
1262 {
1263   MAILSTREAM *stream = (MAILSTREAM *) s;
1264   unsigned long i,j,ret;
1265   char *t,*u;
1266   if (response) {		/* make CRLFless BASE64 string */
1267     if (size) {
1268       for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
1269 	   j < i; j++) if (t[j] > ' ') *u++ = t[j];
1270       *u = '\0';		/* tie off string for mm_dlog() */
1271       if (stream->debug) mail_dlog (t,LOCAL->sensitive);
1272 				/* append CRLF */
1273       *u++ = '\015'; *u++ = '\012';
1274       ret = net_sout (LOCAL->netstream,t,u - t);
1275       fs_give ((void **) &t);
1276     }
1277     else ret = imap_soutr (stream,"");
1278   }
1279   else {			/* abort requested */
1280     ret = imap_soutr (stream,"*");
1281     LOCAL->saslcancel = T;	/* mark protocol-requested SASL cancel */
1282   }
1283   return ret;
1284 }
1285 
1286 /* IMAP close
1287  * Accepts: MAIL stream
1288  *	    option flags
1289  */
1290 
imap_close(MAILSTREAM * stream,long options)1291 void imap_close (MAILSTREAM *stream,long options)
1292 {
1293   THREADER *thr,*t;
1294   IMAPPARSEDREPLY *reply;
1295   if (stream && LOCAL) {	/* send "LOGOUT" */
1296     if (!LOCAL->byeseen) {	/* don't even think of doing it if saw a BYE */
1297 				/* expunge silently if requested */
1298       if (options & CL_EXPUNGE)
1299 	imap_send (stream,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL);
1300       if (LOCAL->netstream &&
1301 	  !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
1302 	mm_log (reply->text,WARN);
1303     }
1304 				/* close NET connection if still open */
1305     if (LOCAL->netstream) net_close (LOCAL->netstream);
1306     LOCAL->netstream = NIL;
1307 				/* free up memory */
1308     if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
1309     if (LOCAL->namespace) {
1310       mail_free_namespace (&LOCAL->namespace[0]);
1311       mail_free_namespace (&LOCAL->namespace[1]);
1312       mail_free_namespace (&LOCAL->namespace[2]);
1313       fs_give ((void **) &LOCAL->namespace);
1314     }
1315     if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
1316 				/* flush threaders */
1317     if (thr = LOCAL->cap.threader) while (t = thr) {
1318       fs_give ((void **) &t->name);
1319       thr = t->next;
1320       fs_give ((void **) &t);
1321     }
1322     if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
1323     if (LOCAL->user) fs_give ((void **) &LOCAL->user);
1324     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
1325     if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
1326 				/* nuke the local data */
1327     fs_give ((void **) &stream->local);
1328   }
1329 }
1330 
1331 /* IMAP fetch fast information
1332  * Accepts: MAIL stream
1333  *	    sequence
1334  *	    option flags
1335  *
1336  * Generally, imap_structure is preferred
1337  */
1338 
imap_fast(MAILSTREAM * stream,char * sequence,long flags)1339 void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
1340 {
1341   IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID);
1342   if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
1343 }
1344 
1345 
1346 /* IMAP fetch flags
1347  * Accepts: MAIL stream
1348  *	    sequence
1349  *	    option flags
1350  */
1351 
imap_flags(MAILSTREAM * stream,char * sequence,long flags)1352 void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
1353 {				/* send "FETCH sequence FLAGS" */
1354   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1355   IMAPPARSEDREPLY *reply;
1356   IMAPARG *args[3],aseq,aatt;
1357   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1358 						     flags & FT_UID);
1359   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1360   aatt.type = ATOM; aatt.text = (void *) "FLAGS";
1361   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1362   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1363     mm_log (reply->text,ERROR);
1364 }
1365 
1366 /* IMAP fetch overview
1367  * Accepts: MAIL stream, sequence bits set
1368  *	    pointer to overview return function
1369  * Returns: T if successful, NIL otherwise
1370  */
1371 
imap_overview(MAILSTREAM * stream,overview_t ofn)1372 long imap_overview (MAILSTREAM *stream,overview_t ofn)
1373 {
1374   MESSAGECACHE *elt;
1375   ENVELOPE *env;
1376   OVERVIEW ov;
1377   char *s,*t;
1378   unsigned long i,start,last,len,slen;
1379   if (!LOCAL->netstream) return NIL;
1380 				/* build overview sequence */
1381   for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
1382     if ((elt = mail_elt (stream,i))->sequence) {
1383       if (!elt->private.msg.env) {
1384 	if (s) {		/* continuing a sequence */
1385 	  if (i == last + 1) last = i;
1386 	  else {		/* end of range */
1387 	    if (last != start) sprintf (t,":%lu,%lu",last,i);
1388 	    else sprintf (t,",%lu",i);
1389 	    if ((len - (slen = (t += strlen (t)) - s)) < 20) {
1390 	      fs_resize ((void **) &s,len += MAILTMPLEN);
1391 	      t = s + slen;	/* relocate current pointer */
1392 	    }
1393 	    start = last = i;	/* begin a new range */
1394 	  }
1395 	}
1396 	else {			/* first time, start new buffer */
1397 	  s = (char *) fs_get (len = MAILTMPLEN);
1398 	  sprintf (s,"%lu",start = last = i);
1399 	  t = s + strlen (s);	/* end of buffer */
1400 	}
1401       }
1402     }
1403 				/* last sequence */
1404   if (last != start) sprintf (t,":%lu",last);
1405   if (s) {			/* prefetch as needed */
1406     imap_fetch (stream,s,FT_NEEDENV);
1407     fs_give ((void **) &s);
1408   }
1409   ov.optional.lines = 0;	/* now overview each message */
1410   ov.optional.xref = NIL;
1411   if (ofn) for (i = 1; i <= stream->nmsgs; i++)
1412     if (((elt = mail_elt (stream,i))->sequence) &&
1413 	(env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
1414       ov.subject = env->subject;
1415       ov.from = env->from;
1416       ov.date = env->date;
1417       ov.message_id = env->message_id;
1418       ov.references = env->references;
1419       ov.optional.octets = elt->rfc822_size;
1420       (*ofn) (stream,mail_uid (stream,i),&ov,i);
1421     }
1422   return LONGT;
1423 }
1424 
1425 /* IMAP fetch structure
1426  * Accepts: MAIL stream
1427  *	    message # to fetch
1428  *	    pointer to return body
1429  *	    option flags
1430  * Returns: envelope of this message, body returned in body value
1431  *
1432  * Fetches the "fast" information as well
1433  */
1434 
imap_structure(MAILSTREAM * stream,unsigned long msgno,BODY ** body,long flags)1435 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
1436 			  long flags)
1437 {
1438   unsigned long i,j,k,x;
1439   char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN];
1440   MESSAGECACHE *elt;
1441   ENVELOPE **env;
1442   BODY **b;
1443   IMAPPARSEDREPLY *reply = NIL;
1444   IMAPARG *args[3],aseq,aatt;
1445   SEARCHSET *set = LOCAL->lookahead;
1446   LOCAL->lookahead = NIL;
1447   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1448   aseq.type = SEQUENCE; aseq.text = (void *) seq;
1449   aatt.type = ATOM; aatt.text = NIL;
1450   if (flags & FT_UID)		/* see if can find msgno from UID */
1451     for (i = 1; i <= stream->nmsgs; i++)
1452       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1453 	msgno = i;		/* found msgno, use it from now on */
1454 	flags &= ~FT_UID;	/* no longer a UID fetch */
1455       }
1456   sprintf (s = seq,"%lu",msgno);/* initial sequence */
1457   if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
1458     /* UID fetching is requested and we can't map the UID to a message sequence
1459      * number.  Assume that the message isn't cached at all.
1460      */
1461     if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV +
1462 					     (body ? FT_NEEDBODY : NIL) +
1463 					     (flags & (FT_UID + FT_NOHDRS)))))
1464       mm_log (reply->text,ERROR);
1465 				/* now hunt for this UID */
1466     for (i = 1; i <= stream->nmsgs; i++)
1467       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1468 	if (body) *body = elt->private.msg.body;
1469 	return elt->private.msg.env;
1470       }
1471     if (body) *body = NIL;	/* can't find the UID */
1472     return NIL;
1473   }
1474   elt = mail_elt (stream,msgno);/* get cache pointer */
1475   if (stream->scache) {		/* short caching? */
1476     env = &stream->env;		/* use temporaries on the stream */
1477     b = &stream->body;
1478     if (msgno != stream->msgno){/* flush old poop if a different message */
1479       mail_free_envelope (env);
1480       mail_free_body (b);
1481       stream->msgno = msgno;	/* this is now the current short cache msg */
1482     }
1483   }
1484 
1485   else {			/* normal cache */
1486     env = &elt->private.msg.env;/* get envelope and body pointers */
1487     b = &elt->private.msg.body;
1488 				/* prefetch if don't have envelope */
1489     if (!(flags & FT_NOLOOKAHEAD) &&
1490 	((!*env || (*env)->incomplete) ||
1491 	 (body && !*b && LEVELIMAP2bis (stream)))) {
1492       if (set) {		/* have a lookahead list? */
1493 	MESSAGE *msg;
1494 	for (k = imap_fetchlookaheadlimit;
1495 	     k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30));
1496 	     set = set->next) {
1497 	  i = (set->first == 0xffffffff) ? stream->nmsgs :
1498 	    min (set->first,stream->nmsgs);
1499 	  if (j = (set->last == 0xffffffff) ? stream->nmsgs :
1500 	      min (set->last,stream->nmsgs)) {
1501 	    if (i > j) {	/* swap the range if backwards */
1502 	      x = i; i = j; j = x;
1503 	    }
1504 				/* find first message not msgno or in cache */
1505 	    while (((i == msgno) ||
1506 		    ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1507 		     (!body || msg->body))) && (i++ < j));
1508 				/* until range or lookahead finished */
1509 	    while (k && (i <= j)) {
1510 				/* find first cached message in range */
1511 	      for (x = i + 1; (x <= j) &&
1512 		     !((msg = &(mail_elt (stream,x)->private.msg))->env &&
1513 		       (!body || msg->body)); x++);
1514 	      if (i == --x) {	/* only one message? */
1515 		sprintf (s += strlen (s),",%lu",i++);
1516 		k--;		/* prefetching one message */
1517 	      }
1518 	      else {		/* a range to prefetch */
1519 		sprintf (s += strlen (s),",%lu:%lu",i,x);
1520 		i = 1 + x - i;	/* number of messages in this range */
1521 				/* still can look ahead some more? */
1522 		if (k = (k > i) ? k - i : 0)
1523 				/* yes, scan further in this range */
1524 		  for (i = x + 2; (i <= j) &&
1525 			 ((i == msgno) ||
1526 			  ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1527 			   (!body || msg->body)));
1528 		       i++);
1529 	      }
1530 	    }
1531 	  }
1532 	  else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) {
1533 	    sprintf (s += strlen (s),",%lu",i);
1534 	    k--;		/* prefetching one message */
1535 	  }
1536       }
1537       }
1538 				/* build message number list */
1539       else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++)
1540 	if (!mail_elt (stream,i)->private.msg.env) {
1541 	  s += strlen (s);	/* find string end, see if nearing end */
1542 	  if ((s - seq) > (MAILTMPLEN - 20)) break;
1543 	  sprintf (s,",%lu",i);	/* append message */
1544  	  for (j = i + 1, k--;	/* hunt for last message without an envelope */
1545 	       k && (j <= stream->nmsgs) &&
1546 	       !mail_elt (stream,j)->private.msg.env; j++, k--);
1547 				/* if different, make a range */
1548 	  if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1549 	}
1550     }
1551   }
1552 
1553   if (!stream->lock) {		/* no-op if stream locked */
1554     /* Build the fetch attributes.  Unlike imap_fetch(), this tries not to
1555      * fetch data that is already cached.  However, since it is based on the
1556      * message requested and not on any of the prefetched messages, it can
1557      * goof, either by fetching data already cached or not prefetching data
1558      * that isn't cached (but was cached in the message requested).
1559      * Fortunately, no great harm is done.  If it doesn't prefetch the data,
1560      * it will get it when the affected message(s) are requested.
1561      */
1562     if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID");
1563     else tmp[0] = '\0';		/* initialize command */
1564 				/* need envelope? */
1565     if (!*env || (*env)->incomplete) {
1566       strcat (tmp," ENVELOPE");	/* yes, get it and possible extra poop */
1567       if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
1568 	if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
1569 				     hdrheader[LOCAL->cap.extlevel],
1570 				     imap_extrahdrs,hdrtrailer);
1571 	else sprintf (tmp + strlen (tmp)," %s %s",
1572 		      hdrheader[LOCAL->cap.extlevel],hdrtrailer);
1573       }
1574     }
1575 				/* need body? */
1576     if (body && !*b && LEVELIMAP2bis (stream))
1577       strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY");
1578     if (!elt->day) strcat (tmp," INTERNALDATE");
1579     if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
1580     if (tmp[0]) {		/* anything to do? */
1581       tmp[0] = '(';		/* make into a list */
1582       strcat (tmp," FLAGS)");	/* always get current flags */
1583       aatt.text = (void *) tmp;	/* do the built command */
1584       if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
1585 				/* failed, probably RFC-1176 server */
1586 	if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
1587 	  aatt.text = (void *) "ALL";
1588 	  if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1589 				/* doesn't have body capabilities */
1590 	    LOCAL->cap.imap2bis = NIL;
1591 	  else mm_log (reply->text,ERROR);
1592 	}
1593 	else mm_log (reply->text,ERROR);
1594       }
1595     }
1596   }
1597   if (body) {			/* wants to return body */
1598     if (!*b && !LEVELIMAP2bis (stream)) {
1599 				/* simulate body structure fetch for IMAP2 */
1600       *b = mail_initbody (mail_newbody ());
1601       (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type));
1602       ((*b)->parameter = mail_newbody_parameter ())->attribute =
1603 	cpystr ("CHARSET");
1604       (*b)->parameter->value = cpystr ("US-ASCII");
1605       s = mail_fetch_text (stream,msgno,NIL,&i,flags);
1606       (*b)->size.bytes = i;
1607       while (i--) if (*s++ == '\n') (*b)->size.lines++;
1608     }
1609     *body = *b;			/* return the body */
1610   }
1611   return *env;			/* return the envelope */
1612 }
1613 
1614 /* IMAP fetch message data
1615  * Accepts: MAIL stream
1616  *	    message number
1617  *	    section specifier
1618  *	    offset of first designated byte or 0 to start at beginning
1619  *	    maximum number of bytes or 0 for all bytes
1620  *	    lines to fetch if header
1621  *	    flags
1622  * Returns: T on success, NIL on failure
1623  */
1624 
imap_msgdata(MAILSTREAM * stream,unsigned long msgno,char * section,unsigned long first,unsigned long last,STRINGLIST * lines,long flags)1625 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
1626 		   unsigned long first,unsigned long last,STRINGLIST *lines,
1627 		   long flags)
1628 {
1629   int i;
1630   char *t,tmp[MAILTMPLEN],partial[40],seq[40];
1631   char *noextend,*nopartial,*nolines,*nopeek,*nononpeek;
1632   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1633   IMAPPARSEDREPLY *reply;
1634   IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg;
1635   noextend = nopartial = nolines = nopeek = nononpeek = NIL;
1636 				/* does searching desire a lookahead? */
1637   if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) &&
1638       !stream->scache) {
1639     sprintf (seq,"%lu:%lu",msgno,
1640 	     (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs));
1641     aseq.type = SEQUENCE;
1642     aseq.text = (void *) seq;
1643   }
1644   else {			/* no, do it the easy way */
1645     aseq.type = NUMBER;
1646     aseq.text = (void *) msgno;
1647   }
1648   aatt.type = ATOM;		/* assume atomic attribute */
1649   alns.type = LIST; alns.text = (void *) lines;
1650   acls.type = BODYCLOSE; acls.text = (void *) partial;
1651   aflg.type = ATOM; aflg.text = (void *) "FLAGS";
1652   args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
1653   auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL;
1654   partial[0] = '\0';		/* initially no partial specifier */
1655   if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */
1656 				/* HEADER fetching with special handling? */
1657     if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) {
1658       if (lines) {		/* want specific header lines? */
1659 	aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1660 	aatt.text = (void *) ((flags & FT_NOT) ?
1661 			      "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1662 	args[2] = &alns; args[3] = &acls;
1663       }
1664 				/* must be prefetching */
1665       else aatt.text = (void *) ((flags & FT_PEEK) ?
1666 				 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1667 				 "(BODY[HEADER] BODY[TEXT])");
1668     }
1669     else {			/* simple case */
1670       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1671       aatt.text = (void *) section;
1672       args[2] = &acls;
1673     }
1674     if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1);
1675   }
1676 
1677   /* IMAP4 did not have:
1678    * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1679    * . TEXT body part (can simulate top-level with RFC822.TEXT or
1680    *			RFC822.TEXT.PEEK)
1681    * . MIME body part
1682    * . (usable) partial fetching
1683    * . (usable) selective header line fetching
1684    */
1685   else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */
1686 				/* BODY[HEADER] becomes BODY.PEEK[0] */
1687     if (!strcmp (section,"HEADER"))
1688       aatt.text = (void *)
1689 	((flags & FT_PREFETCHTEXT) ?
1690 	 ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1691 	  "(BODY[0] RFC822.TEXT)") :
1692 	 ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]"));
1693 				/* BODY[TEXT] becomes RFC822.TEXT */
1694     else if (!strcmp (section,"TEXT"))
1695       aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" :
1696 			    "RFC822.TEXT");
1697     else if (!section[0])	/* BODY[] becomes RFC822 */
1698       aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
1699 				/* nested header */
1700     else if (t = strstr (section,".HEADER")) {
1701       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1702       args[2] = &acls;		/* will need to close section */
1703       aatt.text = (void *) tmp;	/* convert .HEADER to .0 */
1704       strncpy (tmp,section,t-section);
1705       strcpy (tmp+(t-section),".0");
1706     }
1707     else {			/* IMAP4 body part */
1708       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1709       args[2] = &acls;		/* will need to close section */
1710       aatt.text = (void *) section;
1711     }
1712     if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4";
1713     if (first || last) nopartial = "4";
1714     if (lines) nolines = "4";
1715   }
1716 
1717   /* IMAP2bis did not have:
1718    * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1719    * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1720    * . MIME body part
1721    * . partial fetching
1722    * . selective header line fetching
1723    * . non-peeking header fetching
1724    * . peeking body fetching
1725    */
1726 				/* IMAP2bis compatibility */
1727   else if (LEVELIMAP2bis (stream)) {
1728 				/* BODY[HEADER] becomes RFC822.HEADER */
1729     if (!strcmp (section,"HEADER")) {
1730       aatt.text = (void *)
1731 	((flags & FT_PREFETCHTEXT) ?
1732 	 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1733       if (flags & FT_PEEK) flags &= ~FT_PEEK;
1734       else nononpeek = "2bis";
1735     }
1736 				/* BODY[TEXT] becomes RFC822.TEXT */
1737     else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1738 				/* BODY[] becomes RFC822 */
1739     else if (!section[0]) aatt.text = (void *) "RFC822";
1740     else {			/* IMAP2bis body part */
1741       aatt.type = BODYTEXT;
1742       args[2] = &acls;		/* will need to close section */
1743       aatt.text = (void *) section;
1744     }
1745     if (strstr (section,".HEADER") || strstr (section,".MIME") ||
1746 	     strstr (section,".TEXT")) noextend = "2bis";
1747     if (first || last) nopartial = "2bis";
1748     if (lines) nolines = "2bis";
1749     if (flags & FT_PEEK) nopeek = "2bis";
1750   }
1751 
1752   /* IMAP2 did not have:
1753    * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1754    * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1755    * . MIME body part
1756    * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1757    * . partial fetching
1758    * . selective header line fetching
1759    * . non-peeking header fetching
1760    * . peeking body fetching
1761    */
1762   else {			/* IMAP2 (RFC 1176/1064) compatibility */
1763 				/* BODY[HEADER] */
1764     if (!strcmp (section,"HEADER")) {
1765       aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
1766 			    "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1767       if (flags & FT_PEEK) flags &= ~FT_PEEK;
1768       nononpeek = "2";
1769     }
1770 				/* BODY[TEXT] becomes RFC822.TEXT */
1771     else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1772 				/* BODY[1] treated like RFC822.TEXT */
1773     else if (!strcmp (section,"1")) {
1774       SIZEDTEXT text;
1775       MESSAGECACHE *elt = mail_elt (stream,msgno);
1776 				/* have a cached RFC822.TEXT? */
1777       if (elt->private.msg.text.text.data) {
1778 	text.size = elt->private.msg.text.text.size;
1779 				/* should move instead of copy */
1780 	text.data = memcpy (fs_get (text.size+1),
1781 			    elt->private.msg.text.text.data,text.size);
1782 	(t = (char *) text.data)[text.size] = '\0';
1783 	imap_cache (stream,msgno,"1",NIL,&text);
1784 	return LONGT;		/* don't have to do any fetches */
1785       }
1786 				/* otherwise do RFC822.TEXT */
1787       aatt.text = (void *) "RFC822.TEXT";
1788     }
1789 				/* BODY[] becomes RFC822 */
1790     else if (!section[0]) aatt.text = (void *) "RFC822";
1791     else noextend = "2";	/* how did we get here? */
1792     if (flags & FT_PEEK) nopeek = "2";
1793     if (first || last) nopartial = "2";
1794     if (lines) nolines = "2";
1795   }
1796 
1797   /* Report unavailable functionalities.  The application can use the helpful
1798    * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1799    * imap4r1.h to avoid triggering these errors.  There aren't any workarounds
1800    * for these restrictions.
1801    */
1802   if (noextend) {
1803     sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1804 	     noextend);
1805     mm_log (tmp,ERROR);
1806     return NIL;			/* can't do anything close either */
1807   }
1808   if (nopartial) {
1809     sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1810 	     nopartial);
1811     mm_notify (stream,tmp,WARN);
1812   }
1813   if (nolines) {
1814     sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1815 	    nolines);
1816     mm_notify (stream,tmp,WARN);
1817   }
1818 
1819 				/* trying to do unsupported peek behavior? */
1820   if ((t = nopeek) || (t = nononpeek)) {
1821 				/* get most recent \Seen setting */
1822     if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs)))
1823       mm_log (reply->text,WARN);
1824 				/* note current setting of \Seen flag */
1825     if (!(i = mail_elt (stream,msgno)->seen)) {
1826       sprintf (tmp,nopeek ?	/* only babble if \Seen not set */
1827 	       "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1828 	       "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t);
1829       mm_notify (stream,tmp,NIL);
1830     }
1831 				/* send the fetch command */
1832     if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1833       mm_log (reply->text,ERROR);
1834       return NIL;		/* failure */
1835     }
1836 				/* send command if need to reset \Seen */
1837     if (((nopeek && !i && mail_elt (stream,msgno)->seen &&
1838 	  (aflg.text = "-FLAGS \\Seen")) ||
1839 	 ((nononpeek && !mail_elt (stream,msgno)->seen) &&
1840 	  (aflg.text = "+FLAGS \\Seen"))) &&
1841 	!imap_OK (stream,reply = imap_send (stream,"STORE",auxargs)))
1842       mm_log (reply->text,WARN);
1843   }
1844 				/* simple case if traditional behavior */
1845   else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1846     mm_log (reply->text,ERROR);
1847     return NIL;			/* failure */
1848   }
1849 				/* simulate BODY[1] return for RFC 1064/1176 */
1850   if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) {
1851     SIZEDTEXT text;
1852     MESSAGECACHE *elt = mail_elt (stream,msgno);
1853     text.size = elt->private.msg.text.text.size;
1854 				/* should move instead of copy */
1855     text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data,
1856 			text.size);
1857     (t = (char *) text.data)[text.size] = '\0';
1858     imap_cache (stream,msgno,"1",NIL,&text);
1859   }
1860   return LONGT;
1861 }
1862 
1863 /* IMAP fetch UID
1864  * Accepts: MAIL stream
1865  *	    message number
1866  * Returns: UID
1867  */
1868 
imap_uid(MAILSTREAM * stream,unsigned long msgno)1869 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
1870 {
1871   MESSAGECACHE *elt;
1872   IMAPPARSEDREPLY *reply;
1873   IMAPARG *args[3],aseq,aatt;
1874   char *s,seq[MAILTMPLEN];
1875   unsigned long i,j,k;
1876 				/* IMAP2 didn't have UIDs */
1877   if (!LEVELIMAP4 (stream)) return msgno;
1878 				/* do we know its UID yet? */
1879   if (!(elt = mail_elt (stream,msgno))->private.uid) {
1880     aseq.type = SEQUENCE; aseq.text = (void *) seq;
1881     aatt.type = ATOM; aatt.text = (void *) "UID";
1882     args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1883     sprintf (seq,"%lu",msgno);
1884     if (k = imap_uidlookahead) {/* build UID list */
1885       for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
1886 	if (!mail_elt (stream,i)->private.uid) {
1887 	  s += strlen (s);	/* find string end, see if nearing end */
1888 	  if ((s - seq) > (MAILTMPLEN - 20)) break;
1889 	  sprintf (s,",%lu",i);	/* append message */
1890 	  for (j = i + 1, k--;	/* hunt for last message without a UID */
1891 	       k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
1892 	       j++, k--);
1893 				/* if different, make a range */
1894 	  if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1895 	}
1896     }
1897 				/* send "FETCH msgno UID" */
1898     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1899       mm_log (reply->text,ERROR);
1900   }
1901   return elt->private.uid;	/* return our UID now */
1902 }
1903 
1904 /* IMAP fetch message number from UID
1905  * Accepts: MAIL stream
1906  *	    UID
1907  * Returns: message number
1908  */
1909 
imap_msgno(MAILSTREAM * stream,unsigned long uid)1910 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
1911 {
1912   IMAPPARSEDREPLY *reply;
1913   IMAPARG *args[3],aseq,aatt;
1914   char seq[MAILTMPLEN];
1915   int holes = 0;
1916   unsigned long i,msgno;
1917 				/* IMAP2 didn't have UIDs */
1918   if (!LEVELIMAP4 (stream)) return uid;
1919   /* This really should be a binary search, but since there are likely to be
1920    * holes in the msgno->UID map it's hard to do.
1921    */
1922   for (msgno = 1; msgno <= stream->nmsgs; msgno++) {
1923     if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T;
1924     else if (i == uid) return msgno;
1925   }
1926   if (holes) {			/* have holes in cache? */
1927 				/* yes, have server hunt for UID */
1928     LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0;
1929     aseq.type = SEQUENCE; aseq.text = (void *) seq;
1930     aatt.type = ATOM; aatt.text = (void *) "UID";
1931     args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1932     sprintf (seq,"%lu",uid);
1933 				/* send "UID FETCH uid UID" */
1934     if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
1935       mm_log (reply->text,ERROR);
1936     if (LOCAL->lastuid.uid) {	/* got any results from FETCH? */
1937       if ((LOCAL->lastuid.uid == uid) &&
1938 				/* what, me paranoid? */
1939 	  (LOCAL->lastuid.msgno <= stream->nmsgs) &&
1940 	  (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid))
1941 				/* got it the easy way */
1942 	return LOCAL->lastuid.msgno;
1943 				/* sigh, do another linear search... */
1944       for (msgno = 1; msgno <= stream->nmsgs; msgno++)
1945 	if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
1946     }
1947   }
1948   return 0;			/* didn't find the UID anywhere */
1949 }
1950 
1951 /* IMAP modify flags
1952  * Accepts: MAIL stream
1953  *	    sequence
1954  *	    flag(s)
1955  *	    option flags
1956  */
1957 
imap_flag(MAILSTREAM * stream,char * sequence,char * flag,long flags)1958 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
1959 {
1960   char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
1961   IMAPPARSEDREPLY *reply;
1962   IMAPARG *args[4],aseq,ascm,aflg;
1963   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1964 						     flags & ST_UID);
1965   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1966   ascm.type = ATOM; ascm.text = (void *)
1967     ((flags & ST_SET) ?
1968      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
1969       "+Flags.silent" : "+Flags") :
1970      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
1971       "-Flags.silent" : "-Flags"));
1972   aflg.type = FLAGS; aflg.text = (void *) flag;
1973   args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
1974 				/* send "STORE sequence +Flags flag" */
1975   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1976     mm_log (reply->text,ERROR);
1977 }
1978 
1979 /* IMAP search for messages
1980  * Accepts: MAIL stream
1981  *	    character set
1982  *	    search program
1983  *	    option flags
1984  * Returns: T on success, NIL on failure
1985  */
1986 
imap_search(MAILSTREAM * stream,char * charset,SEARCHPGM * pgm,long flags)1987 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
1988 {
1989   unsigned long i,j,k;
1990   char *s;
1991   IMAPPARSEDREPLY *reply;
1992   MESSAGECACHE *elt;
1993   if ((flags & SE_NOSERVER) ||	/* if want to do local search */
1994       LOCAL->loser ||		/* or loser */
1995       (!LEVELIMAP4 (stream) &&	/* or old server but new functions... */
1996        (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or ||
1997 	pgm->not || pgm->header || pgm->larger || pgm->smaller ||
1998 	pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
1999 	pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
2000 	pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
2001 	pgm->followup_to || pgm->references)) ||
2002       (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) {
2003     if ((flags & SE_NOLOCAL) ||
2004 	!mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2005       return NIL;
2006   }
2007 				/* do silly ALL or seq-only search locally */
2008   else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) &&
2009 	   !(pgm->uid || pgm->or || pgm->not ||
2010 	     pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc ||
2011 	     pgm->subject || pgm->body || pgm->text ||
2012 	     pgm->larger || pgm->smaller ||
2013 	     pgm->sentbefore || pgm->senton || pgm->sentsince ||
2014 	     pgm->before || pgm->on || pgm->since ||
2015 	     pgm->answered || pgm->unanswered ||
2016 	     pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft ||
2017 	     pgm->flagged || pgm->unflagged || pgm->recent || pgm->old ||
2018 	     pgm->seen || pgm->unseen ||
2019 	     pgm->keyword || pgm->unkeyword ||
2020 	     pgm->return_path || pgm->sender ||
2021 	     pgm->reply_to || pgm->in_reply_to || pgm->message_id ||
2022 	     pgm->newsgroups || pgm->followup_to || pgm->references)) {
2023     if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER))
2024       fatal ("impossible mail_search_default() failure");
2025   }
2026 
2027   else {			/* do server-based SEARCH */
2028     char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH";
2029     IMAPARG *args[4],apgm,aatt,achs;
2030     SEARCHSET *ss,*set;
2031     args[1] = args[2] = args[3] = NIL;
2032     apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
2033     if (charset) {		/* optional charset argument requested */
2034       args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
2035       aatt.type = ATOM; aatt.text = (void *) "CHARSET";
2036       achs.type = ASTRING; achs.text = (void *) charset;
2037     }
2038     else args[0] = &apgm;	/* no charset argument */
2039 				/* tell receiver that these will be UIDs */
2040     LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
2041     reply = imap_send (stream,cmd,args);
2042 				/* did server barf with that searchpgm? */
2043     if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) &&
2044 	!strcmp (reply->key,"BAD")) {
2045       LOCAL->filter = T;	/* retry, filtering SEARCH results */
2046       for (i = 1; i <= stream->nmsgs; i++)
2047 	mail_elt (stream,i)->private.filter = NIL;
2048       for (set = ss; set; set = set->next) if (i = set->first) {
2049 				/* single message becomes one-message range */
2050 	if (!(j = set->last)) j = i;
2051 	else if (j < i) {	/* swap reversed range */
2052 	  i = set->last; j = set->first;
2053 	}
2054 	while (i <= j) mail_elt (stream,i++)->private.filter = T;
2055       }
2056       pgm->msgno = NIL;		/* and without the searchset */
2057       reply = imap_send (stream,cmd,args);
2058       pgm->msgno = ss;		/* restore searchset */
2059       LOCAL->filter = NIL;	/* turn off filtering */
2060     }
2061     LOCAL->uidsearch = NIL;
2062 				/* do locally if server won't grok */
2063     if (!strcmp (reply->key,"BAD")) {
2064       if ((flags & SE_NOLOCAL) ||
2065 	  !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2066 	return NIL;
2067     }
2068     else if (!imap_OK (stream,reply)) {
2069       mm_log (reply->text,ERROR);
2070       return NIL;
2071     }
2072   }
2073 
2074 				/* can never pre-fetch with a short cache */
2075   if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
2076       !stream->scache) {	/* only if prefetching permitted */
2077     s = LOCAL->tmp;		/* build sequence in temporary buffer */
2078     *s = '\0';			/* initially nothing */
2079 				/* search through mailbox */
2080     for (i = 1; k && (i <= stream->nmsgs); ++i)
2081 				/* for searched messages with no envelope */
2082       if ((elt = mail_elt (stream,i)) && elt->searched &&
2083 	  !mail_elt (stream,i)->private.msg.env) {
2084 				/* prepend with comma if not first time */
2085 	if (LOCAL->tmp[0]) *s++ = ',';
2086 	sprintf (s,"%lu",j = i);/* output message number */
2087 	s += strlen (s);	/* point at end of string */
2088 	k--;			/* count one up */
2089 				/* search for possible end of range */
2090 	while (k && (i < stream->nmsgs) &&
2091 	       (elt = mail_elt (stream,i+1))->searched &&
2092 	       !elt->private.msg.env) i++,k--;
2093 	if (i != j) {		/* if a range */
2094 	  sprintf (s,":%lu",i);	/* output delimiter and end of range */
2095 	  s += strlen (s);	/* point at end of string */
2096 	}
2097 	if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
2098       }
2099     if (LOCAL->tmp[0]) {	/* anything to pre-fetch? */
2100       /* pre-fetch envelopes for the first imap_prefetch number of messages */
2101       if (!imap_OK (stream,reply =
2102 		    imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV +
2103 				((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
2104 				((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
2105 	mm_log (reply->text,ERROR);
2106       fs_give ((void **) &s);	/* flush copy of sequence */
2107     }
2108   }
2109   return LONGT;
2110 }
2111 
2112 /* IMAP sort messages
2113  * Accepts: mail stream
2114  *	    character set
2115  *	    search program
2116  *	    sort program
2117  *	    option flags
2118  * Returns: vector of sorted message sequences or NIL if error
2119  */
2120 
imap_sort(MAILSTREAM * stream,char * charset,SEARCHPGM * spg,SORTPGM * pgm,long flags)2121 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
2122 			  SORTPGM *pgm,long flags)
2123 {
2124   unsigned long i,j,start,last;
2125   unsigned long *ret = NIL;
2126   pgm->nmsgs = 0;		/* start off with no messages */
2127 				/* can use server-based sort? */
2128   if (LEVELSORT (stream) && !(flags & SE_NOSERVER) &&
2129       (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) {
2130     char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
2131     IMAPARG *args[4],apgm,achs,aspg;
2132     IMAPPARSEDREPLY *reply;
2133     SEARCHSET *ss = NIL;
2134     SEARCHPGM *tsp = NIL;
2135     apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
2136     achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
2137     aspg.type = SEARCHPROGRAM;
2138 				/* did he provide a searchpgm? */
2139     if (!(aspg.text = (void *) spg)) {
2140       for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2141 	if (mail_elt (stream,i)->searched) {
2142 	  if (ss) {		/* continuing a sequence */
2143 	    if (i == last + 1) last = i;
2144 	    else {		/* end of range */
2145 	      if (last != start) ss->last = last;
2146 	      (ss = ss->next = mail_newsearchset ())->first = i;
2147 	      start = last = i;	/* begin a new range */
2148 	    }
2149 	  }
2150 	  else {		/* first time, start new searchpgm */
2151 	    (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2152 	    ss->first = start = last = i;
2153 	  }
2154 	}
2155 				/* nothing to sort if no messages */
2156       if (!(aspg.text = (void *) tsp)) return NIL;
2157 				/* else install last sequence */
2158       if (last != start) ss->last = last;
2159     }
2160 
2161     args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2162 				/* ask server to do it */
2163     reply = imap_send (stream,cmd,args);
2164     if (tsp) {			/* was there a temporary searchpgm? */
2165       aspg.text = NIL;		/* yes, flush it */
2166       mail_free_searchpgm (&tsp);
2167 				/* did server barf with that searchpgm? */
2168       if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2169 	LOCAL->filter = T;	/* retry, filtering SORT/THREAD results */
2170 	reply = imap_send (stream,cmd,args);
2171 	LOCAL->filter = NIL;	/* turn off filtering */
2172       }
2173     }
2174 				/* do locally if server barfs */
2175     if (!strcmp (reply->key,"BAD"))
2176       return (flags & SE_NOLOCAL) ? NIL :
2177 	imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER);
2178 				/* server sorted OK? */
2179     else if (imap_OK (stream,reply)) {
2180       pgm->nmsgs = LOCAL->sortsize;
2181       ret = LOCAL->sortdata;
2182       LOCAL->sortdata = NIL;	/* mail program is responsible for flushing */
2183     }
2184     else mm_log (reply->text,ERROR);
2185   }
2186 
2187 				/* not much can do if short caching */
2188   else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
2189   else {			/* try to be a bit more clever */
2190     char *s,*t;
2191     unsigned long len;
2192     MESSAGECACHE *elt;
2193     SORTCACHE **sc;
2194     SORTPGM *sp;
2195     long ftflags = 0;
2196 				/* see if need envelopes */
2197     for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) {
2198     case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
2199       ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL);
2200     }
2201     if (spg) {			/* only if a search needs to be done */
2202       int silent = stream->silent;
2203       stream->silent = T;	/* don't pass up mm_searched() events */
2204 				/* search for messages */
2205       mail_search_full (stream,charset,spg,flags & SE_NOSERVER);
2206       stream->silent = silent;	/* restore silence state */
2207     }
2208 				/* initialize progress counters */
2209     pgm->nmsgs = pgm->progress.cached = 0;
2210 				/* pass 1: count messages to sort */
2211     for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
2212       if ((elt = mail_elt (stream,i))->searched) {
2213 	pgm->nmsgs++;
2214 	if (ftflags ? !elt->private.msg.env : !elt->day) {
2215 	  if (s) {		/* continuing a sequence */
2216 	    if (i == last + 1) last = i;
2217 	    else {		/* end of range */
2218 	      if (last != start) sprintf (t,":%lu,%lu",last,i);
2219 	      else sprintf (t,",%lu",i);
2220 	      start = last = i;	/* begin a new range */
2221 	      if ((len - (j = ((t += strlen (t)) - s)) < 20)) {
2222 		fs_resize ((void **) &s,len += MAILTMPLEN);
2223 		t = s + j;	/* relocate current pointer */
2224 	      }
2225 	    }
2226 	  }
2227 	  else {		/* first time, start new buffer */
2228 	    s = (char *) fs_get (len = MAILTMPLEN);
2229 	    sprintf (s,"%lu",start = last = i);
2230 	    t = s + strlen (s);	/* end of buffer */
2231 	  }
2232 	}
2233       }
2234 				/* last sequence */
2235     if (last != start) sprintf (t,":%lu",last);
2236     if (s) {			/* load cache for all messages being sorted */
2237       imap_fetch (stream,s,ftflags);
2238       fs_give ((void **) &s);
2239     }
2240     if (pgm->nmsgs) {		/* pass 2: sort cache */
2241       sortresults_t sr = (sortresults_t)
2242 	mail_parameters (NIL,GET_SORTRESULTS,NIL);
2243       sc = mail_sort_loadcache (stream,pgm);
2244 				/* pass 3: sort messages */
2245       if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
2246       fs_give ((void **) &sc);	/* don't need sort vector any more */
2247 				/* also return via callback if requested */
2248       if (sr) (*sr) (stream,ret,pgm->nmsgs);
2249     }
2250   }
2251   return ret;
2252 }
2253 
2254 /* IMAP thread messages
2255  * Accepts: mail stream
2256  *	    thread type
2257  *	    character set
2258  *	    search program
2259  *	    option flags
2260  * Returns: thread node tree or NIL if error
2261  */
2262 
imap_thread(MAILSTREAM * stream,char * type,char * charset,SEARCHPGM * spg,long flags)2263 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
2264 			 SEARCHPGM *spg,long flags)
2265 {
2266   THREADER *thr;
2267   if (!(flags & SE_NOSERVER) &&
2268       (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger))))
2269 				/* does server have this threader type? */
2270     for (thr = LOCAL->cap.threader; thr; thr = thr->next)
2271       if (!compare_cstring (thr->name,type))
2272 	return imap_thread_work (stream,type,charset,spg,flags);
2273 				/* server doesn't support it, do locally */
2274   return (flags & SE_NOLOCAL) ? NIL:
2275     mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2276 }
2277 
2278 /* IMAP thread messages worker routine
2279  * Accepts: mail stream
2280  *	    thread type
2281  *	    character set
2282  *	    search program
2283  *	    option flags
2284  * Returns: thread node tree
2285  */
2286 
imap_thread_work(MAILSTREAM * stream,char * type,char * charset,SEARCHPGM * spg,long flags)2287 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
2288 			      SEARCHPGM *spg,long flags)
2289 {
2290   unsigned long i,start,last;
2291   char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
2292   IMAPARG *args[4],apgm,achs,aspg;
2293   IMAPPARSEDREPLY *reply;
2294   THREADNODE *ret = NIL;
2295   SEARCHSET *ss = NIL;
2296   SEARCHPGM *tsp = NIL;
2297   apgm.type = ATOM; apgm.text = (void *) type;
2298   achs.type = ASTRING;
2299   achs.text = (void *) (charset ? charset : "US-ASCII");
2300   aspg.type = SEARCHPROGRAM;
2301 				/* did he provide a searchpgm? */
2302   if (!(aspg.text = (void *) spg)) {
2303     for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2304       if (mail_elt (stream,i)->searched) {
2305 	if (ss) {		/* continuing a sequence */
2306 	  if (i == last + 1) last = i;
2307 	  else {		/* end of range */
2308 	    if (last != start) ss->last = last;
2309 	    (ss = ss->next = mail_newsearchset ())->first = i;
2310 	    start = last =i;	/* begin a new range */
2311 	  }
2312 	}
2313 	else {			/* first time, start new searchpgm */
2314 	  (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2315 	  ss->first = start = last = i;
2316 	}
2317       }
2318 				/* nothing to sort if no messages */
2319     if (!(aspg.text = (void *) tsp)) return NIL;
2320 				/* else install last sequence */
2321     if (last != start) ss->last = last;
2322   }
2323 
2324   args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2325 				/* ask server to do it */
2326   reply = imap_send (stream,cmd,args);
2327   if (tsp) {			/* was there a temporary searchpgm? */
2328     aspg.text = NIL;		/* yes, flush it */
2329     mail_free_searchpgm (&tsp);
2330 				/* did server barf with that searchpgm? */
2331     if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2332       LOCAL->filter = T;	/* retry, filtering SORT/THREAD results */
2333       reply = imap_send (stream,cmd,args);
2334       LOCAL->filter = NIL;	/* turn off filtering */
2335     }
2336   }
2337 				/* do locally if server barfs */
2338   if (!strcmp (reply->key,"BAD"))
2339     ret = (flags & SE_NOLOCAL) ? NIL:
2340     mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2341 				/* server threaded OK? */
2342   else if (imap_OK (stream,reply)) {
2343     ret = LOCAL->threaddata;
2344     LOCAL->threaddata = NIL;	/* mail program is responsible for flushing */
2345   }
2346   else mm_log (reply->text,ERROR);
2347   return ret;
2348 }
2349 
2350 /* IMAP ping mailbox
2351  * Accepts: MAIL stream
2352  * Returns: T if stream still alive, else NIL
2353  */
2354 
imap_ping(MAILSTREAM * stream)2355 long imap_ping (MAILSTREAM *stream)
2356 {
2357   return (LOCAL->netstream &&	/* send "NOOP" */
2358 	  imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
2359 }
2360 
2361 
2362 /* IMAP check mailbox
2363  * Accepts: MAIL stream
2364  */
2365 
imap_check(MAILSTREAM * stream)2366 void imap_check (MAILSTREAM *stream)
2367 {
2368 				/* send "CHECK" */
2369   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
2370   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
2371 }
2372 
2373 /* IMAP expunge mailbox
2374  * Accepts: MAIL stream
2375  *	    sequence to expunge if non-NIL
2376  *	    expunge options
2377  * Returns: T if success, NIL if failure
2378  */
2379 
imap_expunge(MAILSTREAM * stream,char * sequence,long options)2380 long imap_expunge (MAILSTREAM *stream,char *sequence,long options)
2381 {
2382   long ret = NIL;
2383   IMAPPARSEDREPLY *reply = NIL;
2384   if (sequence) {		/* wants selective expunging? */
2385     if (options & EX_UID) {	/* UID EXPUNGE form? */
2386       if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */
2387 	IMAPARG *args[2],aseq;
2388 	aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2389 	args[0] = &aseq; args[1] = NIL;
2390 	ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args));
2391       }
2392       else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR);
2393     }
2394 				/* otherwise try to make into UID EXPUNGE */
2395     else if (mail_sequence (stream,sequence)) {
2396       unsigned long i,j;
2397       char *t = (char *) fs_get (IMAPTMPLEN);
2398       char *s = t;
2399 				/* search through mailbox */
2400       for (*s = '\0', i = 1; i <= stream->nmsgs; ++i)
2401 	if (mail_elt (stream,i)->sequence) {
2402 	  if (t[0]) *s++ = ',';	/* prepend with comma if not first time */
2403 	  sprintf (s,"%lu",mail_uid (stream,j = i));
2404 	  s += strlen (s);	/* point at end of string */
2405 				/* search for possible end of range */
2406 	  while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++;
2407 	  if (i != j) {		/* output end of range */
2408 	    sprintf (s,":%lu",mail_uid (stream,i));
2409 	    s += strlen (s);	/* point at end of string */
2410 	  }
2411 	  if ((s - t) > (IMAPTMPLEN - 50)) {
2412 	    mm_log ("Excessively complex sequence",ERROR);
2413 	    return NIL;
2414 	  }
2415 	}
2416 				/* now do as UID EXPUNGE */
2417       ret = imap_expunge (stream,t,EX_UID);
2418       fs_give ((void **) &t);
2419     }
2420   }
2421 				/* ordinary EXPUNGE */
2422   else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL));
2423   if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR);
2424   return ret;
2425 }
2426 
2427 /* IMAP copy message(s)
2428  * Accepts: MAIL stream
2429  *	    sequence
2430  *	    destination mailbox
2431  *	    option flags
2432  * Returns: T if successful else NIL
2433  */
2434 
imap_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long flags)2435 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
2436 {
2437   char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
2438   char *s;
2439   long ret = NIL;
2440   IMAPPARSEDREPLY *reply;
2441   IMAPARG *args[3],aseq,ambx;
2442   imapreferral_t ir =
2443     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2444   mailproxycopy_t pc =
2445     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
2446   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2447 						     flags & CP_UID);
2448   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2449   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2450   args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
2451 				/* note mailbox in case APPENDUID */
2452   LOCAL->appendmailbox = mailbox;
2453 				/* send "COPY sequence mailbox" */
2454   ret = imap_OK (stream,reply = imap_send (stream,cmd,args));
2455   LOCAL->appendmailbox = NIL;	/* no longer appending */
2456   if (ret) {			/* success, delete messages if move */
2457     if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
2458 				    ST_SET + ((flags&CP_UID) ? ST_UID : NIL));
2459   }
2460 				/* failed, do referral action if any */
2461   else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
2462 	   (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
2463     ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL));
2464 				/* otherwise issue error message */
2465   else mm_log (reply->text,ERROR);
2466   return ret;
2467 }
2468 
2469 /* IMAP mail append message from stringstruct
2470  * Accepts: MAIL stream
2471  *	    destination mailbox
2472  *	    append callback
2473  *	    data for callback
2474  * Returns: T if append successful, else NIL
2475  */
2476 
imap_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)2477 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
2478 {
2479   MAILSTREAM *st = stream;
2480   IMAPARG *args[3],ambx,amap;
2481   IMAPPARSEDREPLY *reply = NIL;
2482   APPENDDATA map;
2483   char tmp[MAILTMPLEN];
2484   long debug = stream ? stream->debug : NIL;
2485   long ret = NIL;
2486   imapreferral_t ir =
2487     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2488 				/* mailbox must be good */
2489   if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2490 				/* create a stream if given one no good */
2491     if ((stream && LOCAL && LOCAL->netstream) ||
2492 	(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2493 			     (debug ? OP_DEBUG : NIL)))) {
2494 				/* note mailbox in case APPENDUID */
2495       LOCAL->appendmailbox = mailbox;
2496 				/* use multi-append? */
2497       if (LEVELMULTIAPPEND (stream)) {
2498 	ambx.type = ASTRING; ambx.text = (void *) tmp;
2499 	amap.type = MULTIAPPEND; amap.text = (void *) &map;
2500 	map.af = af; map.data = data;
2501 	args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2502 				/* success if OK */
2503 	ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args));
2504 	LOCAL->appendmailbox = NIL;
2505       }
2506 				/* do succession of single appends */
2507       else while ((*af) (stream,data,&map.flags,&map.date,&map.message) &&
2508 		  map.message &&
2509 		  (ret = imap_OK (stream,reply =
2510 				  imap_append_single (stream,tmp,map.flags,
2511 						      map.date,map.message))));
2512       LOCAL->appendmailbox = NIL;
2513 				/* don't do referrals if success or no reply */
2514       if (ret || !reply) mailbox = NIL;
2515 				/* otherwise generate referral */
2516       else if (!(mailbox = (ir && LOCAL->referral) ?
2517 		 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2518 	mm_log (reply->text,ERROR);
2519 				/* close temporary stream */
2520       if (st != stream) stream = mail_close (stream);
2521       if (mailbox)		/* chase referral if any */
2522 	ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date,
2523 				    map.message,&map,debug);
2524     }
2525     else mm_log ("Can't access server for append",ERROR);
2526   }
2527   return ret;			/* return */
2528 }
2529 
2530 /* IMAP mail append message referral retry
2531  * Accepts: destination mailbox
2532  *	    temporary buffer
2533  *	    append callback
2534  *	    data for callback
2535  *	    flags from previous attempt
2536  *	    date from previous attempt
2537  *	    message stringstruct from previous attempt
2538  *	    options (currently non-zero to set OP_DEBUG)
2539  * Returns: T if append successful, else NIL
2540  */
2541 
imap_append_referral(char * mailbox,char * tmp,append_t af,void * data,char * flags,char * date,STRING * message,APPENDDATA * map,long options)2542 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
2543 			   char *flags,char *date,STRING *message,
2544 			   APPENDDATA *map,long options)
2545 {
2546   MAILSTREAM *stream;
2547   IMAPARG *args[3],ambx,amap;
2548   IMAPPARSEDREPLY *reply;
2549   imapreferral_t ir =
2550     (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL);
2551 				/* barf if bad mailbox */
2552   while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2553 				/* create a stream if given one no good */
2554     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2555 			      (options ? OP_DEBUG : NIL)))) {
2556       sprintf (tmp,"Can't access referral server: %.80s",mailbox);
2557       mm_log (tmp,ERROR);
2558       return NIL;
2559     }
2560 				/* got referral server, use multi-append? */
2561     if (LEVELMULTIAPPEND (stream)) {
2562       ambx.type = ASTRING; ambx.text = (void *) tmp;
2563       amap.type = MULTIAPPENDREDO; amap.text = (void *) map;
2564       args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2565 				/* do multiappend on referral site */
2566       if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) {
2567 	mail_close (stream);	/* multiappend OK, close stream */
2568 	return LONGT;		/* all done */
2569       }
2570     }
2571 				/* do multiple single appends */
2572     else while (imap_OK (stream,reply =
2573 			 imap_append_single (stream,tmp,flags,date,message)))
2574       if (!((*af) (stream,data,&flags,&date,&message) && message)) {
2575 	mail_close (stream);	/* last message, close stream */
2576 	return LONGT;		/* all done */
2577       }
2578 				/* generate error if no nested referral */
2579     if (!(mailbox = (ir && LOCAL->referral) ?
2580 	  (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2581       mm_log (reply->text,ERROR);
2582     mail_close (stream);	/* close previous referral stream */
2583   }
2584   return NIL;			/* bogus mailbox */
2585 }
2586 
2587 /* IMAP append single message
2588  * Accepts: mail stream
2589  *	    destination mailbox
2590  *	    initial flags
2591  *	    internal date
2592  *	    stringstruct of message to append
2593  * Returns: reply from append
2594  */
2595 
imap_append_single(MAILSTREAM * stream,char * mailbox,char * flags,char * date,STRING * message)2596 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
2597 				     char *flags,char *date,STRING *message)
2598 {
2599   MESSAGECACHE elt;
2600   IMAPARG *args[5],ambx,aflg,adat,amsg;
2601   IMAPPARSEDREPLY *reply;
2602   char tmp[MAILTMPLEN];
2603   int i;
2604   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2605   args[i = 0] = &ambx;
2606   if (flags) {
2607     aflg.type = FLAGS; aflg.text = (void *) flags;
2608     args[++i] = &aflg;
2609   }
2610   if (date) {			/* ensure date in INTERNALDATE format */
2611     if (!mail_parse_date (&elt,date)) {
2612 				/* flush previous reply */
2613       if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
2614 				/* build new fake reply */
2615       LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*");
2616       LOCAL->reply.key = "BAD";
2617       LOCAL->reply.text = "Bad date in append";
2618       return &LOCAL->reply;
2619     }
2620     adat.type = ASTRING;
2621     adat.text = (void *) (date = mail_date (tmp,&elt));
2622     args[++i] = &adat;
2623   }
2624   amsg.type = LITERAL; amsg.text = (void *) message;
2625   args[++i] = &amsg;
2626   args[++i] = NIL;
2627 				/* easy if IMAP4[rev1] */
2628   if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args);
2629   else {			/* try the IMAP2bis way */
2630     args[1] = &amsg; args[2] = NIL;
2631     reply = imap_send (stream,"APPEND",args);
2632   }
2633   return reply;
2634 }
2635 
2636 /* IMAP garbage collect stream
2637  * Accepts: Mail stream
2638  *	    garbage collection flags
2639  */
2640 
imap_gc(MAILSTREAM * stream,long gcflags)2641 void imap_gc (MAILSTREAM *stream,long gcflags)
2642 {
2643   unsigned long i;
2644   MESSAGECACHE *elt;
2645   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2646 				/* make sure the cache is large enough */
2647   (*mc) (stream,stream->nmsgs,CH_SIZE);
2648   if (gcflags & GC_TEXTS) {	/* garbage collect texts? */
2649     if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
2650       if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT))
2651 	imap_gc_body (elt->private.msg.body);
2652     imap_gc_body (stream->body);
2653   }
2654 				/* gc cache if requested and unlocked */
2655   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
2656     if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
2657 	(elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
2658 }
2659 
2660 /* IMAP garbage collect body texts
2661  * Accepts: body to GC
2662  */
2663 
imap_gc_body(BODY * body)2664 void imap_gc_body (BODY *body)
2665 {
2666   PART *part;
2667   if (body) {			/* have a body? */
2668     if (body->mime.text.data)	/* flush MIME data */
2669       fs_give ((void **) &body->mime.text.data);
2670 				/* flush text contents */
2671     if (body->contents.text.data)
2672       fs_give ((void **) &body->contents.text.data);
2673     body->mime.text.size = body->contents.text.size = 0;
2674 				/* multipart? */
2675     if (body->type == TYPEMULTIPART)
2676       for (part = body->nested.part; part; part = part->next)
2677 	imap_gc_body (&part->body);
2678 				/* MESSAGE/RFC822? */
2679     else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
2680       imap_gc_body (body->nested.msg->body);
2681       if (body->nested.msg->full.text.data)
2682 	fs_give ((void **) &body->nested.msg->full.text.data);
2683       if (body->nested.msg->header.text.data)
2684 	fs_give ((void **) &body->nested.msg->header.text.data);
2685       if (body->nested.msg->text.text.data)
2686 	fs_give ((void **) &body->nested.msg->text.text.data);
2687       body->nested.msg->full.text.size = body->nested.msg->header.text.size =
2688 	body->nested.msg->text.text.size = 0;
2689     }
2690   }
2691 }
2692 
2693 /* IMAP get capabilities
2694  * Accepts: mail stream
2695  */
2696 
imap_capability(MAILSTREAM * stream)2697 void imap_capability (MAILSTREAM *stream)
2698 {
2699   THREADER *thr,*t;
2700   LOCAL->gotcapability = NIL;	/* flush any previous capabilities */
2701 				/* request new capabilities */
2702   imap_send (stream,"CAPABILITY",NIL);
2703   if (!LOCAL->gotcapability) {	/* did server get any? */
2704 				/* no, flush threaders just in case */
2705     if (thr = LOCAL->cap.threader) while (t = thr) {
2706       fs_give ((void **) &t->name);
2707       thr = t->next;
2708       fs_give ((void **) &t);
2709     }
2710 				/* zap most capabilities */
2711     memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
2712 				/* assume IMAP2bis server if failure */
2713     LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
2714   }
2715 }
2716 
2717 /* IMAP set ACL
2718  * Accepts: mail stream
2719  *	    mailbox name
2720  *	    authentication identifer
2721  *	    new access rights
2722  * Returns: T on success, NIL on failure
2723  */
2724 
imap_setacl(MAILSTREAM * stream,char * mailbox,char * id,char * rights)2725 long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights)
2726 {
2727   IMAPARG *args[4],ambx,aid,art;
2728   ambx.type = aid.type = art.type = ASTRING;
2729   ambx.text = (void *) mailbox; aid.text = (void *) id;
2730   art.text = (void *) rights;
2731   args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL;
2732   return imap_acl_work (stream,"SETACL",args);
2733 }
2734 
2735 
2736 /* IMAP delete ACL
2737  * Accepts: mail stream
2738  *	    mailbox name
2739  *	    authentication identifer
2740  * Returns: T on success, NIL on failure
2741  */
2742 
imap_deleteacl(MAILSTREAM * stream,char * mailbox,char * id)2743 long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id)
2744 {
2745   IMAPARG *args[3],ambx,aid;
2746   ambx.type = aid.type = ASTRING;
2747   ambx.text = (void *) mailbox; aid.text = (void *) id;
2748   args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2749   return imap_acl_work (stream,"DELETEACL",args);
2750 }
2751 
2752 
2753 /* IMAP get ACL
2754  * Accepts: mail stream
2755  *	    mailbox name
2756  * Returns: T on success with data returned via callback, NIL on failure
2757  */
2758 
imap_getacl(MAILSTREAM * stream,char * mailbox)2759 long imap_getacl (MAILSTREAM *stream,char *mailbox)
2760 {
2761   IMAPARG *args[2],ambx;
2762   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2763     args[0] = &ambx; args[1] = NIL;
2764   return imap_acl_work (stream,"GETACL",args);
2765 }
2766 
2767 /* IMAP list rights
2768  * Accepts: mail stream
2769  *	    mailbox name
2770  *	    authentication identifer
2771  * Returns: T on success with data returned via callback, NIL on failure
2772  */
2773 
imap_listrights(MAILSTREAM * stream,char * mailbox,char * id)2774 long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id)
2775 {
2776   IMAPARG *args[3],ambx,aid;
2777   ambx.type = aid.type = ASTRING;
2778   ambx.text = (void *) mailbox; aid.text = (void *) id;
2779   args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2780   return imap_acl_work (stream,"LISTRIGHTS",args);
2781 }
2782 
2783 
2784 /* IMAP my rights
2785  * Accepts: mail stream
2786  *	    mailbox name
2787  * Returns: T on success with data returned via callback, NIL on failure
2788  */
2789 
imap_myrights(MAILSTREAM * stream,char * mailbox)2790 long imap_myrights (MAILSTREAM *stream,char *mailbox)
2791 {
2792   IMAPARG *args[2],ambx;
2793   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2794   args[0] = &ambx; args[1] = NIL;
2795   return imap_acl_work (stream,"MYRIGHTS",args);
2796 }
2797 
2798 
2799 /* IMAP ACL worker routine
2800  * Accepts: mail stream
2801  *	    command
2802  *	    command arguments
2803  * Returns: T on success, NIL on failure
2804  */
2805 
imap_acl_work(MAILSTREAM * stream,char * command,IMAPARG * args[])2806 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[])
2807 {
2808   long ret = NIL;
2809   if (LEVELACL (stream)) {	/* send command */
2810     IMAPPARSEDREPLY *reply;
2811     if (imap_OK (stream,reply = imap_send (stream,command,args)))
2812       ret = LONGT;
2813     else mm_log (reply->text,ERROR);
2814   }
2815   else mm_log ("ACL not available on this IMAP server",ERROR);
2816   return ret;
2817 }
2818 
2819 /* IMAP set quota
2820  * Accepts: mail stream
2821  *	    quota root name
2822  *	    resource limit list as a stringlist
2823  * Returns: T on success with data returned via callback, NIL on failure
2824  */
2825 
imap_setquota(MAILSTREAM * stream,char * qroot,STRINGLIST * limits)2826 long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits)
2827 {
2828   long ret = NIL;
2829   if (LEVELQUOTA (stream)) {	/* send "SETQUOTA" */
2830     IMAPPARSEDREPLY *reply;
2831     IMAPARG *args[3],aqrt,alim;
2832     aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2833     alim.type = SNLIST; alim.text = (void *) limits;
2834     args[0] = &aqrt; args[1] = &alim; args[2] = NIL;
2835     if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args)))
2836       ret = LONGT;
2837     else mm_log (reply->text,ERROR);
2838   }
2839   else mm_log ("Quota not available on this IMAP server",ERROR);
2840   return ret;
2841 }
2842 
2843 /* IMAP get quota
2844  * Accepts: mail stream
2845  *	    quota root name
2846  * Returns: T on success with data returned via callback, NIL on failure
2847  */
2848 
imap_getquota(MAILSTREAM * stream,char * qroot)2849 long imap_getquota (MAILSTREAM *stream,char *qroot)
2850 {
2851   long ret = NIL;
2852   if (LEVELQUOTA (stream)) {	/* send "GETQUOTA" */
2853     IMAPPARSEDREPLY *reply;
2854     IMAPARG *args[2],aqrt;
2855     aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2856     args[0] = &aqrt; args[1] = NIL;
2857     if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args)))
2858       ret = LONGT;
2859     else mm_log (reply->text,ERROR);
2860   }
2861   else mm_log ("Quota not available on this IMAP server",ERROR);
2862   return ret;
2863 }
2864 
2865 
2866 /* IMAP get quota root
2867  * Accepts: mail stream
2868  *	    mailbox name
2869  * Returns: T on success with data returned via callback, NIL on failure
2870  */
2871 
imap_getquotaroot(MAILSTREAM * stream,char * mailbox)2872 long imap_getquotaroot (MAILSTREAM *stream,char *mailbox)
2873 {
2874   long ret = NIL;
2875   if (LEVELQUOTA (stream)) {	/* send "GETQUOTAROOT" */
2876     IMAPPARSEDREPLY *reply;
2877     IMAPARG *args[2],ambx;
2878     ambx.type = ASTRING; ambx.text = (void *) mailbox;
2879     args[0] = &ambx; args[1] = NIL;
2880     if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args)))
2881       ret = LONGT;
2882     else mm_log (reply->text,ERROR);
2883   }
2884   else mm_log ("Quota not available on this IMAP server",ERROR);
2885   return ret;
2886 }
2887 
2888 /* Internal routines */
2889 
2890 
2891 /* IMAP send command
2892  * Accepts: MAIL stream
2893  *	    command
2894  *	    argument list
2895  * Returns: parsed reply
2896  */
2897 
2898 #define CMDBASE LOCAL->tmp	/* command base */
2899 
imap_send(MAILSTREAM * stream,char * cmd,IMAPARG * args[])2900 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
2901 {
2902   IMAPPARSEDREPLY *reply;
2903   IMAPARG *arg,**arglst;
2904   SORTPGM *spg;
2905   STRINGLIST *list;
2906   SIZEDTEXT st;
2907   APPENDDATA *map;
2908   sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL);
2909   size_t i;
2910   void *a;
2911   char c,*s,*t,tag[10];
2912   stream->unhealthy = NIL;	/* make stream healthy again */
2913   				/* gensym a new tag */
2914   sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
2915   if (!LOCAL->netstream)	/* make sure have a session */
2916     return imap_fake (stream,tag,"[CLOSED] IMAP connection lost");
2917   mail_lock (stream);		/* lock up the stream */
2918   if (sc)			/* tell client sending a command */
2919     (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") &&
2920 			compare_cstring (cmd,"STORE") &&
2921 			compare_cstring (cmd,"SEARCH")) ?
2922 		       NIL : SC_EXPUNGEDEFERRED));
2923 				/* ignore referral from previous command */
2924   if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
2925   sprintf (CMDBASE,"%s %s",tag,cmd);
2926   s = CMDBASE + strlen (CMDBASE);
2927   if (arglst = args) while (arg = *arglst++) {
2928     *s++ = ' ';			/* delimit argument with space */
2929     switch (arg->type) {
2930     case ATOM:			/* atom */
2931       for (t = (char *) arg->text; *t; *s++ = *t++);
2932       break;
2933     case NUMBER:		/* number */
2934       sprintf (s,"%lu",(unsigned long) arg->text);
2935       s += strlen (s);
2936       break;
2937     case FLAGS:			/* flag list as a single string */
2938       if (*(t = (char *) arg->text) != '(') {
2939 	*s++ = '(';		/* wrap parens around string */
2940 	while (*t) *s++ = *t++;
2941 	*s++ = ')';		/* wrap parens around string */
2942       }
2943       else while (*t) *s++ = *t++;
2944       break;
2945     case ASTRING:		/* atom or string, must be literal? */
2946       st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
2947       if (reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND))
2948 	return reply;
2949       break;
2950     case LITERAL:		/* literal, as a stringstruct */
2951       if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply;
2952       break;
2953 
2954     case LIST:			/* list of strings */
2955       list = (STRINGLIST *) arg->text;
2956       c = '(';			/* open paren */
2957       do {			/* for each list item */
2958 	*s++ = c;		/* write prefix character */
2959 	if (reply = imap_send_astring (stream,tag,&s,&list->text,NIL,
2960 				       CMDBASE+MAXCOMMAND)) return reply;
2961 	c = ' ';		/* prefix character for subsequent strings */
2962       }
2963       while (list = list->next);
2964       *s++ = ')';		/* close list */
2965       break;
2966     case SEARCHPROGRAM:		/* search program */
2967       if (reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
2968 				  CMDBASE+MAXCOMMAND))
2969 	return reply;
2970       break;
2971     case SORTPROGRAM:		/* search program */
2972       c = '(';			/* open paren */
2973       for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) {
2974 	*s++ = c;		/* write prefix */
2975 	if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++);
2976 	switch (spg->function) {
2977 	case SORTDATE:
2978 	  for (t = "DATE"; *t; *s++ = *t++);
2979 	  break;
2980 	case SORTARRIVAL:
2981 	  for (t = "ARRIVAL"; *t; *s++ = *t++);
2982 	  break;
2983 	case SORTFROM:
2984 	  for (t = "FROM"; *t; *s++ = *t++);
2985 	  break;
2986 	case SORTSUBJECT:
2987 	  for (t = "SUBJECT"; *t; *s++ = *t++);
2988 	  break;
2989 	case SORTTO:
2990 	  for (t = "TO"; *t; *s++ = *t++);
2991 	  break;
2992 	case SORTCC:
2993 	  for (t = "CC"; *t; *s++ = *t++);
2994 	  break;
2995 	case SORTSIZE:
2996 	  for (t = "SIZE"; *t; *s++ = *t++);
2997 	  break;
2998 	default:
2999 	  fatal ("Unknown sort program function in imap_send()!");
3000 	}
3001 	c = ' ';		/* prefix character for subsequent items */
3002       }
3003       *s++ = ')';		/* close list */
3004       break;
3005 
3006     case BODYTEXT:		/* body section */
3007       for (t = "BODY["; *t; *s++ = *t++);
3008       for (t = (char *) arg->text; *t; *s++ = *t++);
3009       break;
3010     case BODYPEEK:		/* body section */
3011       for (t = "BODY.PEEK["; *t; *s++ = *t++);
3012       for (t = (char *) arg->text; *t; *s++ = *t++);
3013       break;
3014     case BODYCLOSE:		/* close bracket and possible length */
3015       s[-1] = ']';		/* no leading space */
3016       for (t = (char *) arg->text; *t; *s++ = *t++);
3017       break;
3018     case SEQUENCE:		/* sequence */
3019       if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND)
3020 	while (*t) *s++ = *t++;	/* easy case */
3021       else {
3022 	mail_unlock (stream);	/* unlock stream */
3023 	a = arg->text;		/* save original sequence pointer */
3024 	arg->type = ATOM;	/* make recursive call be faster */
3025 	do {			/* break up into multiple commands */
3026 	  if (i <= MAXCOMMAND) {/* final part? */
3027 	    reply = imap_send (stream,cmd,args);
3028 	    i = 0;		/* and mark as done */
3029 	  }
3030 	  else {		/* still needs to be split further */
3031 	    if (!(t = strchr (t + MAXCOMMAND - 30,',')) ||
3032 		((t - (char *) arg->text) > MAXCOMMAND))
3033 	      fatal ("impossible over-long sequence");
3034 	    *t = '\0';		/* tie off sequence at point of split*/
3035 				/* recurse to do this part */
3036 	    reply = imap_send (stream,cmd,args);
3037 	    *t++ = ',';		/* restore the comma in case something cares */
3038 				/* punt if error */
3039 	    if (!imap_OK (stream,reply)) break;
3040 				/* calculate size of remaining sequence */
3041 	    i -= (t - (char *) arg->text);
3042 				/* point to new remaining sequence */
3043 	    arg->text = (void *) t;
3044 	  }
3045 	} while (i);
3046 	arg->type = SEQUENCE;	/* restore in case something cares */
3047 	arg->text = a;
3048 	return reply;		/* return result */
3049       }
3050       break;
3051     case LISTMAILBOX:		/* astring with wildcards */
3052       st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3053       if (reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND))
3054 	return reply;
3055       break;
3056 
3057     case MULTIAPPEND:		/* append multiple messages */
3058 				/* get package pointer */
3059       map = (APPENDDATA *) arg->text;
3060       if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)||
3061 	  !map->message) {
3062 	STRING es;
3063 	INIT (&es,mail_string,"",0);
3064 	return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3065 	  reply : imap_fake (stream,tag,"Server zero-length literal error");
3066       }
3067     case MULTIAPPENDREDO:	/* redo multiappend */
3068 				/* get package pointer */
3069       map = (APPENDDATA *) arg->text;
3070       do {			/* make sure date valid if given */
3071 	char datetmp[MAILTMPLEN];
3072 	MESSAGECACHE elt;
3073 	STRING es;
3074 	if (!map->date || mail_parse_date (&elt,map->date)) {
3075 	  if (t = map->flags) {	/* flags given? */
3076 	    if (*t != '(') {
3077 	      *s++ = '(';	/* wrap parens around string */
3078 	      while (*t) *s++ = *t++;
3079 	      *s++ = ')';	/* wrap parens around string */
3080 	    }
3081 	    else while (*t) *s++ = *t++;
3082 	    *s++ = ' ';		/* delimit with space */
3083 	  }
3084 	  if (map->date) {	/* date given? */
3085 	    st.size = strlen ((char *) (st.data = (unsigned char *)
3086 					mail_date (datetmp,&elt)));
3087 	    if (reply = imap_send_astring (stream,tag,&s,&st,NIL,
3088 					   CMDBASE+MAXCOMMAND)) return reply;
3089 	    *s++ = ' ';		/* delimit with space */
3090 	  }
3091 	  if (reply = imap_send_literal (stream,tag,&s,map->message))
3092 	    return reply;
3093 				/* get next message */
3094 	  if ((*map->af) (stream,map->data,&map->flags,&map->date,
3095 			  &map->message)) {
3096 				/* have a message, delete next in command */
3097 	    if (map->message) *s++ = ' ';
3098 	    continue;		/* loop back for next message */
3099 	  }
3100 	}
3101 				/* bad date or need to abort */
3102 	INIT (&es,mail_string,"",0);
3103 	return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3104 	  reply : imap_fake (stream,tag,"Server zero-length literal error");
3105 	break;			/* exit the loop */
3106       } while (map->message);
3107       break;
3108 
3109     case SNLIST:		/* list of string/number pairs */
3110       list = (STRINGLIST *) arg->text;
3111       c = '(';			/* open paren */
3112       do {			/* for each list item */
3113 	*s++ = c;		/* write prefix character */
3114 	if (list) {		/* sigh, QUOTA has bizarre syntax! */
3115 	  for (t = (char *) list->text.data; *t; *s++ = *t++);
3116 	  sprintf (s," %lu",list->text.size);
3117 	  s += strlen (s);
3118 	  c = ' ';		/* prefix character for subsequent strings */
3119 	}
3120       }
3121       while (list = list->next);
3122       *s++ = ')';		/* close list */
3123       break;
3124     default:
3125       fatal ("Unknown argument type in imap_send()!");
3126     }
3127   }
3128 				/* send the command */
3129   reply = imap_sout (stream,tag,CMDBASE,&s);
3130   mail_unlock (stream);		/* unlock stream */
3131   return reply;
3132 }
3133 
3134 /* IMAP send atom-string
3135  * Accepts: MAIL stream
3136  *	    reply tag
3137  *	    pointer to current position pointer of output bigbuf
3138  *	    atom-string to output
3139  *	    flag if list_wildcards allowed
3140  *	    maximum to write as atom or qstring
3141  * Returns: error reply or NIL if success
3142  */
3143 
imap_send_astring(MAILSTREAM * stream,char * tag,char ** s,SIZEDTEXT * as,long wildok,char * limit)3144 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
3145 				    SIZEDTEXT *as,long wildok,char *limit)
3146 {
3147   unsigned long j;
3148   char c;
3149   STRING st;
3150 				/* default to atom unless empty or loser */
3151   int qflag = (as->size && !LOCAL->loser) ? NIL : T;
3152 				/* in case needed */
3153   INIT (&st,mail_string,(void *) as->data,as->size);
3154 				/* always write literal if no space */
3155   if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st);
3156   for (j = 0; j < as->size; j++) switch (c = as->data[j]) {
3157   default:			/* all other characters */
3158     if (!(c & 0x80)) {		/* must not be 8bit */
3159       if (c <= ' ') qflag = T;	/* must quote if a CTL */
3160       break;
3161     }
3162   case '\0':			/* not a CHAR */
3163   case '\012': case '\015':	/* not a TEXT-CHAR */
3164   case '"': case '\\':		/* quoted-specials (IMAP2 required this) */
3165     return imap_send_literal (stream,tag,s,&st);
3166   case '*': case '%':		/* list_wildcards */
3167     if (wildok) break;		/* allowed if doing the wild thing */
3168 				/* atom_specials */
3169   case '(': case ')': case '{': case ' ': case 0x7f:
3170 #if 0
3171   case '"': case '\\':		/* quoted-specials (could work in IMAP4) */
3172 #endif
3173     qflag = T;			/* must use quoted string format */
3174     break;
3175   }
3176   if (qflag) *(*s)++ = '"';	/* write open quote */
3177   for (j = 0; j < as->size; j++) *(*s)++ = as->data[j];
3178   if (qflag) *(*s)++ = '"';	/* write close quote */
3179   return NIL;
3180 }
3181 
3182 /* IMAP send literal
3183  * Accepts: MAIL stream
3184  *	    reply tag
3185  *	    pointer to current position pointer of output bigbuf
3186  *	    literal to output as stringstruct
3187  * Returns: error reply or NIL if success
3188  */
3189 
imap_send_literal(MAILSTREAM * stream,char * tag,char ** s,STRING * st)3190 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
3191 				    STRING *st)
3192 {
3193   IMAPPARSEDREPLY *reply;
3194   unsigned long i = SIZE (st);
3195   unsigned long j;
3196   sprintf (*s,"{%lu}",i);	/* write literal count */
3197   *s += strlen (*s);		/* size of literal count */
3198 				/* send the command */
3199   reply = imap_sout (stream,tag,CMDBASE,s);
3200   if (strcmp (reply->tag,"+")) {/* prompt for more data? */
3201     mail_unlock (stream);	/* no, give up */
3202     return reply;
3203   }
3204   while (i) {			/* dump the text */
3205     if (st->cursize) {		/* if text to do in this chunk */
3206       /* RFC 3501 technically forbids NULs in literals.  Normally, the
3207        * delivering MTA would take care of MIME converting the message text
3208        * so that it is NUL-free.  If it doesn't, then we have the choice of
3209        * either violating IMAP by sending NULs, corrupting the data, or going
3210        * to lots of work to do MIME conversion in the IMAP server.
3211        *
3212        * No current stringstruct driver objects to having its buffer patched.
3213        * If this ever changes, it will be necessary to change this kludge.
3214        */
3215 				/* patch NULs to C1 control */
3216       for (j = 0; j < st->cursize; ++j)
3217 	if (!st->curpos[j]) st->curpos[j] = 0x80;
3218       if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) {
3219 	mail_unlock (stream);
3220 	return imap_fake (stream,tag,"[CLOSED] IMAP connection broken (data)");
3221       }
3222       i -= st->cursize;		/* note that we wrote out this much */
3223       st->curpos += (st->cursize - 1);
3224       st->cursize = 0;
3225     }
3226     (*st->dtb->next) (st);	/* advance to next buffer's worth */
3227   }
3228   return NIL;			/* success */
3229 }
3230 
3231 /* IMAP send search program
3232  * Accepts: MAIL stream
3233  *	    reply tag
3234  *	    base pointer if trimming needed
3235  *	    pointer to current position pointer of output bigbuf
3236  *	    search program to output
3237  *	    pointer to limit guideline
3238  * Returns: error reply or NIL if success
3239  */
3240 
3241 
imap_send_spgm(MAILSTREAM * stream,char * tag,char * base,char ** s,SEARCHPGM * pgm,char * limit)3242 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
3243 				 char **s,SEARCHPGM *pgm,char *limit)
3244 {
3245   IMAPPARSEDREPLY *reply;
3246   SEARCHHEADER *hdr;
3247   SEARCHOR *pgo;
3248   SEARCHPGMLIST *pgl;
3249   char *t;
3250 				/* trim if called recursively */
3251   if (base) *s = imap_send_spgm_trim (base,*s,NIL);
3252   base = *s;			/* this is the new base */
3253 				/* default searchpgm */
3254   for (t = "ALL"; *t; *(*s)++ = *t++);
3255   if (!pgm) return NIL;		/* done if NIL searchpgm */
3256   if ((pgm->msgno &&		/* message sequences */
3257        (pgm->msgno->next ||	/* trim away first:last */
3258 	(pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) &&
3259        (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) ||
3260       (pgm->uid &&
3261        (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit))))
3262     return reply;
3263 				/* message sizes */
3264   if (pgm->larger) {
3265     sprintf (*s," LARGER %lu",pgm->larger);
3266     *s += strlen (*s);
3267   }
3268   if (pgm->smaller) {
3269     sprintf (*s," SMALLER %lu",pgm->smaller);
3270     *s += strlen (*s);
3271   }
3272 
3273 				/* message flags */
3274   if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++);
3275   if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++);
3276   if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++);
3277   if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++);
3278   if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++);
3279   if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++);
3280   if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++);
3281   if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++);
3282   if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++);
3283   if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++);
3284   if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++);
3285   if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++);
3286   if ((pgm->keyword &&		/* keywords */
3287        (reply = imap_send_slist (stream,tag,base,s," KEYWORD ",pgm->keyword,
3288 				 limit))) ||
3289       (pgm->unkeyword &&
3290        (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ",
3291 				 pgm->unkeyword,limit))))
3292     return reply;
3293 				/* sent date ranges */
3294   if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
3295   if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
3296   if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
3297 				/* internal date ranges */
3298   if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
3299   if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
3300   if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
3301   if (pgm->older) {
3302     sprintf (*s," OLDER %lu",pgm->older);
3303     *s += strlen (*s);
3304   }
3305   if (pgm->younger) {
3306     sprintf (*s," YOUNGER %lu",pgm->younger);
3307     *s += strlen (*s);
3308   }
3309 				/* search texts */
3310   if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ",
3311 					     pgm->bcc,limit))) ||
3312       (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc,
3313 					    limit))) ||
3314       (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ",
3315 					      pgm->from,limit))) ||
3316       (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to,
3317 					    limit))))
3318     return reply;
3319   if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ",
3320 						 pgm->subject,limit))) ||
3321       (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ",
3322 					      pgm->body,limit))) ||
3323       (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ",
3324 					      pgm->text,limit))))
3325     return reply;
3326 
3327   /* Note that these criteria are not supported by IMAP and have to be
3328      emulated */
3329   if ((pgm->return_path &&
3330        (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ",
3331 				 pgm->return_path,limit))) ||
3332       (pgm->sender &&
3333        (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ",
3334 				 pgm->sender,limit))) ||
3335       (pgm->reply_to &&
3336        (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ",
3337 				 pgm->reply_to,limit))) ||
3338       (pgm->in_reply_to &&
3339        (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ",
3340 				 pgm->in_reply_to,limit))) ||
3341       (pgm->message_id &&
3342        (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ",
3343 				 pgm->message_id,limit))) ||
3344       (pgm->newsgroups &&
3345        (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ",
3346 				 pgm->newsgroups,limit))) ||
3347       (pgm->followup_to &&
3348        (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ",
3349 				 pgm->followup_to,limit))) ||
3350       (pgm->references &&
3351        (reply = imap_send_slist (stream,tag,base,s," HEADER References ",
3352 				 pgm->references,limit)))) return reply;
3353 
3354 				/* all other headers */
3355   if (hdr = pgm->header) do {
3356     *s = imap_send_spgm_trim (base,*s," HEADER ");
3357     if (reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit))
3358       return reply;
3359     *(*s)++ = ' ';
3360     if (reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit))
3361       return reply;
3362   } while (hdr = hdr->next);
3363   for (pgo = pgm->or; pgo; pgo = pgo->next) {
3364     *s = imap_send_spgm_trim (base,*s," OR (");
3365     if (reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit))
3366       return reply;
3367     for (t = ") ("; *t; *(*s)++ = *t++);
3368     if (reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit))
3369       return reply;
3370     *(*s)++ = ')';
3371   }
3372   for (pgl = pgm->not; pgl; pgl = pgl->next) {
3373     *s = imap_send_spgm_trim (base,*s," NOT (");
3374     if (reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit))
3375       return reply;
3376     *(*s)++ = ')';
3377   }
3378 				/* trim if needed */
3379   *s = imap_send_spgm_trim (base,*s,NIL);
3380   return NIL;			/* search program written OK */
3381 }
3382 
3383 
3384 /* Write new text and trim extraneous "ALL" from searchpgm
3385  * Accepts: pointer to start of searchpgm or NIL
3386  *	    current end pointer
3387  *	    new text to write or NIL
3388  * Returns: new end pointer, trimmed if needed
3389  */
3390 
imap_send_spgm_trim(char * base,char * s,char * text)3391 char *imap_send_spgm_trim (char *base,char *s,char *text)
3392 {
3393   char *t;
3394 				/* write new text */
3395   if (text) for (t = text; *t; *s++ = *t++);
3396 				/* need to trim? */
3397   if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') &&
3398       (base[2] == 'L') && (base[3] == ' ')) {
3399     memmove (base,t,s - t);	/* yes, blat down remaining text */
3400     s -= 4;			/* and reduce current pointer */
3401   }
3402   return s;			/* return new end pointer */
3403 }
3404 
3405 /* IMAP send search set
3406  * Accepts: MAIL stream
3407  *	    current command tag
3408  *	    base pointer if trimming needed
3409  *	    pointer to current position pointer of output bigbuf
3410  *	    search set to output
3411  *	    message prefix
3412  *	    maximum output pointer
3413  * Returns: NIL if success, error reply if error
3414  */
3415 
imap_send_sset(MAILSTREAM * stream,char * tag,char * base,char ** s,SEARCHSET * set,char * prefix,char * limit)3416 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
3417 				 char **s,SEARCHSET *set,char *prefix,
3418 				 char *limit)
3419 {
3420   IMAPPARSEDREPLY *reply;
3421   STRING st;
3422   char c,*t;
3423   char *start = *s;
3424 				/* trim and write prefix */
3425   *s = imap_send_spgm_trim (base,*s,prefix);
3426 				/* run down search list */
3427   for (c = NIL; set && (*s < limit); set = set->next, c = ',') {
3428     if (c) *(*s)++ = c;		/* write delimiter and first value */
3429     if (set->first == 0xffffffff) *(*s)++ = '*';
3430     else {
3431       sprintf (*s,"%lu",set->first);
3432       *s += strlen (*s);
3433     }
3434 				/* have a second value? */
3435     if (set->last && (set->first != set->last)) {
3436       *(*s)++ = ':';		/* write delimiter and second value */
3437       if (set->last == 0xffffffff) *(*s)++ = '*';
3438       else {
3439 	sprintf (*s,"%lu",set->last);
3440 	*s += strlen (*s);
3441       }
3442     }
3443   }
3444   if (set) {			/* insert "OR" in front of incomplete set */
3445     memmove (start + 3,start,*s - start);
3446     memcpy (start," OR",3);
3447     *s += 3;			/* point to end of buffer */
3448 				/* write glue that is equivalent to ALL */
3449     for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++);
3450 				/* but broken by a literal */
3451     INIT (&st,mail_string,(void *) "FOO",3);
3452     if (reply = imap_send_literal (stream,tag,s,&st)) return reply;
3453     *(*s)++ = ')';		/* close glue */
3454     if (reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit))
3455       return reply;
3456     *(*s)++ = ')';		/* close second OR argument */
3457   }
3458   return NIL;
3459 }
3460 
3461 /* IMAP send search list
3462  * Accepts: MAIL stream
3463  *	    reply tag
3464  *	    base pointer if trimming needed
3465  *	    pointer to current position pointer of output bigbuf
3466  *	    name of search list
3467  *	    search list to output
3468  *	    maximum output pointer
3469  * Returns: NIL if success, error reply if error
3470  */
3471 
imap_send_slist(MAILSTREAM * stream,char * tag,char * base,char ** s,char * name,STRINGLIST * list,char * limit)3472 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
3473 				  char **s,char *name,STRINGLIST *list,
3474 				  char *limit)
3475 {
3476   IMAPPARSEDREPLY *reply;
3477   do {
3478     *s = imap_send_spgm_trim (base,*s,name);
3479     base = NIL;			/* no longer need trimming */
3480     reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit);
3481   }
3482   while (!reply && (list = list->next));
3483   return reply;
3484 }
3485 
3486 
3487 /* IMAP send search date
3488  * Accepts: pointer to current position pointer of output bigbuf
3489  *	    field name
3490  *	    search date to output
3491  */
3492 
imap_send_sdate(char ** s,char * name,unsigned short date)3493 void imap_send_sdate (char **s,char *name,unsigned short date)
3494 {
3495   sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
3496 	   months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
3497   *s += strlen (*s);
3498 }
3499 
3500 /* IMAP send buffered command to sender
3501  * Accepts: MAIL stream
3502  *	    reply tag
3503  *	    string
3504  *	    pointer to string tail pointer
3505  * Returns: reply
3506  */
3507 
imap_sout(MAILSTREAM * stream,char * tag,char * base,char ** s)3508 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s)
3509 {
3510   IMAPPARSEDREPLY *reply;
3511   if (stream->debug) {		/* output debugging telemetry */
3512     **s = '\0';
3513     mail_dlog (base,LOCAL->sensitive);
3514   }
3515   *(*s)++ = '\015';		/* append CRLF */
3516   *(*s)++ = '\012';
3517   **s = '\0';
3518   reply = net_sout (LOCAL->netstream,base,*s - base) ?
3519     imap_reply (stream,tag) :
3520       imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)");
3521   *s = base;			/* restart buffer */
3522   return reply;
3523 }
3524 
3525 
3526 /* IMAP send null-terminated string to sender
3527  * Accepts: MAIL stream
3528  *	    string
3529  * Returns: T if success, else NIL
3530  */
3531 
imap_soutr(MAILSTREAM * stream,char * string)3532 long imap_soutr (MAILSTREAM *stream,char *string)
3533 {
3534   long ret;
3535   unsigned long i;
3536   char *s;
3537   if (stream->debug) mm_dlog (string);
3538   sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1),
3539 	   "%s\015\012",string);
3540   ret = net_sout (LOCAL->netstream,s,i);
3541   fs_give ((void **) &s);
3542   return ret;
3543 }
3544 
3545 /* IMAP get reply
3546  * Accepts: MAIL stream
3547  *	    tag to search or NIL if want a greeting
3548  * Returns: parsed reply, never NIL
3549  */
3550 
imap_reply(MAILSTREAM * stream,char * tag)3551 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
3552 {
3553   IMAPPARSEDREPLY *reply;
3554   while (LOCAL->netstream) {	/* parse reply from server */
3555     if (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) {
3556 				/* continuation ready? */
3557       if (!strcmp (reply->tag,"+")) return reply;
3558 				/* untagged data? */
3559       else if (!strcmp (reply->tag,"*")) {
3560 	imap_parse_unsolicited (stream,reply);
3561 	if (!tag) return reply;	/* return if just wanted greeting */
3562       }
3563       else {			/* tagged data */
3564 	if (tag && !compare_cstring (tag,reply->tag)) return reply;
3565 				/* report bogon */
3566 	sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
3567 		 (char *) reply->tag,(char *) reply->key,(char *) reply->text);
3568 	mm_notify (stream,LOCAL->tmp,WARN);
3569 	stream->unhealthy = T;
3570       }
3571     }
3572   }
3573   return imap_fake (stream,tag,
3574 		    "[CLOSED] IMAP connection broken (server response)");
3575 }
3576 
3577 /* IMAP parse reply
3578  * Accepts: MAIL stream
3579  *	    text of reply
3580  * Returns: parsed reply, or NIL if can't parse at least a tag and key
3581  */
3582 
3583 
imap_parse_reply(MAILSTREAM * stream,char * text)3584 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
3585 {
3586   char *r;
3587   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
3588 				/* init fields in case error */
3589   LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL;
3590   if (!(LOCAL->reply.line = text)) {
3591 				/* NIL text means the stream died */
3592     if (LOCAL->netstream) net_close (LOCAL->netstream);
3593     LOCAL->netstream = NIL;
3594     return NIL;
3595   }
3596   if (stream->debug) mm_dlog (LOCAL->reply.line);
3597   if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) {
3598     mm_notify (stream,"IMAP server sent a blank line",WARN);
3599     stream->unhealthy = T;
3600     return NIL;
3601   }
3602 				/* non-continuation replies */
3603   if (strcmp (LOCAL->reply.tag,"+")) {
3604 				/* parse key */
3605     if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) {
3606 				/* determine what is missing */
3607       sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",
3608 	       (char *) LOCAL->reply.tag);
3609       mm_notify (stream,LOCAL->tmp,WARN);
3610       stream->unhealthy = T;
3611       return NIL;		/* can't parse this text */
3612     }
3613     ucase (LOCAL->reply.key);	/* canonicalize key to upper */
3614 				/* get text as well, allow empty text */
3615     if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
3616       LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
3617   }
3618   else {			/* special handling of continuation */
3619     LOCAL->reply.key = "BAD";	/* so it barfs if not expecting continuation */
3620     if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
3621       LOCAL->reply.text = "";
3622   }
3623   return &LOCAL->reply;		/* return parsed reply */
3624 }
3625 
3626 /* IMAP fake reply when stream determined to be dead
3627  * Accepts: MAIL stream
3628  *	    tag
3629  *	    text of fake reply (must start with "[CLOSED]")
3630  * Returns: parsed reply
3631  */
3632 
imap_fake(MAILSTREAM * stream,char * tag,char * text)3633 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
3634 {
3635   mm_notify (stream,text,BYE);	/* send bye alert */
3636   if (LOCAL->netstream) net_close (LOCAL->netstream);
3637   LOCAL->netstream = NIL;	/* farewell, dear NET stream... */
3638 				/* flush previous reply */
3639   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
3640 				/* build new fake reply */
3641   LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*");
3642   LOCAL->reply.key = "NO";
3643   LOCAL->reply.text = text;
3644   return &LOCAL->reply;		/* return parsed reply */
3645 }
3646 
3647 
3648 /* IMAP check for OK response in tagged reply
3649  * Accepts: MAIL stream
3650  *	    parsed reply
3651  * Returns: T if OK else NIL
3652  */
3653 
imap_OK(MAILSTREAM * stream,IMAPPARSEDREPLY * reply)3654 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
3655 {
3656   long ret = NIL;
3657 				/* OK - operation succeeded */
3658   if (!strcmp (reply->key,"OK")) {
3659     imap_parse_response (stream,reply->text,NIL,NIL);
3660     ret = T;
3661   }
3662 				/* NO - operation failed */
3663   else if (!strcmp (reply->key,"NO"))
3664     imap_parse_response (stream,reply->text,WARN,NIL);
3665   else {			/* BAD - operation rejected */
3666     if (!strcmp (reply->key,"BAD")) {
3667       imap_parse_response (stream,reply->text,ERROR,NIL);
3668       sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text);
3669     }
3670 				/* bad protocol received */
3671     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
3672 		  (char *) reply->key,(char *) reply->text);
3673     mm_log (LOCAL->tmp,ERROR);	/* either way, this is not good */
3674   }
3675   return ret;
3676 }
3677 
3678 /* IMAP parse and act upon unsolicited reply
3679  * Accepts: MAIL stream
3680  *	    parsed reply
3681  */
3682 
imap_parse_unsolicited(MAILSTREAM * stream,IMAPPARSEDREPLY * reply)3683 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
3684 {
3685   unsigned long i = 0;
3686   unsigned long j,msgno;
3687   unsigned char *s,*t;
3688   char *r;
3689 				/* see if key is a number */
3690   if (isdigit (*reply->key)) {
3691     msgno = strtoul (reply->key,(char **) &s,10);
3692     if (*s) {			/* better be nothing after number */
3693       sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
3694 	       (char *) reply->key);
3695       mm_notify (stream,LOCAL->tmp,WARN);
3696       stream->unhealthy = T;
3697       return;
3698     }
3699     if (!reply->text) {		/* better be some data */
3700       mm_notify (stream,"Missing message data",WARN);
3701       stream->unhealthy = T;
3702       return;
3703     }
3704 				/* get message data type, canonicalize upper */
3705     s = ucase (strtok_r (reply->text," ",&r));
3706 				/* and locate the text after it */
3707     t = strtok_r (NIL,"\n",&r);
3708 				/* now take the action */
3709 				/* change in size of mailbox */
3710     if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs))
3711       mail_exists (stream,msgno);
3712     else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs))
3713       mail_recent (stream,msgno);
3714     else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) {
3715       mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
3716       MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
3717       if (elt) imap_gc_body (elt->private.msg.body);
3718 				/* notify upper level */
3719       mail_expunged (stream,msgno);
3720     }
3721 
3722     else if ((!strcmp (s,"FETCH") || !strcmp (s,"STORE")) &&
3723 	     msgno && (msgno <= stream->nmsgs)) {
3724       char *prop;
3725       GETS_DATA md;
3726       ENVELOPE **e;
3727       MESSAGECACHE *elt = mail_elt (stream,msgno);
3728       ENVELOPE *env = NIL;
3729       imapenvelope_t ie =
3730 	(imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL);
3731       ++t;			/* skip past open parenthesis */
3732 				/* parse Lisp-form property list */
3733       while (prop = (strtok_r (t," )",&r))) {
3734 	t = strtok_r (NIL,"\n",&r);
3735 	INIT_GETS (md,stream,elt->msgno,NIL,0,0);
3736 	e = NIL;		/* not pointing at any envelope yet */
3737 				/* canonicalize property, parse it */
3738 	if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t);
3739 	else if (!strcmp (prop,"INTERNALDATE") &&
3740 		 (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) {
3741 	  if (!mail_parse_date (elt,s)) {
3742 	    sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s);
3743 	    mm_notify (stream,LOCAL->tmp,WARN);
3744 	    stream->unhealthy = T;
3745 				/* slam in default so we don't try again */
3746 	    mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000");
3747 	  }
3748 	  fs_give ((void **) &s);
3749 	}
3750 				/* unique identifier */
3751 	else if (!strcmp (prop,"UID")) {
3752 	  LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10);
3753 	  LOCAL->lastuid.msgno = elt->msgno;
3754 	}
3755 	else if (!strcmp (prop,"ENVELOPE")) {
3756 	  if (stream->scache) {	/* short cache, flush old stuff */
3757 	    mail_free_body (&stream->body);
3758 	    stream->msgno = elt->msgno;
3759 	    e = &stream->env;	/* get pointer to envelope */
3760 	  }
3761 	  else e = &elt->private.msg.env;
3762 	  imap_parse_envelope (stream,e,&t,reply);
3763 	}
3764 	else if (!strncmp (prop,"BODY",4)) {
3765 	  if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) {
3766 	    BODY **body;
3767 	    if (stream->scache){/* short cache, flush old stuff */
3768 	      if (stream->msgno != msgno) {
3769 		mail_free_envelope (&stream->env);
3770 		sprintf (LOCAL->tmp,"Body received for %lu but current is %lu",
3771 			 msgno,stream->msgno);
3772 		stream->msgno = msgno;
3773 	      }
3774 				/* get pointer to body */
3775 	      body = &stream->body;
3776 	    }
3777 	    else body = &elt->private.msg.body;
3778 				/* flush any prior body */
3779 	    mail_free_body (body);
3780 				/* instantiate and parse a new body */
3781 	    imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply);
3782 	  }
3783 
3784 	  else if (prop[4] == '[') {
3785 	    STRINGLIST *stl = NIL;
3786 	    SIZEDTEXT text;
3787 				/* will want to return envelope data */
3788 	    if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") ||
3789 		!strcmp (md.what,"0]"))
3790 	      e = stream->scache ? &stream->env : &elt->private.msg.env;
3791 	    LOCAL->tmp[0] ='\0';/* no errors yet */
3792 				/* found end of section? */
3793 	    if (!(s = strchr (md.what,']'))) {
3794 				/* skip leading nesting */
3795 	      for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++);
3796 				/* better be one of these */
3797 	      if (strncmp (s,"HEADER.FIELDS",13) &&
3798 		  (!s[13] || strcmp (s+13,".NOT")))
3799 		sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what);
3800 				/* get list of headers */
3801 	      else if (!(stl = imap_parse_stringlist (stream,&t,reply)))
3802 		sprintf (LOCAL->tmp,"Bogus header field list: %.80s",
3803 			 (char *) t);
3804 	      else if (*t != ']')
3805 		sprintf (LOCAL->tmp,"Unterminated header section: %.80s",
3806 			 (char *) t);
3807 				/* point after the text */
3808 	      else if (t = strchr (s = t,' ')) *t++ = '\0';
3809 	    }
3810 	    if (s && !LOCAL->tmp[0]) {
3811 	      *s++ = '\0';	/* tie off section specifier */
3812 	      if (*s == '<') {	/* partial specifier? */
3813 		md.first = strtoul (s+1,(char **) &s,10) + 1;
3814 		if (*s++ != '>')	/* make sure properly terminated */
3815 		  sprintf (LOCAL->tmp,"Unterminated partial data: %.80s",
3816 			   (char *) s-1);
3817 	      }
3818 	      if (!LOCAL->tmp[0] && *s)
3819 		sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s);
3820 	    }
3821 	    if (LOCAL->tmp[0]) { /* got any errors? */
3822 	      mm_notify (stream,LOCAL->tmp,WARN);
3823 	      stream->unhealthy = T;
3824 	      mail_free_stringlist (&stl);
3825 	    }
3826 	    else {		/* parse text from server */
3827 	      text.data = (unsigned char *)
3828 		imap_parse_string (stream,&t,reply,
3829 				   ((md.what[0] && (md.what[0] != 'H')) ||
3830 				    md.first || md.last) ? &md : NIL,
3831 				   &text.size,NIL);
3832 				/* all done if partial */
3833 	      if (md.first || md.last) mail_free_stringlist (&stl);
3834 				/* otherwise register it in the cache */
3835 	      else imap_cache (stream,msgno,md.what,stl,&text);
3836 	    }
3837 	    fs_give ((void **) &md.what);
3838 	  }
3839 	  else {
3840 	    sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop);
3841 	    mm_notify (stream,LOCAL->tmp,WARN);
3842 	    stream->unhealthy = T;
3843 	  }
3844 	}
3845 
3846 				/* one of the RFC822 props? */
3847 	else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){
3848 	  SIZEDTEXT text;
3849 	  if (!prop[6]) {	/* cache full message */
3850 	    md.what = "";
3851 	    text.data = (unsigned char *)
3852 	      imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
3853 	    imap_cache (stream,msgno,md.what,NIL,&text);
3854 	  }
3855 	  else if (!strcmp (prop+7,"SIZE"))
3856 	    elt->rfc822_size = strtoul (t,(char **) &t,10);
3857 				/* legacy properties */
3858 	  else if (!strcmp (prop+7,"HEADER")) {
3859 	    text.data = (unsigned char *)
3860 	      imap_parse_string (stream,&t,reply,NIL,&text.size,NIL);
3861 	    imap_cache (stream,msgno,"HEADER",NIL,&text);
3862 	    e = stream->scache ? &stream->env : &elt->private.msg.env;
3863 	  }
3864 	  else if (!strcmp (prop+7,"TEXT")) {
3865 	    md.what = "TEXT";
3866 	    text.data = (unsigned char *)
3867 	      imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
3868 	    imap_cache (stream,msgno,md.what,NIL,&text);
3869 	  }
3870 	  else {
3871 	    sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop);
3872 	    mm_notify (stream,LOCAL->tmp,WARN);
3873 	    stream->unhealthy = T;
3874 	  }
3875 	}
3876 	else {
3877 	  sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
3878 	  mm_notify (stream,LOCAL->tmp,WARN);
3879 	  stream->unhealthy = T;
3880 	}
3881 	if (e && *e) env = *e;	/* note envelope if we got one */
3882       }
3883 				/* do callback if requested */
3884       if (ie && env) (*ie) (stream,msgno,env);
3885     }
3886 				/* obsolete response to COPY */
3887     else if (strcmp (s,"COPY")) {
3888       sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s);
3889       mm_notify (stream,LOCAL->tmp,WARN);
3890       stream->unhealthy = T;
3891     }
3892   }
3893 
3894   else if (!strcmp (reply->key,"FLAGS") && reply->text &&
3895 	   (*reply->text == '(') &&
3896 	   (s = strtok_r (reply->text+1," )",&r)))
3897     do if (*s != '\\') {
3898       for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] &&
3899 	     compare_cstring (s,stream->user_flags[i]); i++);
3900       if (i > NUSERFLAGS) {
3901 	sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s",
3902 		 (char *) s);
3903 	mm_notify (stream,LOCAL->tmp,WARN);
3904       }
3905       else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s);
3906     }
3907     while (s = strtok_r (NIL," )",&r));
3908   else if (!strcmp (reply->key,"SEARCH")) {
3909 				/* only do something if have text */
3910     if (reply->text && (t = strtok_r (reply->text," ",&r))) do
3911       if (i = strtoul (t,NIL,10)) {
3912 				/* UIDs always passed to main program */
3913 	if (LOCAL->uidsearch) mm_searched (stream,i);
3914 				/* should be a msgno then */
3915 	else if ((i <= stream->nmsgs) &&
3916 		 (!LOCAL->filter || mail_elt (stream,i)->private.filter)) {
3917 	  mail_elt (stream,i)->searched = T;
3918 	  if (!stream->silent) mm_searched (stream,i);
3919 	}
3920       } while (t = strtok_r (NIL," ",&r));
3921   }
3922   else if (!strcmp (reply->key,"SORT")) {
3923     sortresults_t sr = (sortresults_t)
3924       mail_parameters (NIL,GET_SORTRESULTS,NIL);
3925     LOCAL->sortsize = 0;	/* initialize sort data */
3926     if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
3927     LOCAL->sortdata = (unsigned long *)
3928       fs_get ((stream->nmsgs + 1) * sizeof (unsigned long));
3929 				/* only do something if have text */
3930     if (reply->text && (t = strtok_r (reply->text," ",&r))) {
3931       do if ((i = atol (t)) && (LOCAL->filter ?
3932 				mail_elt (stream,i)->searched : T))
3933 	LOCAL->sortdata[LOCAL->sortsize++] = i;
3934       while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs));
3935     }
3936     LOCAL->sortdata[LOCAL->sortsize] = 0;
3937 				/* also return via callback if requested */
3938     if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize);
3939   }
3940   else if (!strcmp (reply->key,"THREAD")) {
3941     threadresults_t tr = (threadresults_t)
3942       mail_parameters (NIL,GET_THREADRESULTS,NIL);
3943     if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
3944     if (s = reply->text) {
3945       LOCAL->threaddata = imap_parse_thread (stream,&s);
3946       if (tr) (*tr) (stream,LOCAL->threaddata);
3947       if (s && *s) {
3948 	sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s);
3949 	mm_notify (stream,LOCAL->tmp,WARN);
3950 	stream->unhealthy = T;
3951       }
3952     }
3953   }
3954 
3955   else if (!strcmp (reply->key,"STATUS") && reply->text) {
3956     MAILSTATUS status;
3957     unsigned char *txt = reply->text;
3958     if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt &&
3959 	(*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) &&
3960 	(s - txt) && !s[1]) {
3961       *s = '\0';		/* tie off status data */
3962 				/* initialize data block */
3963       status.flags = status.messages = status.recent = status.unseen =
3964 	status.uidnext = status.uidvalidity = 0;
3965       while (*txt && (s = strchr (txt,' '))) {
3966 	*s++ = '\0';		/* tie off status attribute name */
3967 				/* get attribute value */
3968 	i = strtoul (s,(char **) &s,10);
3969 	if (!compare_cstring (txt,"MESSAGES")) {
3970 	  status.flags |= SA_MESSAGES;
3971 	  status.messages = i;
3972 	}
3973 	else if (!compare_cstring (txt,"RECENT")) {
3974 	  status.flags |= SA_RECENT;
3975 	  status.recent = i;
3976 	}
3977 	else if (!compare_cstring (txt,"UNSEEN")) {
3978 	  status.flags |= SA_UNSEEN;
3979 	  status.unseen = i;
3980 	}
3981 	else if (!compare_cstring (txt,"UIDNEXT")) {
3982 	  status.flags |= SA_UIDNEXT;
3983 	  status.uidnext = i;
3984 	}
3985 	else if (!compare_cstring (txt,"UIDVALIDITY")) {
3986 	  status.flags |= SA_UIDVALIDITY;
3987 	  status.uidvalidity = i;
3988 	}
3989 				/* next attribute */
3990 	txt = (*s == ' ') ? s + 1 : s;
3991       }
3992       if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) <
3993 	  IMAPTMPLEN) {
3994 	strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t);
3995 				/* pass status to main program */
3996 	mm_status (stream,LOCAL->tmp,&status);
3997       }
3998     }
3999     if (t) fs_give ((void **) &t);
4000   }
4001 
4002   else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
4003 	   reply->text && (*reply->text == '(') &&
4004 	   (s = strchr (reply->text,')')) && (s[1] == ' ')) {
4005     char delimiter = '\0';
4006     *s++ = '\0';		/* tie off attribute list */
4007 				/* parse attribute list */
4008     if (t = strtok_r (reply->text+1," ",&r)) do {
4009       if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS;
4010       else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT;
4011       else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED;
4012       else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED;
4013       else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN;
4014       else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN;
4015 				/* ignore extension flags */
4016     }
4017     while (t = strtok_r (NIL," ",&r));
4018     switch (*++s) {		/* process delimiter */
4019     case 'N':			/* NIL */
4020     case 'n':
4021       s += 4;			/* skip over NIL<space> */
4022       break;
4023     case '"':			/* have a delimiter */
4024       delimiter = (*++s == '\\') ? *++s : *s;
4025       s += 3;			/* skip over <delimiter><quote><space> */
4026     }
4027 				/* parse the mailbox name */
4028     if (t = imap_parse_astring (stream,&s,reply,&j)) {
4029 				/* prepend prefix if requested */
4030       if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN))
4031 	sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t);
4032       else s = t;		/* otherwise just mailbox name */
4033 				/* pass data to main program */
4034       if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i);
4035       else mm_list (stream,delimiter,s,i);
4036       fs_give ((void **) &t);	/* flush mailbox name */
4037     }
4038   }
4039   else if (!strcmp (reply->key,"NAMESPACE")) {
4040     if (LOCAL->namespace) {
4041       mail_free_namespace (&LOCAL->namespace[0]);
4042       mail_free_namespace (&LOCAL->namespace[1]);
4043       mail_free_namespace (&LOCAL->namespace[2]);
4044     }
4045     else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *));
4046     if (s = reply->text) {	/* parse namespace results */
4047       LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply);
4048       LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply);
4049       LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply);
4050       if (s && *s) {
4051 	sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s);
4052 	mm_notify (stream,LOCAL->tmp,WARN);
4053 	stream->unhealthy = T;
4054       }
4055     }
4056     else {
4057       mm_notify (stream,"Missing namespace list",WARN);
4058       stream->unhealthy = T;
4059     }
4060   }
4061 
4062   else if (!strcmp (reply->key,"ACL") && (s = reply->text) &&
4063 	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
4064     getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL);
4065     if (s && (*s++ == ' ')) {
4066       ACLLIST *al = mail_newacllist ();
4067       ACLLIST *ac = al;
4068       do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) &&
4069 	     s && (*s++ == ' '))
4070 	ac->rights = imap_parse_astring (stream,&s,reply,NIL);
4071       while (ac->rights && s && (*s == ' ') && s++ &&
4072 	     (ac = ac->next = mail_newacllist ()));
4073       if (!ac->rights || (s && *s)) {
4074 	sprintf (LOCAL->tmp,"Invalid ACL identifer/rights for %.80s",
4075 		 (char *) t);
4076 	mm_notify (stream,LOCAL->tmp,WARN);
4077 	stream->unhealthy = T;
4078       }
4079       else if (ar) (*ar) (stream,t,al);
4080       mail_free_acllist (&al);	/* clean up */
4081     }
4082 				/* no optional rights */
4083     else if (ar) (*ar) (stream,t,NIL);
4084     fs_give ((void **) &t);	/* free mailbox name */
4085   }
4086 
4087   else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) &&
4088 	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
4089     listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL);
4090     char *id,*r;
4091     if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){
4092       if (s && (*s++ == ' ') &&
4093 	  (r = imap_parse_astring (stream,&s,reply,NIL))) {
4094 	if (s && (*s++ == ' ')) {
4095 	  STRINGLIST *rl = mail_newstringlist ();
4096 	  STRINGLIST *rc = rl;
4097 	  do rc->text.data = (unsigned char *)
4098 	    imap_parse_astring (stream,&s,reply,&rc->text.size);
4099 	  while (rc->text.data && s && (*s == ' ') && s++ &&
4100 		 (rc = rc->next = mail_newstringlist ()));
4101 	  if (!rc->text.data || (s && *s)) {
4102 	    sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s",
4103 		     (char *) t);
4104 	    mm_notify (stream,LOCAL->tmp,WARN);
4105 	    stream->unhealthy = T;
4106 	  }
4107 	  else if (lr) (*lr) (stream,t,id,r,rl);
4108 				/* clean up */
4109 	  mail_free_stringlist (&rl);
4110 	}
4111 				/* no optional rights */
4112 	else if (lr) (*lr) (stream,t,id,r,NIL);
4113 	fs_give ((void **) &r);	/* free rights */
4114       }
4115       else {
4116 	sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t);
4117 	mm_notify (stream,LOCAL->tmp,WARN);
4118 	stream->unhealthy = T;
4119       }
4120       fs_give ((void **) &id);	/* free identifier */
4121     }
4122     else {
4123       sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifer for %.80s",(char *) t);
4124       mm_notify (stream,LOCAL->tmp,WARN);
4125       stream->unhealthy = T;
4126     }
4127     fs_give ((void **) &t);	/* free mailbox name */
4128   }
4129 
4130   else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) &&
4131 	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
4132     myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL);
4133     char *r;
4134     if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) {
4135       if (s && *s) {
4136 	sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t);
4137 	mm_notify (stream,LOCAL->tmp,WARN);
4138 	stream->unhealthy = T;
4139       }
4140       else if (mr) (*mr) (stream,t,r);
4141       fs_give ((void **) &r);	/* free rights */
4142     }
4143     else {
4144       sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t);
4145       mm_notify (stream,LOCAL->tmp,WARN);
4146       stream->unhealthy = T;
4147     }
4148     fs_give ((void **) &t);	/* free mailbox name */
4149   }
4150 
4151 				/* this response has a bizarre syntax! */
4152   else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) &&
4153 	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
4154 				/* in case error */
4155     sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t);
4156     if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) {
4157       quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL);
4158       QUOTALIST *ql = NIL;
4159       QUOTALIST *qc;
4160 				/* parse non-empty quota resource list */
4161       if (*s != ')') for (ql = qc = mail_newquotalist (); T;
4162 			  qc = qc->next = mail_newquotalist ()) {
4163 	if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s &&
4164 	    (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) {
4165 	  if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10);
4166 	  else if (t = strchr (s,' ')) t = s;
4167 	  if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
4168 	    if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
4169 	    else if (t = strpbrk (s," )")) t = s;
4170 				/* another resource follows? */
4171 	    if (*s == ' ') continue;
4172 				/* end of resource list? */
4173 	    if ((*s == ')') && !s[1]) {
4174 	      if (qt) (*qt) (stream,t,ql);
4175 	      break;		/* all done */
4176 	    }
4177 	  }
4178 	}
4179 				/* something bad happened */
4180 	mm_notify (stream,LOCAL->tmp,WARN);
4181 	stream->unhealthy = T;
4182 	break;			/* parse failed */
4183       }
4184 				/* all done with quota resource list now */
4185       if (ql) mail_free_quotalist (&ql);
4186     }
4187     else {
4188       mm_notify (stream,LOCAL->tmp,WARN);
4189       stream->unhealthy = T;
4190     }
4191     fs_give ((void **) &t);	/* free root name */
4192   }
4193   else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) &&
4194 	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
4195     sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t);
4196     if (s && (*s++ == ' ')) {
4197       quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL);
4198       STRINGLIST *rl = mail_newstringlist ();
4199       STRINGLIST *rc = rl;
4200       do rc->text.data = (unsigned char *)
4201 	imap_parse_astring (stream,&s,reply,&rc->text.size);
4202       while (rc->text.data && *s && (*s++ == ' ') &&
4203 	       (rc = rc->next = mail_newstringlist ()));
4204       if (!rc->text.data || (s && *s)) {
4205 	mm_notify (stream,LOCAL->tmp,WARN);
4206 	stream->unhealthy = T;
4207       }
4208       else if (qr) (*qr) (stream,t,rl);
4209 				/* clean up */
4210       mail_free_stringlist (&rl);
4211     }
4212     else {
4213       mm_notify (stream,LOCAL->tmp,WARN);
4214       stream->unhealthy = T;
4215     }
4216     fs_give ((void **) &t);
4217   }
4218 
4219   else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
4220     imap_parse_response (stream,reply->text,NIL,T);
4221   else if (!strcmp (reply->key,"NO"))
4222     imap_parse_response (stream,reply->text,WARN,T);
4223   else if (!strcmp (reply->key,"BAD"))
4224     imap_parse_response (stream,reply->text,ERROR,T);
4225   else if (!strcmp (reply->key,"BYE")) {
4226     LOCAL->byeseen = T;		/* note that a BYE seen */
4227     imap_parse_response (stream,reply->text,BYE,T);
4228   }
4229   else if (!strcmp (reply->key,"CAPABILITY") && reply->text)
4230     imap_parse_capabilities (stream,reply->text);
4231   else if (!strcmp (reply->key,"MAILBOX") && reply->text) {
4232     if (LOCAL->prefix &&
4233 	((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN))
4234       sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text);
4235     else t = reply->text;
4236     mm_list (stream,NIL,t,NIL);
4237   }
4238   else {
4239     sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4240 	     (char *) reply->key);
4241     mm_notify (stream,LOCAL->tmp,WARN);
4242     stream->unhealthy = T;
4243   }
4244 }
4245 
4246 /* Parse human-readable response text
4247  * Accepts: mail stream
4248  *	    text
4249  *	    error level for mm_notify()
4250  *	    non-NIL if want mm_notify() event even if no response code
4251  */
4252 
imap_parse_response(MAILSTREAM * stream,char * text,long errflg,long ntfy)4253 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy)
4254 {
4255   char *s,*t,*r;
4256   size_t i;
4257   unsigned long j;
4258   MESSAGECACHE *elt;
4259   copyuid_t cu;
4260   appenduid_t au;
4261   SEARCHSET *source = NIL;
4262   SEARCHSET *dest = NIL;
4263   if (text && (*text == '[') && (t = strchr (s = text + 1,']')) &&
4264       ((i = t - s) < IMAPTMPLEN)) {
4265     LOCAL->tmp[i] = '\0';	/* make mungable copy of text code */
4266     if (s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) *s++ = '\0';
4267     if (s) {			/* have argument? */
4268       ntfy = NIL;		/* suppress mm_notify if normal SELECT data */
4269       if (!compare_cstring (t,"UIDVALIDITY") &&
4270 	  ((j = strtoul (s,NIL,10)) != stream->uid_validity)) {
4271 	mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4272 	stream->uid_validity = j;
4273 				/* purge any UIDs in cache */
4274 	for (j = 1; j <= stream->nmsgs; j++)
4275 	  if (elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT))
4276 	    elt->private.uid = 0;
4277       }
4278       else if (!compare_cstring (t,"UIDNEXT"))
4279 	stream->uid_last = strtoul (s,NIL,10) - 1;
4280       else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') &&
4281 	       (t[i-1] == ')')) {
4282 	t[i-1] = '\0';		/* tie off flags */
4283 	stream->perm_seen = stream->perm_deleted = stream->perm_answered =
4284 	  stream->perm_draft = stream->kwd_create = NIL;
4285 	stream->perm_user_flags = NIL;
4286 	if (s = strtok_r (s+1," ",&r)) do {
4287 	  if (*s == '\\') {	/* system flags */
4288 	    if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T;
4289 	    else if (!compare_cstring (s,"\\Deleted"))
4290 	      stream->perm_deleted = T;
4291 	    else if (!compare_cstring (s,"\\Flagged"))
4292 	      stream->perm_flagged = T;
4293 	    else if (!compare_cstring (s,"\\Answered"))
4294 	      stream->perm_answered = T;
4295 	    else if (!compare_cstring (s,"\\Draft")) stream->perm_draft = T;
4296 	    else if (!strcmp (s,"\\*")) stream->kwd_create = T;
4297 	  }
4298 	  else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
4299 	}
4300 	while (s = strtok_r (NIL," ",&r));
4301       }
4302 
4303       else if (!compare_cstring (t,"CAPABILITY"))
4304 	imap_parse_capabilities (stream,s);
4305       else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) &&
4306 	       !compare_cstring (t,"COPYUID") &&
4307 	       (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) &&
4308 	       isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4309 	       (source = mail_parse_set (s,&s)) && (*s++ == ' ') &&
4310 	       (dest = mail_parse_set (s,&s)) && !*s)
4311 	(*cu) (stream,LOCAL->appendmailbox,j,source,dest);
4312       else if (j && !compare_cstring (t,"APPENDUID") &&
4313 	       (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) &&
4314 	       isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4315 	       (dest = mail_parse_set (s,&s)) && !*s)
4316 	(*au) (LOCAL->appendmailbox,j,dest);
4317       else {			/* all other response code events */
4318 	ntfy = T;		/* must mm_notify() */
4319 	if (!compare_cstring (t,"REFERRAL"))
4320 	  LOCAL->referral = cpystr (t + 9);
4321       }
4322       mail_free_searchset (&source);
4323       mail_free_searchset (&dest);
4324     }
4325     else {			/* no arguments */
4326       if (!compare_cstring (t,"UIDNOTSTICKY")) {
4327 	ntfy = NIL;
4328 	stream->uid_nosticky = T;
4329       }
4330       else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T;
4331       else if (!compare_cstring (t,"READ-WRITE"))
4332 	stream->rdonly = NIL;
4333       else if (!compare_cstring (t,"PARSE") && !errflg)
4334 	errflg = PARSE;
4335     }
4336   }
4337 				/* give event to main program */
4338   if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg);
4339 }
4340 
4341 /* Parse a namespace
4342  * Accepts: mail stream
4343  *	    current text pointer
4344  *	    parsed reply
4345  * Returns: namespace list, text pointer updated
4346  */
4347 
imap_parse_namespace(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)4348 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
4349 				 IMAPPARSEDREPLY *reply)
4350 {
4351   NAMESPACE *ret = NIL;
4352   NAMESPACE *nam = NIL;
4353   NAMESPACE *prev = NIL;
4354   PARAMETER *par = NIL;
4355   if (*txtptr) {		/* only if argument given */
4356 				/* ignore leading space */
4357     while (**txtptr == ' ') ++*txtptr;
4358     switch (**txtptr) {
4359     case 'N':			/* if NIL */
4360     case 'n':
4361       ++*txtptr;		/* bump past "N" */
4362       ++*txtptr;		/* bump past "I" */
4363       ++*txtptr;		/* bump past "L" */
4364       break;
4365     case '(':
4366       ++*txtptr;		/* skip past open paren */
4367       while (**txtptr == '(') {
4368 	++*txtptr;		/* skip past open paren */
4369 	prev = nam;		/* note previous if any */
4370 	nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0,
4371 				  sizeof (NAMESPACE));
4372 	if (!ret) ret = nam;	/* if first time note first namespace */
4373 				/* if previous link new block to it */
4374 	if (prev) prev->next = nam;
4375 	nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL);
4376 				/* ignore whitespace */
4377 	while (**txtptr == ' ') ++*txtptr;
4378 	switch (**txtptr) {	/* parse delimiter */
4379 	case 'N':
4380 	case 'n':
4381 	  *txtptr += 3;		/* bump past "NIL" */
4382 	  break;
4383 	case '"':
4384 	  if (*++*txtptr == '\\') nam->delimiter = *++*txtptr;
4385 	  else nam->delimiter = **txtptr;
4386 	  *txtptr += 2;		/* bump past character and closing quote */
4387 	  break;
4388 	default:
4389 	  sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s",
4390 		   (char *) *txtptr);
4391 	  mm_notify (stream,LOCAL->tmp,WARN);
4392 	  stream->unhealthy = T;
4393 	  *txtptr = NIL;	/* stop parse */
4394 	  return ret;
4395 	}
4396 
4397 	while (**txtptr == ' '){/* append new parameter to tail */
4398 	  if (nam->param) par = par->next = mail_newbody_parameter ();
4399 	  else nam->param = par = mail_newbody_parameter ();
4400 	  if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL,
4401 						    NIL,NIL))) {
4402 	    mm_notify (stream,"Missing namespace extension attribute",WARN);
4403 	    stream->unhealthy = T;
4404 	    par->attribute = cpystr ("UNKNOWN");
4405 	  }
4406 				/* skip space */
4407 	  while (**txtptr == ' ') ++*txtptr;
4408 	  if (**txtptr == '(') {/* have value list?  */
4409 	    char *att = par->attribute;
4410 	    ++*txtptr;		/* yes */
4411 	    do {		/* parse each value */
4412 	      if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,
4413 						    NIL,LONGT))) {
4414 		sprintf (LOCAL->tmp,
4415 			 "Missing value for namespace attribute %.80s",att);
4416 		mm_notify (stream,LOCAL->tmp,WARN);
4417 		stream->unhealthy = T;
4418 		par->value = cpystr ("UNKNOWN");
4419 	      }
4420 				/* is there another value? */
4421 	      if (**txtptr == ' ') par = par->next = mail_newbody_parameter ();
4422 	    } while (!par->value);
4423 	  }
4424 	  else {
4425 	    sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s",
4426 		     par->attribute);
4427 	    mm_notify (stream,LOCAL->tmp,WARN);
4428 	    stream->unhealthy = T;
4429 	    par->value = cpystr ("UNKNOWN");
4430 	  }
4431 	}
4432 	if (**txtptr == ')') ++*txtptr;
4433 	else {			/* missing trailing paren */
4434 	  sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s",
4435 		   (char *) *txtptr);
4436 	  mm_notify (stream,LOCAL->tmp,WARN);
4437 	  stream->unhealthy = T;
4438 	  return ret;
4439 	}
4440       }
4441       if (**txtptr == ')') {	/* expected trailing paren? */
4442 	++*txtptr;		/* got it! */
4443 	break;
4444       }
4445     default:
4446       sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr);
4447       mm_notify (stream,LOCAL->tmp,WARN);
4448       stream->unhealthy = T;
4449       *txtptr = NIL;		/* stop parse now */
4450       break;
4451     }
4452   }
4453   return ret;
4454 }
4455 
4456 /* Parse a thread node list
4457  * Accepts: mail stream
4458  *	    current text pointer
4459  * Returns: thread node list, text pointer updated
4460  */
4461 
imap_parse_thread(MAILSTREAM * stream,unsigned char ** txtptr)4462 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr)
4463 {
4464   char *s;
4465   THREADNODE *ret = NIL;	/* returned tree */
4466   THREADNODE *last = NIL;	/* last branch in this tree */
4467   THREADNODE *parent = NIL;	/* parent of current node */
4468   THREADNODE *cur;		/* current node */
4469   while (**txtptr == '(') {	/* see a thread? */
4470     ++*txtptr;			/* skip past open paren */
4471     while (**txtptr != ')') {	/* parse thread */
4472       if (**txtptr == '(') {	/* thread branch */
4473 	cur = imap_parse_thread (stream,txtptr);
4474 				/* add to parent */
4475 	if (parent) parent = parent->next = cur;
4476 	else {			/* no parent, create dummy */
4477 	  if (last) last = last->branch = mail_newthreadnode (NIL);
4478 				/* new tree */
4479 	  else ret = last = mail_newthreadnode (NIL);
4480 				/* add to dummy parent */
4481 	  last->next = parent = cur;
4482 	}
4483       }
4484 				/* threaded message number */
4485       else if (isdigit (*(s = *txtptr)) &&
4486 	       ((cur = mail_newthreadnode (NIL))->num =
4487 		strtoul (*txtptr,(char **) txtptr,10))) {
4488 	if (LOCAL->filter && !mail_elt (stream,cur->num)->searched)
4489 	  cur->num = NIL;	/* make dummy if filtering and not searched */
4490 				/* add to parent */
4491 	if (parent) parent = parent->next = cur;
4492 				/* no parent, start new thread */
4493 	else if (last) last = last->branch = parent = cur;
4494 				/* create new tree */
4495 	else ret = last = parent = cur;
4496       }
4497       else {			/* anything else is a bogon */
4498 	char tmp[MAILTMPLEN];
4499 	sprintf (tmp,"Bogus thread member: %.80s",s);
4500 	mm_notify (stream,tmp,WARN);
4501 	stream->unhealthy = T;
4502 	return ret;
4503       }
4504 				/* skip past any space */
4505       if (**txtptr == ' ') ++*txtptr;
4506     }
4507     ++*txtptr;			/* skip pase end of thread */
4508     parent = NIL;		/* close this thread */
4509   }
4510   return ret;			/* return parsed thread */
4511 }
4512 
4513 /* Parse RFC822 message header
4514  * Accepts: MAIL stream
4515  *	    envelope to parse into
4516  *	    header as sized text
4517  *	    stringlist if partial header
4518  */
4519 
imap_parse_header(MAILSTREAM * stream,ENVELOPE ** env,SIZEDTEXT * hdr,STRINGLIST * stl)4520 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
4521 			STRINGLIST *stl)
4522 {
4523   ENVELOPE *nenv;
4524 				/* parse what we can from this header */
4525   rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL,
4526 		    net_host (LOCAL->netstream),stream->dtb->flags);
4527   if (*env) {			/* need to merge this header into envelope? */
4528     if (!(*env)->newsgroups) {	/* need Newsgroups? */
4529       (*env)->newsgroups = nenv->newsgroups;
4530       nenv->newsgroups = NIL;
4531     }
4532     if (!(*env)->followup_to) {	/* need Followup-To? */
4533       (*env)->followup_to = nenv->followup_to;
4534       nenv->followup_to = NIL;
4535     }
4536     if (!(*env)->references) {	/* need References? */
4537       (*env)->references = nenv->references;
4538       nenv->references = NIL;
4539     }
4540     if (!(*env)->sparep) {	/* need spare pointer? */
4541       (*env)->sparep = nenv->sparep;
4542       nenv->sparep = NIL;
4543     }
4544     mail_free_envelope (&nenv);
4545     (*env)->imapenvonly = NIL;	/* have complete envelope now */
4546   }
4547 				/* otherwise set it to this envelope */
4548   else (*env = nenv)->incomplete = stl ? T : NIL;
4549 }
4550 
4551 /* IMAP parse envelope
4552  * Accepts: MAIL stream
4553  *	    pointer to envelope pointer
4554  *	    current text pointer
4555  *	    parsed reply
4556  *
4557  * Updates text pointer
4558  */
4559 
imap_parse_envelope(MAILSTREAM * stream,ENVELOPE ** env,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)4560 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
4561 			  unsigned char **txtptr,IMAPPARSEDREPLY *reply)
4562 {
4563   ENVELOPE *oenv = *env;
4564   char c = *((*txtptr)++);	/* grab first character */
4565 				/* ignore leading spaces */
4566   while (c == ' ') c = *((*txtptr)++);
4567   switch (c) {			/* dispatch on first character */
4568   case '(':			/* if envelope S-expression */
4569     *env = mail_newenvelope ();	/* parse the new envelope */
4570     (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4571     (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4572     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
4573     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
4574     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
4575     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
4576     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
4577     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
4578     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL,
4579 					     LONGT);
4580     (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4581     if (oenv) {			/* need to merge old envelope? */
4582       (*env)->newsgroups = oenv->newsgroups;
4583       oenv->newsgroups = NIL;
4584       (*env)->followup_to = oenv->followup_to;
4585       oenv->followup_to = NIL;
4586       (*env)->references = oenv->references;
4587       oenv->references = NIL;
4588       mail_free_envelope(&oenv);/* free old envelope */
4589     }
4590 				/* have IMAP envelope components only */
4591     else (*env)->imapenvonly = T;
4592     if (**txtptr != ')') {
4593       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr);
4594       mm_notify (stream,LOCAL->tmp,WARN);
4595       stream->unhealthy = T;
4596     }
4597     else ++*txtptr;		/* skip past delimiter */
4598     break;
4599   case 'N':			/* if NIL */
4600   case 'n':
4601     ++*txtptr;			/* bump past "I" */
4602     ++*txtptr;			/* bump past "L" */
4603     break;
4604   default:
4605     sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr);
4606     mm_notify (stream,LOCAL->tmp,WARN);
4607     stream->unhealthy = T;
4608     break;
4609   }
4610 }
4611 
4612 /* IMAP parse address list
4613  * Accepts: MAIL stream
4614  *	    current text pointer
4615  *	    parsed reply
4616  * Returns: address list, NIL on failure
4617  *
4618  * Updates text pointer
4619  */
4620 
imap_parse_adrlist(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)4621 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
4622 			     IMAPPARSEDREPLY *reply)
4623 {
4624   ADDRESS *adr = NIL;
4625   char c = **txtptr;		/* sniff at first character */
4626 				/* ignore leading spaces */
4627   while (c == ' ') c = *++*txtptr;
4628   ++*txtptr;			/* skip past open paren */
4629   switch (c) {
4630   case '(':			/* if envelope S-expression */
4631     adr = imap_parse_address (stream,txtptr,reply);
4632     if (**txtptr != ')') {
4633       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",
4634 	       (char *) *txtptr);
4635       mm_notify (stream,LOCAL->tmp,WARN);
4636       stream->unhealthy = T;
4637     }
4638     else ++*txtptr;		/* skip past delimiter */
4639     break;
4640   case 'N':			/* if NIL */
4641   case 'n':
4642     ++*txtptr;			/* bump past "I" */
4643     ++*txtptr;			/* bump past "L" */
4644     break;
4645   default:
4646     sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
4647     mm_notify (stream,LOCAL->tmp,WARN);
4648     stream->unhealthy = T;
4649     break;
4650   }
4651   return adr;
4652 }
4653 
4654 /* IMAP parse address
4655  * Accepts: MAIL stream
4656  *	    current text pointer
4657  *	    parsed reply
4658  * Returns: address, NIL on failure
4659  *
4660  * Updates text pointer
4661  */
4662 
imap_parse_address(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)4663 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
4664 			     IMAPPARSEDREPLY *reply)
4665 {
4666   long ingroup = 0;
4667   ADDRESS *adr = NIL;
4668   ADDRESS *ret = NIL;
4669   ADDRESS *prev = NIL;
4670   char c = **txtptr;		/* sniff at first address character */
4671   switch (c) {
4672   case '(':			/* if envelope S-expression */
4673     while (c == '(') {		/* recursion dies on small stack machines */
4674       ++*txtptr;		/* skip past open paren */
4675       if (adr) prev = adr;	/* note previous if any */
4676       adr = mail_newaddr ();	/* instantiate address and parse its fields */
4677       adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4678       adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4679       adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4680       adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4681       if (**txtptr != ')') {	/* handle trailing paren */
4682 	sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr);
4683 	mm_notify (stream,LOCAL->tmp,WARN);
4684 	stream->unhealthy = T;
4685       }
4686       else ++*txtptr;		/* skip past close paren */
4687       c = **txtptr;		/* set up for while test */
4688 				/* ignore leading spaces in front of next */
4689       while (c == ' ') c = *++*txtptr;
4690 
4691       if (!adr->mailbox) {	/* end of group? */
4692 				/* decrement group if all looks well */
4693 	if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup;
4694 	else {
4695 	  if (ingroup) {	/* in a group? */
4696 	    sprintf (LOCAL->tmp,/* yes, must be bad syntax */
4697 		     "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
4698 		     adr->personal ? adr->personal : "",
4699 		     adr->adl ? adr->adl : "",
4700 		     adr->host ? adr->host : "");
4701 	    mm_notify (stream,LOCAL->tmp,WARN);
4702 	  }
4703 	  else mm_notify (stream,"End of group encountered when not in group",
4704 			  WARN);
4705 	  stream->unhealthy = T;
4706 	  mail_free_address (&adr);
4707 	  adr = prev;
4708 	  prev = NIL;
4709 	}
4710       }
4711       else if (!adr->host) {	/* start of group? */
4712 	if (adr->personal || adr->adl) {
4713 	  sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s",
4714 		   adr->personal ? adr->personal : "",
4715 		   adr->adl ? adr->adl : "");
4716 	  mm_notify (stream,LOCAL->tmp,WARN);
4717 	  stream->unhealthy = T;
4718 	  mail_free_address (&adr);
4719 	  adr = prev;
4720 	  prev = NIL;
4721 	}
4722 	else ++ingroup;		/* in a group now */
4723       }
4724       if (adr) {		/* good address */
4725 	if (!ret) ret = adr;	/* if first time note first adr */
4726 				/* if previous link new block to it */
4727 	if (prev) prev->next = adr;
4728 				/* flush bogus personal name */
4729 	if (LOCAL->loser && adr->personal && strchr (adr->personal,'@'))
4730 	  fs_give ((void **) &adr->personal);
4731       }
4732     }
4733     break;
4734   case 'N':			/* if NIL */
4735   case 'n':
4736     *txtptr += 3;		/* bump past NIL */
4737     break;
4738   default:
4739     sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
4740     mm_notify (stream,LOCAL->tmp,WARN);
4741     stream->unhealthy = T;
4742     break;
4743   }
4744   return ret;
4745 }
4746 
4747 /* IMAP parse flags
4748  * Accepts: current message cache
4749  *	    current text pointer
4750  *
4751  * Updates text pointer
4752  */
4753 
imap_parse_flags(MAILSTREAM * stream,MESSAGECACHE * elt,unsigned char ** txtptr)4754 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
4755 		       unsigned char **txtptr)
4756 {
4757   char *flag;
4758   char c = '\0';
4759   struct {			/* old flags */
4760     unsigned int valid : 1;
4761     unsigned int seen : 1;
4762     unsigned int deleted : 1;
4763     unsigned int flagged : 1;
4764     unsigned int answered : 1;
4765     unsigned int draft : 1;
4766     unsigned long user_flags;
4767   } old;
4768   old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted;
4769   old.flagged = elt->flagged; old.answered = elt->answered;
4770   old.draft = elt->draft; old.user_flags = elt->user_flags;
4771   elt->valid = T;		/* mark have valid flags now */
4772   elt->user_flags = NIL;	/* zap old flag values */
4773   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->draft =
4774     elt->recent = NIL;
4775   while (c != ')') {		/* parse list of flags */
4776 				/* point at a flag */
4777     while (*(flag = ++*txtptr) == ' ');
4778 				/* scan for end of flag */
4779     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
4780     c = **txtptr;		/* save delimiter */
4781     **txtptr = '\0';		/* tie off flag */
4782     if (!*flag) break;		/* null flag */
4783 				/* if starts with \ must be sys flag */
4784     else if (*flag == '\\') {
4785       if (!compare_cstring (flag,"\\Seen")) elt->seen = T;
4786       else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T;
4787       else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T;
4788       else if (!compare_cstring (flag,"\\Answered")) elt->answered = T;
4789       else if (!compare_cstring (flag,"\\Recent")) elt->recent = T;
4790       else if (!compare_cstring (flag,"\\Draft")) elt->draft = T;
4791     }
4792 				/* otherwise user flag */
4793     else elt->user_flags |= imap_parse_user_flag (stream,flag);
4794   }
4795   ++*txtptr;			/* bump past delimiter */
4796   if (!old.valid || (old.seen != elt->seen) ||
4797       (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
4798       (old.answered != elt->answered) || (old.draft != elt->draft) ||
4799       (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno);
4800 }
4801 
4802 
4803 /* IMAP parse user flag
4804  * Accepts: MAIL stream
4805  *	    flag name
4806  * Returns: flag bit position
4807  */
4808 
imap_parse_user_flag(MAILSTREAM * stream,char * flag)4809 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
4810 {
4811   long i;
4812 				/* sniff through all user flags */
4813   for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
4814     if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i);
4815   return (unsigned long) 0;	/* not found */
4816 }
4817 
4818 /* IMAP parse atom-string
4819  * Accepts: MAIL stream
4820  *	    current text pointer
4821  *	    parsed reply
4822  *	    returned string length
4823  * Returns: string
4824  *
4825  * Updates text pointer
4826  */
4827 
imap_parse_astring(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply,unsigned long * len)4828 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
4829 				   IMAPPARSEDREPLY *reply,unsigned long *len)
4830 {
4831   unsigned long i;
4832   unsigned char c,*s,*ret;
4833 				/* ignore leading spaces */
4834   for (c = **txtptr; c == ' '; c = *++*txtptr);
4835   switch (c) {
4836   case '"':			/* quoted string? */
4837   case '{':			/* literal? */
4838     ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL);
4839     break;
4840   default:			/* must be atom */
4841     for (c = *(s = *txtptr);	/* find end of atom */
4842 	 c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') &&
4843 	   (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80);
4844 	 c = *++*txtptr);
4845     if (i = *txtptr - s) {	/* atom ends at atom_special */
4846       if (len) *len = i;	/* return length of atom */
4847       ret = strncpy ((char *) fs_get (i + 1),s,i);
4848       ret[i] = '\0';		/* tie off string */
4849     }
4850     else {			/* no atom found */
4851       sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr);
4852       mm_notify (stream,LOCAL->tmp,WARN);
4853       stream->unhealthy = T;
4854       if (len) *len = 0;
4855       ret = NIL;
4856     }
4857     break;
4858   }
4859   return ret;
4860 }
4861 
4862 /* IMAP parse string
4863  * Accepts: MAIL stream
4864  *	    current text pointer
4865  *	    parsed reply
4866  *	    mailgets data
4867  *	    returned string length
4868  *	    filter newline flag
4869  * Returns: string
4870  *
4871  * Updates text pointer
4872  */
4873 
imap_parse_string(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply,GETS_DATA * md,unsigned long * len,long flags)4874 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
4875 				  IMAPPARSEDREPLY *reply,GETS_DATA *md,
4876 				  unsigned long *len,long flags)
4877 {
4878   char *st;
4879   char *string = NIL;
4880   unsigned long i,j,k;
4881   int bogon = NIL;
4882   unsigned char c = **txtptr;	/* sniff at first character */
4883   mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
4884   readprogress_t rp =
4885     (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
4886 				/* ignore leading spaces */
4887   while (c == ' ') c = *++*txtptr;
4888   st = ++*txtptr;		/* remember start of string */
4889   switch (c) {
4890   case '"':			/* if quoted string */
4891     i = 0;			/* initial byte count */
4892 				/* search for end of string */
4893     for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) {
4894 				/* backslash quotes next character */
4895       if (c == '\\') c = *++*txtptr;
4896 				/* CHAR8 not permitted in quoted string */
4897       if (!bogon && (bogon = (c & 0x80))) {
4898 	sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x",
4899 		 (unsigned int) c);
4900 	mm_notify (stream,LOCAL->tmp,WARN);
4901 	stream->unhealthy = T;
4902       }
4903       else if (!c) {		/* NUL not permitted either */
4904 	mm_notify (stream,"Unterminated quoted string",WARN);
4905 	stream->unhealthy = T;
4906 	if (len) *len = 0;	/* punt, since may be at end of string */
4907 	return NIL;
4908       }
4909     }
4910     ++*txtptr;			/* bump past delimiter */
4911     string = (char *) fs_get ((size_t) i + 1);
4912     for (j = 0; j < i; j++) {	/* copy the string */
4913       if (*st == '\\') ++st;	/* quoted character */
4914       string[j] = *st++;
4915     }
4916     string[j] = '\0';		/* tie off string */
4917     if (len) *len = i;		/* set return value too */
4918     if (md && mg) {		/* have special routine to slurp string? */
4919       STRING bs;
4920       if (md->first) {		/* partial fetch? */
4921 	md->first--;		/* restore origin octet */
4922 	md->last = i;		/* number of octets that we got */
4923       }
4924       INIT (&bs,mail_string,string,i);
4925       (*mg) (mail_read,&bs,i,md);
4926     }
4927     break;
4928 
4929   case 'N':			/* if NIL */
4930   case 'n':
4931     ++*txtptr;			/* bump past "I" */
4932     ++*txtptr;			/* bump past "L" */
4933     if (len) *len = 0;
4934     break;
4935   case '{':			/* if literal string */
4936 				/* get size of string */
4937     if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) {
4938       sprintf (LOCAL->tmp,"Absurd server literal length %lu",i);
4939       mm_notify (stream,LOCAL->tmp,WARN);
4940       stream->unhealthy = T;	/* read and discard */
4941       do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
4942 			LOCAL->tmp);
4943       while (i -= j);
4944     }
4945     if (len) *len = i;		/* set return value */
4946     if (md && mg) {		/* have special routine to slurp string? */
4947       if (md->first) {		/* partial fetch? */
4948 	md->first--;		/* restore origin octet */
4949 	md->last = i;		/* number of octets that we got */
4950       }
4951       else md->flags |= MG_COPY;/* otherwise flag need to copy */
4952       string = (*mg) (net_getbuffer,LOCAL->netstream,i,md);
4953     }
4954     else {			/* must slurp into free storage */
4955       string = (char *) fs_get ((size_t) i + 1);
4956       *string = '\0';		/* init in case getbuffer fails */
4957 				/* get the literal */
4958       if (rp) for (k = 0; j = min ((long) MAILTMPLEN,(long) i); i -= j) {
4959 	net_getbuffer (LOCAL->netstream,j,string + k);
4960 	(*rp) (md,k += j);
4961       }
4962       else net_getbuffer (LOCAL->netstream,i,string);
4963     }
4964     fs_give ((void **) &reply->line);
4965     if (flags && string)	/* need to filter newlines? */
4966       for (st = string; st = strpbrk (st,"\015\012\011"); *st++ = ' ');
4967 				/* get new reply text line */
4968     if (!(reply->line = net_getline (LOCAL->netstream)))
4969       reply->line = cpystr ("");
4970     if (stream->debug) mm_dlog (reply->line);
4971     *txtptr = reply->line;	/* set text pointer to point at it */
4972     break;
4973   default:
4974     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,(char *) *txtptr);
4975     mm_notify (stream,LOCAL->tmp,WARN);
4976     stream->unhealthy = T;
4977     if (len) *len = 0;
4978     break;
4979   }
4980   return (unsigned char *) string;
4981 }
4982 
4983 /* Register text in IMAP cache
4984  * Accepts: MAIL stream
4985  *	    message number
4986  *	    IMAP segment specifier
4987  *	    header string list (if a HEADER section specifier)
4988  *	    sized text to register
4989  * Returns: non-zero if cache non-empty
4990  */
4991 
imap_cache(MAILSTREAM * stream,unsigned long msgno,char * seg,STRINGLIST * stl,SIZEDTEXT * text)4992 long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg,
4993 		 STRINGLIST *stl,SIZEDTEXT *text)
4994 {
4995   char *t,tmp[MAILTMPLEN];
4996   unsigned long i;
4997   BODY *b;
4998   SIZEDTEXT *ret;
4999   STRINGLIST *stc;
5000   MESSAGECACHE *elt = mail_elt (stream,msgno);
5001 				/* top-level header never does mailgets */
5002   if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") ||
5003       !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) {
5004     ret = &elt->private.msg.header.text;
5005     if (text) {			/* don't do this if no text */
5006       if (ret->data) fs_give ((void **) &ret->data);
5007       mail_free_stringlist (&elt->private.msg.lines);
5008       elt->private.msg.lines = stl;
5009 				/* prevent cache reuse of .NOT */
5010       if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.'))
5011 	for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5012       if (stream->scache) {	/* short caching puts it in the stream */
5013 	if (stream->msgno != msgno) {
5014 				/* flush old stuff */
5015 	  mail_free_envelope (&stream->env);
5016 	  mail_free_body (&stream->body);
5017 	  stream->msgno = msgno;
5018 	}
5019 	imap_parse_header (stream,&stream->env,text,stl);
5020       }
5021 				/* regular caching */
5022       else imap_parse_header (stream,&elt->private.msg.env,text,stl);
5023     }
5024   }
5025 				/* top level text */
5026   else if (!strcmp (seg,"TEXT")) {
5027     ret = &elt->private.msg.text.text;
5028     if (text && ret->data) fs_give ((void **) &ret->data);
5029   }
5030   else if (!*seg) {		/* full message */
5031     ret = &elt->private.msg.full.text;
5032     if (text && ret->data) fs_give ((void **) &ret->data);
5033   }
5034 
5035   else {			/* nested, find non-contents specifier */
5036     for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++);
5037     if (*t) *t++ = '\0';	/* tie off section from data specifier */
5038     if (!(b = mail_body (stream,msgno,seg))) {
5039       sprintf (tmp,"Unknown section number: %.80s",seg);
5040       mm_notify (stream,tmp,WARN);
5041       stream->unhealthy = T;
5042       return NIL;
5043     }
5044     if (*t) {			/* if a non-numberic subpart */
5045       if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) &&
5046 	  (!strcmp (t,"HEADER") || !strcmp (t,"0") ||
5047 	   !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) {
5048 	ret = &b->nested.msg->header.text;
5049 	if (text) {
5050 	  if (ret->data) fs_give ((void **) &ret->data);
5051 	  mail_free_stringlist (&b->nested.msg->lines);
5052 	  b->nested.msg->lines = stl;
5053 				/* prevent cache reuse of .NOT */
5054 	  if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.'))
5055 	    for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5056 	  imap_parse_header (stream,&b->nested.msg->env,text,stl);
5057 	}
5058       }
5059       else if (i && !strcmp (t,"TEXT")) {
5060 	ret = &b->nested.msg->text.text;
5061 	if (text && ret->data) fs_give ((void **) &ret->data);
5062       }
5063 				/* otherwise it must be MIME */
5064       else if (!strcmp (t,"MIME")) {
5065 	ret = &b->mime.text;
5066 	if (text && ret->data) fs_give ((void **) &ret->data);
5067       }
5068       else {
5069 	sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t);
5070 	mm_notify (stream,tmp,WARN);
5071 	stream->unhealthy = T;
5072 	return NIL;
5073       }
5074     }
5075     else {			/* ordinary contents */
5076       ret = &b->contents.text;
5077       if (text && ret->data) fs_give ((void **) &ret->data);
5078     }
5079   }
5080   if (text) {			/* update cache if requested */
5081     ret->data = text->data;
5082     ret->size = text->size;
5083   }
5084   return ret->data ? LONGT : NIL;
5085 }
5086 
5087 /* IMAP parse body structure
5088  * Accepts: MAIL stream
5089  *	    body structure to write into
5090  *	    current text pointer
5091  *	    parsed reply
5092  *
5093  * Updates text pointer
5094  */
5095 
imap_parse_body_structure(MAILSTREAM * stream,BODY * body,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5096 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
5097 				unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5098 {
5099   int i;
5100   char *s;
5101   PART *part = NIL;
5102   char c = *((*txtptr)++);	/* grab first character */
5103 				/* ignore leading spaces */
5104   while (c == ' ') c = *((*txtptr)++);
5105   switch (c) {			/* dispatch on first character */
5106   case '(':			/* body structure list */
5107     if (**txtptr == '(') {	/* multipart body? */
5108       body->type= TYPEMULTIPART;/* yes, set its type */
5109       do {			/* instantiate new body part */
5110 	if (part) part = part->next = mail_newbody_part ();
5111 	else body->nested.part = part = mail_newbody_part ();
5112 				/* parse it */
5113 	imap_parse_body_structure (stream,&part->body,txtptr,reply);
5114       } while (**txtptr == '(');/* for each body part */
5115       if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT))
5116 	ucase (body->subtype);
5117       else {
5118 	mm_notify (stream,"Missing multipart subtype",WARN);
5119 	stream->unhealthy = T;
5120 	body->subtype = cpystr (rfc822_default_subtype (body->type));
5121       }
5122       if (**txtptr == ' ')	/* multipart parameters */
5123 	body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5124       if (**txtptr == ' ') {	/* disposition */
5125 	imap_parse_disposition (stream,body,txtptr,reply);
5126 	if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5127       }
5128       if (**txtptr == ' ') {	/* language */
5129 	body->language = imap_parse_language (stream,txtptr,reply);
5130 	if (LOCAL->cap.extlevel < BODYEXTLANG)
5131 	  LOCAL->cap.extlevel = BODYEXTLANG;
5132       }
5133       if (**txtptr == ' ') {	/* location */
5134 	body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5135 	if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5136       }
5137       while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5138       if (**txtptr != ')') {	/* validate ending */
5139 	sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",
5140 		 (char *) *txtptr);
5141 	mm_notify (stream,LOCAL->tmp,WARN);
5142 	stream->unhealthy = T;
5143       }
5144       else ++*txtptr;		/* skip past delimiter */
5145     }
5146 
5147     else {			/* not multipart, parse type name */
5148       if (**txtptr == ')') {	/* empty body? */
5149 	++*txtptr;		/* bump past it */
5150 	break;			/* and punt */
5151       }
5152       body->type = TYPEOTHER;	/* assume unknown type */
5153       body->encoding = ENCOTHER;/* and unknown encoding */
5154 				/* parse type */
5155       if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) {
5156 	ucase (s);		/* application always gets uppercase form */
5157 	for (i = 0;		/* look in existing table */
5158 	     (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++);
5159 	if (i <= TYPEMAX) {	/* only if found a slot */
5160 	  body->type = i;	/* set body type */
5161 	  if (body_types[i]) fs_give ((void **) &s);
5162 	  else body_types[i]=s;	/* assign empty slot */
5163 	}
5164       }
5165       if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT))
5166 	ucase (body->subtype);	/* parse subtype */
5167       else {
5168 	mm_notify (stream,"Missing body subtype",WARN);
5169 	stream->unhealthy = T;
5170 	body->subtype = cpystr (rfc822_default_subtype (body->type));
5171       }
5172       body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5173       body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5174       body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5175 					     LONGT);
5176       if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) {
5177 	ucase (s);		/* application always gets uppercase form */
5178 	for (i = 0;		/* search for body encoding */
5179 	     (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]);
5180 	     i++);
5181 	if (i > ENCMAX) body->encoding = ENCOTHER;
5182 	else {			/* only if found a slot */
5183 	  body->encoding = i;	/* set body encoding */
5184 	  if (body_encodings[i]) fs_give ((void **) &s);
5185 				/* assign empty slot */
5186 	  else body_encodings[i] = s;
5187 	}
5188       }
5189 				/* parse size of contents in bytes */
5190       body->size.bytes = strtoul (*txtptr,(char **) txtptr,10);
5191       switch (body->type) {	/* possible extra stuff */
5192       case TYPEMESSAGE:		/* message envelope and body */
5193 				/* non MESSAGE/RFC822 is basic type */
5194 	if (strcmp (body->subtype,"RFC822")) break;
5195 	{			/* make certain server sends an envelope */
5196 	  ENVELOPE *env = NIL;
5197 	  imap_parse_envelope (stream,&env,txtptr,reply);
5198 	  if (!env) {
5199 	    mm_notify (stream,"Missing body message envelope",WARN);
5200 	    stream->unhealthy = T;
5201 	    body->subtype = cpystr ("RFC822_MISSING_ENVELOPE");
5202 	    break;
5203 	  }
5204 	  (body->nested.msg = mail_newmsg ())->env = env;
5205 	}
5206 	body->nested.msg->body = mail_newbody ();
5207 	imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply);
5208 				/* drop into text case */
5209       case TYPETEXT:		/* size in lines */
5210 	body->size.lines = strtoul (*txtptr,(char **) txtptr,10);
5211 	break;
5212       default:			/* otherwise nothing special */
5213 	break;
5214       }
5215 
5216       if (**txtptr == ' ') {	/* extension data - md5 */
5217 	body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5218 	if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5;
5219       }
5220       if (**txtptr == ' ') {	/* disposition */
5221 	imap_parse_disposition (stream,body,txtptr,reply);
5222 	if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5223       }
5224       if (**txtptr == ' ') {	/* language */
5225 	body->language = imap_parse_language (stream,txtptr,reply);
5226 	if (LOCAL->cap.extlevel < BODYEXTLANG)
5227 	  LOCAL->cap.extlevel = BODYEXTLANG;
5228       }
5229       if (**txtptr == ' ') {	/* location */
5230 	body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5231 	if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5232       }
5233       while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5234       if (**txtptr != ')') {	/* validate ending */
5235 	sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",
5236 		 (char *) *txtptr);
5237 	mm_notify (stream,LOCAL->tmp,WARN);
5238 	stream->unhealthy = T;
5239       }
5240       else ++*txtptr;		/* skip past delimiter */
5241     }
5242     break;
5243   case 'N':			/* if NIL */
5244   case 'n':
5245     ++*txtptr;			/* bump past "I" */
5246     ++*txtptr;			/* bump past "L" */
5247     break;
5248   default:			/* otherwise quite bogus */
5249     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",(char *) *txtptr);
5250     mm_notify (stream,LOCAL->tmp,WARN);
5251     stream->unhealthy = T;
5252     break;
5253   }
5254 }
5255 
5256 /* IMAP parse body parameter
5257  * Accepts: MAIL stream
5258  *	    current text pointer
5259  *	    parsed reply
5260  * Returns: body parameter
5261  * Updates text pointer
5262  */
5263 
imap_parse_body_parameter(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5264 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
5265 				      unsigned char **txtptr,
5266 				      IMAPPARSEDREPLY *reply)
5267 {
5268   PARAMETER *ret = NIL;
5269   PARAMETER *par = NIL;
5270   char c,*s;
5271 				/* ignore leading spaces */
5272   while ((c = *(*txtptr)++) == ' ');
5273 				/* parse parameter list */
5274   if (c == '(') while (c != ')') {
5275 				/* append new parameter to tail */
5276     if (ret) par = par->next = mail_newbody_parameter ();
5277     else ret = par = mail_newbody_parameter ();
5278     if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL,
5279 					   LONGT))) {
5280       mm_notify (stream,"Missing parameter attribute",WARN);
5281       stream->unhealthy = T;
5282       par->attribute = cpystr ("UNKNOWN");
5283     }
5284     if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){
5285       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
5286       mm_notify (stream,LOCAL->tmp,WARN);
5287       stream->unhealthy = T;
5288       par->value = cpystr ("UNKNOWN");
5289     }
5290     switch (c = **txtptr) {	/* see what comes after */
5291     case ' ':			/* flush whitespace */
5292       while ((c = *++*txtptr) == ' ');
5293       break;
5294     case ')':			/* end of attribute/value pairs */
5295       ++*txtptr;		/* skip past closing paren */
5296       break;
5297     default:
5298       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
5299       mm_notify (stream,LOCAL->tmp,WARN);
5300       stream->unhealthy = T;
5301       break;
5302     }
5303   }
5304 				/* empty parameter, must be NIL */
5305   else if (((c == 'N') || (c == 'n')) &&
5306 	   ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
5307 	   ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
5308   else {
5309     sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,
5310 	     (char *) (*txtptr) - 1);
5311     mm_notify (stream,LOCAL->tmp,WARN);
5312     stream->unhealthy = T;
5313   }
5314   return ret;
5315 }
5316 
5317 /* IMAP parse body disposition
5318  * Accepts: MAIL stream
5319  *	    body structure to write into
5320  *	    current text pointer
5321  *	    parsed reply
5322  */
5323 
imap_parse_disposition(MAILSTREAM * stream,BODY * body,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5324 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
5325 			     unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5326 {
5327   switch (*++*txtptr) {
5328   case '(':
5329     ++*txtptr;			/* skip open paren */
5330     body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5331 						LONGT);
5332     body->disposition.parameter =
5333       imap_parse_body_parameter (stream,txtptr,reply);
5334     if (**txtptr != ')') {	/* validate ending */
5335       sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",
5336 	       (char *) *txtptr);
5337       mm_notify (stream,LOCAL->tmp,WARN);
5338       stream->unhealthy = T;
5339     }
5340     else ++*txtptr;		/* skip past delimiter */
5341     break;
5342   case 'N':			/* if NIL */
5343   case 'n':
5344     ++*txtptr;			/* bump past "N" */
5345     ++*txtptr;			/* bump past "I" */
5346     ++*txtptr;			/* bump past "L" */
5347     break;
5348   default:
5349     sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",(char *) *txtptr);
5350     mm_notify (stream,LOCAL->tmp,WARN);
5351     stream->unhealthy = T;
5352 				/* try to skip to next space */
5353     while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
5354     break;
5355   }
5356 }
5357 
5358 /* IMAP parse body language
5359  * Accepts: MAIL stream
5360  *	    current text pointer
5361  *	    parsed reply
5362  * Returns: string list or NIL if empty or error
5363  */
5364 
imap_parse_language(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5365 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
5366 				 IMAPPARSEDREPLY *reply)
5367 {
5368   unsigned long i;
5369   char *s;
5370   STRINGLIST *ret = NIL;
5371 				/* language is a list */
5372   if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
5373   else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i,LONGT)) {
5374     (ret = mail_newstringlist ())->text.data = (unsigned char *) s;
5375     ret->text.size = i;
5376   }
5377   return ret;
5378 }
5379 
5380 /* IMAP parse string list
5381  * Accepts: MAIL stream
5382  *	    current text pointer
5383  *	    parsed reply
5384  * Returns: string list or NIL if empty or error
5385  */
5386 
imap_parse_stringlist(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5387 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
5388 				   IMAPPARSEDREPLY *reply)
5389 {
5390   STRINGLIST *stl = NIL;
5391   STRINGLIST *stc = NIL;
5392   unsigned char *t = *txtptr;
5393 				/* parse the list */
5394   if (*t++ == '(') while (*t != ')') {
5395     if (stl) stc = stc->next = mail_newstringlist ();
5396     else stc = stl = mail_newstringlist ();
5397 				/* parse astring */
5398     if (!(stc->text.data =
5399 	  imap_parse_astring (stream,&t,reply,&stc->text.size))) {
5400       sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t);
5401       mm_notify (stream,LOCAL->tmp,WARN);
5402       stream->unhealthy = T;
5403       mail_free_stringlist (&stl);
5404       break;
5405     }
5406     else if (*t == ' ') ++t;	/* another token follows */
5407   }
5408   if (stl) *txtptr = ++t;	/* update return string */
5409   return stl;
5410 }
5411 
5412 /* IMAP parse unknown body extension data
5413  * Accepts: MAIL stream
5414  *	    current text pointer
5415  *	    parsed reply
5416  *
5417  * Updates text pointer
5418  */
5419 
imap_parse_extension(MAILSTREAM * stream,unsigned char ** txtptr,IMAPPARSEDREPLY * reply)5420 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
5421 			   IMAPPARSEDREPLY *reply)
5422 {
5423   unsigned long i,j;
5424   switch (*++*txtptr) {		/* action depends upon first character */
5425   case '(':
5426     while (**txtptr != ')') imap_parse_extension (stream,txtptr,reply);
5427     ++*txtptr;			/* bump past closing parenthesis */
5428     break;
5429   case '"':			/* if quoted string */
5430     while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr;
5431     ++*txtptr;			/* bump past closing quote */
5432     break;
5433   case 'N':			/* if NIL */
5434   case 'n':
5435     ++*txtptr;			/* bump past "N" */
5436     ++*txtptr;			/* bump past "I" */
5437     ++*txtptr;			/* bump past "L" */
5438     break;
5439   case '{':			/* get size of literal */
5440     ++*txtptr;			/* bump past open squiggle */
5441     if (i = strtoul (*txtptr,(char **) txtptr,10)) do
5442       net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
5443 		     LOCAL->tmp);
5444     while (i -= j);
5445 				/* get new reply text line */
5446     if (!(reply->line = net_getline (LOCAL->netstream)))
5447       reply->line = cpystr ("");
5448     if (stream->debug) mm_dlog (reply->line);
5449     *txtptr = reply->line;	/* set text pointer to point at it */
5450     break;
5451   case '0': case '1': case '2': case '3': case '4':
5452   case '5': case '6': case '7': case '8': case '9':
5453     strtoul (*txtptr,(char **) txtptr,10);
5454     break;
5455   default:
5456     sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr);
5457     mm_notify (stream,LOCAL->tmp,WARN);
5458     stream->unhealthy = T;
5459 				/* try to skip to next space */
5460     while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
5461     break;
5462   }
5463 }
5464 
5465 /* IMAP parse capabilities
5466  * Accepts: MAIL stream
5467  *	    capability list
5468  */
5469 
imap_parse_capabilities(MAILSTREAM * stream,char * t)5470 void imap_parse_capabilities (MAILSTREAM *stream,char *t)
5471 {
5472   char *s,*r;
5473   unsigned long i;
5474   THREADER *thr,*th;
5475   if (!LOCAL->gotcapability) {	/* need to save previous capabilities? */
5476 				/* no, flush threaders */
5477     if (thr = LOCAL->cap.threader) while (th = thr) {
5478       fs_give ((void **) &th->name);
5479       thr = th->next;
5480       fs_give ((void **) &th);
5481     }
5482 				/* zap capabilities */
5483     memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
5484     LOCAL->gotcapability = T;	/* flag that capabilities arrived */
5485   }
5486   for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) {
5487     if (!compare_cstring (t,"IMAP4"))
5488       LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5489     else if (!compare_cstring (t,"IMAP4rev1"))
5490       LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5491     else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T;
5492     else if (!compare_cstring (t,"IMAP2bis"))
5493       LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5494     else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T;
5495     else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T;
5496     else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T;
5497     else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T;
5498     else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T;
5499     else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T;
5500     else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T;
5501     else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T;
5502     else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T;
5503     else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T;
5504     else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T;
5505     else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T;
5506     else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T;
5507     else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T;
5508     else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T;
5509     else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T;
5510     else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T;
5511     else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T;
5512     else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T;
5513     else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T;
5514     else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T;
5515     else if (((t[0] == 'S') || (t[0] == 's')) &&
5516 	     ((t[1] == 'O') || (t[1] == 'o')) &&
5517 	     ((t[2] == 'R') || (t[2] == 'r')) &&
5518 	     ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T;
5519 				/* capability with value? */
5520     else if (s = strchr (t,'=')) {
5521       *s++ = '\0';		/* separate token from value */
5522       if (!compare_cstring (t,"THREAD") && !LOCAL->loser) {
5523 	THREADER *thread = (THREADER *) fs_get (sizeof (THREADER));
5524 	thread->name = cpystr (s);
5525 	thread->dispatch = NIL;
5526 	thread->next = LOCAL->cap.threader;
5527 	LOCAL->cap.threader = thread;
5528       }
5529       else if (!compare_cstring (t,"AUTH")) {
5530 	if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) &&
5531 	    (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i);
5532 	else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T;
5533       }
5534     }
5535 				/* ignore other capabilities */
5536   }
5537 				/* disable LOGIN if PLAIN also advertised */
5538   if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) &&
5539       (LOCAL->cap.auth & (1 << i)) &&
5540       (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS))
5541     LOCAL->cap.auth &= ~(1 << i);
5542 }
5543 
5544 /* IMAP load cache
5545  * Accepts: MAIL stream
5546  *	    sequence
5547  *	    flags
5548  * Returns: parsed reply from fetch
5549  */
5550 
imap_fetch(MAILSTREAM * stream,char * sequence,long flags)5551 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags)
5552 {
5553   int i = 2;
5554   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ?
5555     "UID FETCH" : "FETCH";
5556   IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl;
5557   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
5558 						     flags & FT_UID);
5559   args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence;
5560   args[1] = &aarg; aarg.type = ATOM;
5561   aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
5562   ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel];
5563   axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
5564   ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer;
5565   abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
5566   atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
5567   if (LEVELIMAP4 (stream)) {	/* include UID if IMAP4 or IMAP4rev1 */
5568     aarg.text = (void *) "(UID";
5569     if (flags & FT_NEEDENV) {	/* if need envelopes */
5570       args[i++] = &aenv;	/* include envelope */
5571 				/* extra header poop if IMAP4rev1 */
5572       if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
5573 	args[i++] = &ahhr;	/* header header */
5574 	if (axtr.text) args[i++] = &axtr;
5575 	args[i++] = &ahtr;	/* header trailer */
5576       }
5577 				/* fetch body if requested */
5578       if (flags & FT_NEEDBODY) args[i++] = &abdy;
5579     }
5580     args[i++] = &atrl;		/* fetch trailer */
5581   }
5582 				/* easy if IMAP2 */
5583   else aarg.text = (void *) (flags & FT_NEEDENV) ?
5584     ((flags & FT_NEEDBODY) ?
5585      "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
5586      "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
5587   args[i] = NIL;		/* tie off command */
5588   return imap_send (stream,cmd,args);
5589 }
5590 
5591 /* Reform sequence for losing server that doesn't handle ranges right
5592  * Accepts: MAIL stream
5593  *	    sequence
5594  *	    non-zero if UID
5595  * Returns: sequence
5596  */
5597 
imap_reform_sequence(MAILSTREAM * stream,char * sequence,long flags)5598 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags)
5599 {
5600   unsigned long i,j,star;
5601   char *s,*t,*tl,*rs;
5602 				/* can't win if empty */
5603   if (!stream->nmsgs) return sequence;
5604 				/* get highest possible range value */
5605   star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs;
5606 				/* flush old reformed sequence */
5607   if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
5608   rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence));
5609   for (s = sequence; t = strpbrk (s,",:"); ) switch (*t++) {
5610   case ',':			/* single message */
5611     strncpy (rs,s,i = t - s);	/* copy string up to that point */
5612     rs += i;			/* advance destination pointer */
5613     s += i;			/* and source */
5614     break;
5615   case ':':			/* message range */
5616     i = (*s == '*') ? star : strtoul (s,NIL,10);
5617     if (*t == '*') {		/* range ends with star */
5618       j = star;
5619       tl = t+1;
5620     }
5621     else {			/* numeric range end */
5622       j = strtoul (t,(char **) &tl,10);
5623       if (!tl) tl = t + strlen (t);
5624     }
5625     if (i <= j) {		/* if first less than second */
5626       if (*tl) tl++;		/* skip past end of range if present */
5627       strncpy (rs,s,i = tl - s);/* copy string up to that point */
5628       rs += i;			/* advance destination and source pointers */
5629       s += i;
5630     }
5631     else {			/* here's the workaround for losing servers */
5632       strncpy (rs,t,i = tl - t);/* swap the order */
5633       rs[i] = ':';		/* delimit */
5634       strncpy (rs+i+1,s,j = (t-1) - s);
5635       rs += i + 1 + j;		/* advance destination pointer */
5636       if (*tl) *rs++ = *tl++;	/* write trailing delimiter if present */
5637       s = tl;			/* advance source pointer */
5638     }
5639   }
5640   if (*s) strcpy (rs,s);	/* write remainder of sequence */
5641   else *rs = '\0';		/* tie off string */
5642   return LOCAL->reform;
5643 }
5644 
5645 /* IMAP return host name
5646  * Accepts: MAIL stream
5647  * Returns: host name
5648  */
5649 
imap_host(MAILSTREAM * stream)5650 char *imap_host (MAILSTREAM *stream)
5651 {
5652   if (stream->dtb != &imapdriver)
5653     fatal ("imap_host called on non-IMAP stream!");
5654 				/* return host name on stream if open */
5655   return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) :
5656     ".NO-IMAP-CONNECTION.";
5657 }
5658 
5659 
5660 /* IMAP return IMAP capability structure
5661  * Accepts: MAIL stream
5662  * Returns: IMAP capability structure
5663  */
5664 
imap_cap(MAILSTREAM * stream)5665 IMAPCAP *imap_cap (MAILSTREAM *stream)
5666 {
5667   if (stream->dtb != &imapdriver)
5668     fatal ("imap_cap called on non-IMAP stream!");
5669   return &LOCAL->cap;		/* return capability structure */
5670 }
5671