0cbda0bf17db1cef72adf36f011fbdb4f968cc92
[external/uw-imap-toolkit.git] / imap-2007e / c-client / imap4r1.c
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
337 /* IMAP validate mailbox
338  * Accepts: mailbox name
339  * Returns: our driver if name is valid, NIL otherwise
340  */
341
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
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 \f
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 \f
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 \f
458 /* IMAP scan mailboxes
459  * Accepts: mail stream
460  *          reference
461  *          pattern to search
462  *          string to scan
463  */
464
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
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
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 \f
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
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 \f
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 \f
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
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
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 \f
624 /* IMAP create mailbox
625  * Accepts: mail stream
626  *          mailbox name to create
627  * Returns: T on success, NIL on failure
628  */
629
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
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
655 long imap_rename (MAILSTREAM *stream,char *old,char *newname)
656 {
657   return imap_manage (stream,old,"Rename",newname);
658 }
659 \f
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
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 \f
709 /* IMAP status
710  * Accepts: mail stream
711  *          mailbox name
712  *          status flags
713  * Returns: T on success, NIL on failure
714  */
715
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 \f
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 \f
778 /* IMAP open
779  * Accepts: stream to open
780  * Returns: stream to use on success, NIL on failure
781  */
782
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 \f
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 \f
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 \f
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 \f
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 \f
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
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 \f
1056 /* IMAP log in as anonymous
1057  * Accepts: stream to authenticate
1058  *          scratch buffer
1059  * Returns: T on success, NIL on failure
1060  */
1061
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 \f
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
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 \f
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
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 \f
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
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
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 \f
1286 /* IMAP close
1287  * Accepts: MAIL stream
1288  *          option flags
1289  */
1290
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 \f
1331 /* IMAP fetch fast information
1332  * Accepts: MAIL stream
1333  *          sequence
1334  *          option flags
1335  *
1336  * Generally, imap_structure is preferred
1337  */
1338
1339 void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
1340 {
1341 /* Open source selected only FT_UID, and ignored other flags sent by application. This is corrected */
1342 #ifdef __HEADER_OPTIMIZATION__
1343   IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags);
1344 #else
1345   IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID);
1346 #endif
1347   if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
1348 }
1349
1350
1351 /* IMAP fetch flags
1352  * Accepts: MAIL stream
1353  *          sequence
1354  *          option flags
1355  */
1356
1357 void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
1358 {                               /* send "FETCH sequence FLAGS" */
1359   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1360   IMAPPARSEDREPLY *reply;
1361   IMAPARG *args[3],aseq,aatt;
1362   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1363                                                      flags & FT_UID);
1364   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1365   aatt.type = ATOM; aatt.text = (void *) "FLAGS";
1366   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1367   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1368     mm_log (reply->text,ERROR);
1369 }
1370 \f
1371 /* IMAP fetch overview
1372  * Accepts: MAIL stream, sequence bits set
1373  *          pointer to overview return function
1374  * Returns: T if successful, NIL otherwise
1375  */
1376
1377 long imap_overview (MAILSTREAM *stream,overview_t ofn)
1378 {
1379   MESSAGECACHE *elt;
1380   ENVELOPE *env;
1381   OVERVIEW ov;
1382   char *s,*t;
1383   unsigned long i,start,last,len,slen;
1384   if (!LOCAL->netstream) return NIL;
1385                                 /* build overview sequence */
1386   for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
1387     if ((elt = mail_elt (stream,i))->sequence) {
1388       if (!elt->private.msg.env) {
1389         if (s) {                /* continuing a sequence */
1390           if (i == last + 1) last = i;
1391           else {                /* end of range */
1392             if (last != start) sprintf (t,":%lu,%lu",last,i);
1393             else sprintf (t,",%lu",i);
1394             if ((len - (slen = (t += strlen (t)) - s)) < 20) {
1395               fs_resize ((void **) &s,len += MAILTMPLEN);
1396               t = s + slen;     /* relocate current pointer */
1397             }
1398             start = last = i;   /* begin a new range */
1399           }
1400         }
1401         else {                  /* first time, start new buffer */
1402           s = (char *) fs_get (len = MAILTMPLEN);
1403           sprintf (s,"%lu",start = last = i);
1404           t = s + strlen (s);   /* end of buffer */
1405         }
1406       }
1407     }
1408                                 /* last sequence */
1409   if (last != start) sprintf (t,":%lu",last);
1410   if (s) {                      /* prefetch as needed */
1411     imap_fetch (stream,s,FT_NEEDENV);
1412     fs_give ((void **) &s);
1413   }
1414   ov.optional.lines = 0;        /* now overview each message */
1415   ov.optional.xref = NIL;
1416   if (ofn) for (i = 1; i <= stream->nmsgs; i++)
1417 #ifdef __HEADER_OPTIMIZATION__  
1418 /* New last parameter 0 or 1 added to identify if the call is to fetch header or fetch body 
1419  * 0 mean fetch mail header; 1 means fetch mail full body or attachment */
1420  if (((elt = mail_elt (stream,i))->sequence) &&
1421         (env = mail_fetch_structure (stream,i,NIL,NIL,0)) && ofn) 
1422 #else
1423     if (((elt = mail_elt (stream,i))->sequence) &&
1424         (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn)
1425 #endif
1426  {
1427       ov.subject = env->subject;
1428       ov.from = env->from;
1429       ov.date = env->date;
1430       ov.message_id = env->message_id;
1431       ov.references = env->references;
1432       ov.optional.octets = elt->rfc822_size;
1433       (*ofn) (stream,mail_uid (stream,i),&ov,i);
1434     }
1435   return LONGT;
1436 }
1437 \f
1438 /* IMAP fetch structure
1439  * Accepts: MAIL stream
1440  *          message # to fetch
1441  *          pointer to return body
1442  *          option flags
1443  * Returns: envelope of this message, body returned in body value
1444  *
1445  * Fetches the "fast" information as well
1446  */
1447
1448 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
1449                           long flags)
1450 {
1451   unsigned long i,j,k,x;
1452   char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN];
1453   MESSAGECACHE *elt;
1454   ENVELOPE **env;
1455   BODY **b;
1456   IMAPPARSEDREPLY *reply = NIL;
1457   IMAPARG *args[3],aseq,aatt;
1458   SEARCHSET *set = LOCAL->lookahead;
1459   LOCAL->lookahead = NIL;
1460   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1461   aseq.type = SEQUENCE; aseq.text = (void *) seq;
1462   aatt.type = ATOM; aatt.text = NIL;
1463   if (flags & FT_UID)           /* see if can find msgno from UID */
1464     for (i = 1; i <= stream->nmsgs; i++)
1465       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1466         msgno = i;              /* found msgno, use it from now on */
1467         flags &= ~FT_UID;       /* no longer a UID fetch */
1468       }
1469   sprintf (s = seq,"%lu",msgno);/* initial sequence */
1470   if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
1471     /* UID fetching is requested and we can't map the UID to a message sequence
1472      * number.  Assume that the message isn't cached at all.
1473      */
1474     if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV +
1475                                              (body ? FT_NEEDBODY : NIL) +
1476                                              (flags & (FT_UID + FT_NOHDRS)))))
1477       mm_log (reply->text,ERROR);
1478                                 /* now hunt for this UID */
1479     for (i = 1; i <= stream->nmsgs; i++)
1480       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1481         if (body) *body = elt->private.msg.body;
1482         return elt->private.msg.env;
1483       }
1484     if (body) *body = NIL;      /* can't find the UID */
1485     return NIL;
1486   }
1487   elt = mail_elt (stream,msgno);/* get cache pointer */
1488   if (stream->scache) {         /* short caching? */
1489     env = &stream->env;         /* use temporaries on the stream */
1490     b = &stream->body;
1491     if (msgno != stream->msgno){/* flush old poop if a different message */
1492       mail_free_envelope (env);
1493       mail_free_body (b);
1494       stream->msgno = msgno;    /* this is now the current short cache msg */
1495     }
1496   }
1497 \f
1498   else {                        /* normal cache */
1499     env = &elt->private.msg.env;/* get envelope and body pointers */
1500     b = &elt->private.msg.body;
1501                                 /* prefetch if don't have envelope */
1502     if (!(flags & FT_NOLOOKAHEAD) &&
1503         ((!*env || (*env)->incomplete) ||
1504          (body && !*b && LEVELIMAP2bis (stream)))) {
1505       if (set) {                /* have a lookahead list? */
1506         MESSAGE *msg;
1507         for (k = imap_fetchlookaheadlimit;
1508              k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30));
1509              set = set->next) {
1510           i = (set->first == 0xffffffff) ? stream->nmsgs :
1511             min (set->first,stream->nmsgs);
1512           if (j = (set->last == 0xffffffff) ? stream->nmsgs :
1513               min (set->last,stream->nmsgs)) {
1514             if (i > j) {        /* swap the range if backwards */
1515               x = i; i = j; j = x;
1516             }
1517                                 /* find first message not msgno or in cache */
1518             while (((i == msgno) ||
1519                     ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1520                      (!body || msg->body))) && (i++ < j));
1521                                 /* until range or lookahead finished */
1522             while (k && (i <= j)) {
1523                                 /* find first cached message in range */
1524               for (x = i + 1; (x <= j) &&
1525                      !((msg = &(mail_elt (stream,x)->private.msg))->env &&
1526                        (!body || msg->body)); x++);
1527               if (i == --x) {   /* only one message? */
1528                 sprintf (s += strlen (s),",%lu",i++);
1529                 k--;            /* prefetching one message */
1530               }
1531               else {            /* a range to prefetch */
1532                 sprintf (s += strlen (s),",%lu:%lu",i,x);
1533                 i = 1 + x - i;  /* number of messages in this range */
1534                                 /* still can look ahead some more? */
1535                 if (k = (k > i) ? k - i : 0)
1536                                 /* yes, scan further in this range */
1537                   for (i = x + 2; (i <= j) &&
1538                          ((i == msgno) || 
1539                           ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1540                            (!body || msg->body)));
1541                        i++);
1542               }
1543             }
1544           }
1545           else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) {
1546             sprintf (s += strlen (s),",%lu",i);
1547             k--;                /* prefetching one message */
1548           }
1549       }
1550       }
1551                                 /* build message number list */
1552       else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++)
1553         if (!mail_elt (stream,i)->private.msg.env) {
1554           s += strlen (s);      /* find string end, see if nearing end */
1555           if ((s - seq) > (MAILTMPLEN - 20)) break;
1556           sprintf (s,",%lu",i); /* append message */
1557           for (j = i + 1, k--;  /* hunt for last message without an envelope */
1558                k && (j <= stream->nmsgs) &&
1559                !mail_elt (stream,j)->private.msg.env; j++, k--);
1560                                 /* if different, make a range */
1561           if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1562         }
1563     }
1564   }
1565 \f
1566   if (!stream->lock) {          /* no-op if stream locked */
1567     /* Build the fetch attributes.  Unlike imap_fetch(), this tries not to
1568      * fetch data that is already cached.  However, since it is based on the
1569      * message requested and not on any of the prefetched messages, it can
1570      * goof, either by fetching data already cached or not prefetching data
1571      * that isn't cached (but was cached in the message requested).
1572      * Fortunately, no great harm is done.  If it doesn't prefetch the data,
1573      * it will get it when the affected message(s) are requested.
1574      */
1575     if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID");
1576     else tmp[0] = '\0';         /* initialize command */
1577                                 /* need envelope? */
1578     if (!*env || (*env)->incomplete) {
1579       strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */
1580       if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
1581         if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
1582                                      hdrheader[LOCAL->cap.extlevel],
1583                                      imap_extrahdrs,hdrtrailer);
1584         else sprintf (tmp + strlen (tmp)," %s %s",
1585                       hdrheader[LOCAL->cap.extlevel],hdrtrailer);
1586       }
1587     }
1588                                 /* need body? */
1589     if (body && !*b && LEVELIMAP2bis (stream))
1590       strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY");
1591     if (!elt->day) strcat (tmp," INTERNALDATE");
1592     if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
1593     if (tmp[0]) {               /* anything to do? */
1594       tmp[0] = '(';             /* make into a list */
1595       strcat (tmp," FLAGS)");   /* always get current flags */
1596       aatt.text = (void *) tmp; /* do the built command */
1597       if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
1598                                 /* failed, probably RFC-1176 server */
1599         if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
1600           aatt.text = (void *) "ALL";
1601           if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1602                                 /* doesn't have body capabilities */
1603             LOCAL->cap.imap2bis = NIL;
1604           else mm_log (reply->text,ERROR);
1605         }
1606         else mm_log (reply->text,ERROR);
1607       }
1608     }
1609   }
1610   if (body) {                   /* wants to return body */
1611     if (!*b && !LEVELIMAP2bis (stream)) {
1612                                 /* simulate body structure fetch for IMAP2 */
1613       *b = mail_initbody (mail_newbody ());
1614       (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type));
1615       ((*b)->parameter = mail_newbody_parameter ())->attribute =
1616         cpystr ("CHARSET");
1617       (*b)->parameter->value = cpystr ("US-ASCII");
1618       s = mail_fetch_text (stream,msgno,NIL,&i,flags);
1619       (*b)->size.bytes = i;
1620       while (i--) if (*s++ == '\n') (*b)->size.lines++;
1621     }
1622     *body = *b;                 /* return the body */
1623   }
1624   return *env;                  /* return the envelope */
1625 }
1626 \f
1627 /* IMAP fetch message data
1628  * Accepts: MAIL stream
1629  *          message number
1630  *          section specifier
1631  *          offset of first designated byte or 0 to start at beginning
1632  *          maximum number of bytes or 0 for all bytes
1633  *          lines to fetch if header
1634  *          flags
1635  * Returns: T on success, NIL on failure
1636  */
1637
1638 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
1639                    unsigned long first,unsigned long last,STRINGLIST *lines,
1640                    long flags)
1641 {
1642   int i;
1643   char *t,tmp[MAILTMPLEN],partial[40],seq[40];
1644   char *noextend,*nopartial,*nolines,*nopeek,*nononpeek;
1645   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1646   IMAPPARSEDREPLY *reply;
1647   IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg;
1648   noextend = nopartial = nolines = nopeek = nononpeek = NIL;
1649                                 /* does searching desire a lookahead? */
1650   if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) &&
1651       !stream->scache) {
1652     sprintf (seq,"%lu:%lu",msgno,
1653              (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs));
1654     aseq.type = SEQUENCE;
1655     aseq.text = (void *) seq;
1656   }
1657   else {                        /* no, do it the easy way */
1658     aseq.type = NUMBER;
1659     aseq.text = (void *) msgno;
1660   }
1661   aatt.type = ATOM;             /* assume atomic attribute */
1662   alns.type = LIST; alns.text = (void *) lines;
1663   acls.type = BODYCLOSE; acls.text = (void *) partial;
1664   aflg.type = ATOM; aflg.text = (void *) "FLAGS";
1665   args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
1666   auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL;
1667   partial[0] = '\0';            /* initially no partial specifier */
1668   if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */
1669                                 /* HEADER fetching with special handling? */
1670     if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) {
1671       if (lines) {              /* want specific header lines? */
1672         aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1673         aatt.text = (void *) ((flags & FT_NOT) ?
1674                               "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1675         args[2] = &alns; args[3] = &acls;
1676       }
1677                                 /* must be prefetching */
1678       else aatt.text = (void *) ((flags & FT_PEEK) ?
1679                                  "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1680                                  "(BODY[HEADER] BODY[TEXT])");
1681     }
1682     else {                      /* simple case */
1683       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1684       aatt.text = (void *) section;
1685       args[2] = &acls;
1686     }
1687     if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1);
1688   }
1689 \f
1690   /* IMAP4 did not have:
1691    * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1692    * . TEXT body part (can simulate top-level with RFC822.TEXT or
1693    *                    RFC822.TEXT.PEEK)
1694    * . MIME body part
1695    * . (usable) partial fetching
1696    * . (usable) selective header line fetching
1697    */
1698   else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */
1699                                 /* BODY[HEADER] becomes BODY.PEEK[0] */
1700     if (!strcmp (section,"HEADER"))
1701       aatt.text = (void *)
1702         ((flags & FT_PREFETCHTEXT) ?
1703          ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1704           "(BODY[0] RFC822.TEXT)") :
1705          ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]"));
1706                                 /* BODY[TEXT] becomes RFC822.TEXT */
1707     else if (!strcmp (section,"TEXT"))
1708       aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" :
1709                             "RFC822.TEXT");
1710     else if (!section[0])       /* BODY[] becomes RFC822 */
1711       aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
1712                                 /* nested header */
1713     else if (t = strstr (section,".HEADER")) {
1714       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1715       args[2] = &acls;          /* will need to close section */
1716       aatt.text = (void *) tmp; /* convert .HEADER to .0 */
1717       strncpy (tmp,section,t-section);
1718       strcpy (tmp+(t-section),".0");
1719     }
1720     else {                      /* IMAP4 body part */
1721       aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1722       args[2] = &acls;          /* will need to close section */
1723       aatt.text = (void *) section;
1724     }
1725     if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4";
1726     if (first || last) nopartial = "4";
1727     if (lines) nolines = "4";
1728   }
1729 \f
1730   /* IMAP2bis did not have:
1731    * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1732    * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1733    * . MIME body part
1734    * . partial fetching
1735    * . selective header line fetching
1736    * . non-peeking header fetching
1737    * . peeking body fetching
1738    */
1739                                 /* IMAP2bis compatibility */
1740   else if (LEVELIMAP2bis (stream)) {
1741                                 /* BODY[HEADER] becomes RFC822.HEADER */
1742     if (!strcmp (section,"HEADER")) {
1743       aatt.text = (void *)
1744         ((flags & FT_PREFETCHTEXT) ?
1745          "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1746       if (flags & FT_PEEK) flags &= ~FT_PEEK;
1747       else nononpeek = "2bis";
1748     }
1749                                 /* BODY[TEXT] becomes RFC822.TEXT */
1750     else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1751                                 /* BODY[] becomes RFC822 */
1752     else if (!section[0]) aatt.text = (void *) "RFC822";
1753     else {                      /* IMAP2bis body part */
1754       aatt.type = BODYTEXT;
1755       args[2] = &acls;          /* will need to close section */
1756       aatt.text = (void *) section;
1757     }
1758     if (strstr (section,".HEADER") || strstr (section,".MIME") ||
1759              strstr (section,".TEXT")) noextend = "2bis";
1760     if (first || last) nopartial = "2bis";
1761     if (lines) nolines = "2bis";
1762     if (flags & FT_PEEK) nopeek = "2bis";
1763   }
1764 \f
1765   /* IMAP2 did not have:
1766    * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1767    * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1768    * . MIME body part
1769    * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1770    * . partial fetching
1771    * . selective header line fetching
1772    * . non-peeking header fetching
1773    * . peeking body fetching
1774    */
1775   else {                        /* IMAP2 (RFC 1176/1064) compatibility */
1776                                 /* BODY[HEADER] */
1777     if (!strcmp (section,"HEADER")) {
1778       aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
1779                             "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1780       if (flags & FT_PEEK) flags &= ~FT_PEEK;
1781       nononpeek = "2";
1782     }
1783                                 /* BODY[TEXT] becomes RFC822.TEXT */
1784     else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1785                                 /* BODY[1] treated like RFC822.TEXT */
1786     else if (!strcmp (section,"1")) {
1787       SIZEDTEXT text;
1788       MESSAGECACHE *elt = mail_elt (stream,msgno);
1789                                 /* have a cached RFC822.TEXT? */
1790       if (elt->private.msg.text.text.data) {
1791         text.size = elt->private.msg.text.text.size;
1792                                 /* should move instead of copy */
1793         text.data = memcpy (fs_get (text.size+1),
1794                             elt->private.msg.text.text.data,text.size);
1795         (t = (char *) text.data)[text.size] = '\0';
1796         imap_cache (stream,msgno,"1",NIL,&text);
1797         return LONGT;           /* don't have to do any fetches */
1798       }
1799                                 /* otherwise do RFC822.TEXT */
1800       aatt.text = (void *) "RFC822.TEXT";
1801     }
1802                                 /* BODY[] becomes RFC822 */
1803     else if (!section[0]) aatt.text = (void *) "RFC822";
1804     else noextend = "2";        /* how did we get here? */
1805     if (flags & FT_PEEK) nopeek = "2";
1806     if (first || last) nopartial = "2";
1807     if (lines) nolines = "2";
1808   }
1809 \f
1810   /* Report unavailable functionalities.  The application can use the helpful
1811    * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1812    * imap4r1.h to avoid triggering these errors.  There aren't any workarounds
1813    * for these restrictions.
1814    */
1815   if (noextend) {
1816     sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1817              noextend);
1818     mm_log (tmp,ERROR);
1819     return NIL;                 /* can't do anything close either */
1820   }
1821   if (nopartial) {
1822     sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1823              nopartial);
1824     mm_notify (stream,tmp,WARN);
1825   }
1826   if (nolines) {
1827     sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1828             nolines);
1829     mm_notify (stream,tmp,WARN);
1830   }
1831 \f
1832                                 /* trying to do unsupported peek behavior? */
1833   if ((t = nopeek) || (t = nononpeek)) {
1834                                 /* get most recent \Seen setting */
1835     if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs)))
1836       mm_log (reply->text,WARN);
1837                                 /* note current setting of \Seen flag */
1838     if (!(i = mail_elt (stream,msgno)->seen)) {
1839       sprintf (tmp,nopeek ?     /* only babble if \Seen not set */
1840                "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1841                "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t);
1842       mm_notify (stream,tmp,NIL);
1843     }
1844                                 /* send the fetch command */
1845     if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1846       mm_log (reply->text,ERROR);
1847       return NIL;               /* failure */
1848     }
1849                                 /* send command if need to reset \Seen */
1850     if (((nopeek && !i && mail_elt (stream,msgno)->seen &&
1851           (aflg.text = "-FLAGS \\Seen")) ||
1852          ((nononpeek && !mail_elt (stream,msgno)->seen) &&
1853           (aflg.text = "+FLAGS \\Seen"))) &&
1854         !imap_OK (stream,reply = imap_send (stream,"STORE",auxargs)))
1855       mm_log (reply->text,WARN);
1856   }
1857                                 /* simple case if traditional behavior */
1858   else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1859     mm_log (reply->text,ERROR);
1860     return NIL;                 /* failure */
1861   }
1862                                 /* simulate BODY[1] return for RFC 1064/1176 */
1863   if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) {
1864     SIZEDTEXT text;
1865     MESSAGECACHE *elt = mail_elt (stream,msgno);
1866     text.size = elt->private.msg.text.text.size;
1867                                 /* should move instead of copy */
1868     text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data,
1869                         text.size);
1870     (t = (char *) text.data)[text.size] = '\0';
1871     imap_cache (stream,msgno,"1",NIL,&text);
1872   }
1873   return LONGT;
1874 }
1875 \f
1876 /* IMAP fetch UID
1877  * Accepts: MAIL stream
1878  *          message number
1879  * Returns: UID
1880  */
1881
1882 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
1883 {
1884   MESSAGECACHE *elt;
1885   IMAPPARSEDREPLY *reply;
1886   IMAPARG *args[3],aseq,aatt;
1887   char *s,seq[MAILTMPLEN];
1888   unsigned long i,j,k;
1889                                 /* IMAP2 didn't have UIDs */
1890   if (!LEVELIMAP4 (stream)) return msgno;
1891                                 /* do we know its UID yet? */
1892   if (!(elt = mail_elt (stream,msgno))->private.uid) {
1893     aseq.type = SEQUENCE; aseq.text = (void *) seq;
1894     aatt.type = ATOM; aatt.text = (void *) "UID";
1895     args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1896     sprintf (seq,"%lu",msgno);
1897     if (k = imap_uidlookahead) {/* build UID list */
1898       for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
1899         if (!mail_elt (stream,i)->private.uid) {
1900           s += strlen (s);      /* find string end, see if nearing end */
1901           if ((s - seq) > (MAILTMPLEN - 20)) break;
1902           sprintf (s,",%lu",i); /* append message */
1903           for (j = i + 1, k--;  /* hunt for last message without a UID */
1904                k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
1905                j++, k--);
1906                                 /* if different, make a range */
1907           if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1908         }
1909     }
1910                                 /* send "FETCH msgno UID" */
1911     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1912       mm_log (reply->text,ERROR);
1913   }
1914   return elt->private.uid;      /* return our UID now */
1915 }
1916 \f
1917 /* IMAP fetch message number from UID
1918  * Accepts: MAIL stream
1919  *          UID
1920  * Returns: message number
1921  */
1922
1923 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
1924 {
1925   IMAPPARSEDREPLY *reply;
1926   IMAPARG *args[3],aseq,aatt;
1927   char seq[MAILTMPLEN];
1928   int holes = 0;
1929   unsigned long i,msgno;
1930                                 /* IMAP2 didn't have UIDs */
1931   if (!LEVELIMAP4 (stream)) return uid;
1932   /* This really should be a binary search, but since there are likely to be
1933    * holes in the msgno->UID map it's hard to do.
1934    */
1935   for (msgno = 1; msgno <= stream->nmsgs; msgno++) {
1936     if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T;
1937     else if (i == uid) return msgno;
1938   }
1939   if (holes) {                  /* have holes in cache? */
1940                                 /* yes, have server hunt for UID */
1941     LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0;
1942     aseq.type = SEQUENCE; aseq.text = (void *) seq;
1943     aatt.type = ATOM; aatt.text = (void *) "UID";
1944     args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1945     sprintf (seq,"%lu",uid);
1946                                 /* send "UID FETCH uid UID" */
1947     if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
1948       mm_log (reply->text,ERROR);
1949     if (LOCAL->lastuid.uid) {   /* got any results from FETCH? */
1950       if ((LOCAL->lastuid.uid == uid) &&
1951                                 /* what, me paranoid? */
1952           (LOCAL->lastuid.msgno <= stream->nmsgs) &&
1953           (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid))
1954                                 /* got it the easy way */
1955         return LOCAL->lastuid.msgno;
1956                                 /* sigh, do another linear search... */
1957       for (msgno = 1; msgno <= stream->nmsgs; msgno++)
1958         if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
1959     }
1960   }
1961   return 0;                     /* didn't find the UID anywhere */
1962 }
1963 \f
1964 /* IMAP modify flags
1965  * Accepts: MAIL stream
1966  *          sequence
1967  *          flag(s)
1968  *          option flags
1969  */
1970
1971 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
1972 {
1973   char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
1974   IMAPPARSEDREPLY *reply;
1975   IMAPARG *args[4],aseq,ascm,aflg;
1976   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1977                                                      flags & ST_UID);
1978   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1979   ascm.type = ATOM; ascm.text = (void *)
1980     ((flags & ST_SET) ?
1981      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
1982       "+Flags.silent" : "+Flags") :
1983      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
1984       "-Flags.silent" : "-Flags"));
1985   aflg.type = FLAGS; aflg.text = (void *) flag;
1986   args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
1987                                 /* send "STORE sequence +Flags flag" */
1988   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1989     mm_log (reply->text,ERROR);
1990 }
1991 \f
1992 /* IMAP search for messages
1993  * Accepts: MAIL stream
1994  *          character set
1995  *          search program
1996  *          option flags
1997  * Returns: T on success, NIL on failure
1998  */
1999
2000 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
2001 {
2002   unsigned long i,j,k;
2003   char *s;
2004   IMAPPARSEDREPLY *reply;
2005   MESSAGECACHE *elt;
2006   if ((flags & SE_NOSERVER) ||  /* if want to do local search */
2007       LOCAL->loser ||           /* or loser */
2008       (!LEVELIMAP4 (stream) &&  /* or old server but new functions... */
2009        (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or ||
2010         pgm->not || pgm->header || pgm->larger || pgm->smaller ||
2011         pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
2012         pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
2013         pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
2014         pgm->followup_to || pgm->references)) ||
2015       (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) {
2016     if ((flags & SE_NOLOCAL) ||
2017         !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2018       return NIL;
2019   }
2020                                 /* do silly ALL or seq-only search locally */
2021   else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) &&
2022            !(pgm->uid || pgm->or || pgm->not ||
2023              pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc ||
2024              pgm->subject || pgm->body || pgm->text ||
2025              pgm->larger || pgm->smaller ||
2026              pgm->sentbefore || pgm->senton || pgm->sentsince ||
2027              pgm->before || pgm->on || pgm->since ||
2028              pgm->answered || pgm->unanswered ||
2029              pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft ||
2030              pgm->flagged || pgm->unflagged || pgm->recent || pgm->old ||
2031              pgm->seen || pgm->unseen ||
2032              pgm->keyword || pgm->unkeyword ||
2033              pgm->return_path || pgm->sender ||
2034              pgm->reply_to || pgm->in_reply_to || pgm->message_id ||
2035              pgm->newsgroups || pgm->followup_to || pgm->references)) {
2036     if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER))
2037       fatal ("impossible mail_search_default() failure");
2038   }
2039 \f
2040   else {                        /* do server-based SEARCH */
2041     char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH";
2042     IMAPARG *args[4],apgm,aatt,achs;
2043     SEARCHSET *ss,*set;
2044     args[1] = args[2] = args[3] = NIL;
2045     apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
2046     if (charset) {              /* optional charset argument requested */
2047       args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
2048       aatt.type = ATOM; aatt.text = (void *) "CHARSET";
2049       achs.type = ASTRING; achs.text = (void *) charset;
2050     }
2051     else args[0] = &apgm;       /* no charset argument */
2052                                 /* tell receiver that these will be UIDs */
2053     LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
2054     reply = imap_send (stream,cmd,args);
2055                                 /* did server barf with that searchpgm? */
2056     if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) &&
2057         !strcmp (reply->key,"BAD")) {
2058       LOCAL->filter = T;        /* retry, filtering SEARCH results */
2059       for (i = 1; i <= stream->nmsgs; i++)
2060         mail_elt (stream,i)->private.filter = NIL;
2061       for (set = ss; set; set = set->next) if (i = set->first) {
2062                                 /* single message becomes one-message range */
2063         if (!(j = set->last)) j = i;
2064         else if (j < i) {       /* swap reversed range */
2065           i = set->last; j = set->first;
2066         }
2067         while (i <= j) mail_elt (stream,i++)->private.filter = T;
2068       }      
2069       pgm->msgno = NIL;         /* and without the searchset */
2070       reply = imap_send (stream,cmd,args);
2071       pgm->msgno = ss;          /* restore searchset */
2072       LOCAL->filter = NIL;      /* turn off filtering */
2073     }
2074     LOCAL->uidsearch = NIL;
2075                                 /* do locally if server won't grok */
2076     if (!strcmp (reply->key,"BAD")) {
2077       if ((flags & SE_NOLOCAL) ||
2078           !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2079         return NIL;
2080     }
2081     else if (!imap_OK (stream,reply)) {
2082       mm_log (reply->text,ERROR);
2083       return NIL;
2084     }
2085   }
2086 \f
2087                                 /* can never pre-fetch with a short cache */
2088   if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
2089       !stream->scache) {        /* only if prefetching permitted */
2090     s = LOCAL->tmp;             /* build sequence in temporary buffer */
2091     *s = '\0';                  /* initially nothing */
2092                                 /* search through mailbox */
2093     for (i = 1; k && (i <= stream->nmsgs); ++i) 
2094                                 /* for searched messages with no envelope */
2095       if ((elt = mail_elt (stream,i)) && elt->searched &&
2096           !mail_elt (stream,i)->private.msg.env) {
2097                                 /* prepend with comma if not first time */
2098         if (LOCAL->tmp[0]) *s++ = ',';
2099         sprintf (s,"%lu",j = i);/* output message number */
2100         s += strlen (s);        /* point at end of string */
2101         k--;                    /* count one up */
2102                                 /* search for possible end of range */
2103         while (k && (i < stream->nmsgs) &&
2104                (elt = mail_elt (stream,i+1))->searched &&
2105                !elt->private.msg.env) i++,k--;
2106         if (i != j) {           /* if a range */
2107           sprintf (s,":%lu",i); /* output delimiter and end of range */
2108           s += strlen (s);      /* point at end of string */
2109         }
2110         if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
2111       }
2112     if (LOCAL->tmp[0]) {        /* anything to pre-fetch? */
2113       /* pre-fetch envelopes for the first imap_prefetch number of messages */
2114       if (!imap_OK (stream,reply =
2115                     imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV +
2116                                 ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
2117                                 ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
2118         mm_log (reply->text,ERROR);
2119       fs_give ((void **) &s);   /* flush copy of sequence */
2120     }
2121   }
2122   return LONGT;
2123 }
2124 \f
2125 /* IMAP sort messages
2126  * Accepts: mail stream
2127  *          character set
2128  *          search program
2129  *          sort program
2130  *          option flags
2131  * Returns: vector of sorted message sequences or NIL if error
2132  */
2133
2134 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
2135                           SORTPGM *pgm,long flags)
2136 {
2137   unsigned long i,j,start,last;
2138   unsigned long *ret = NIL;
2139   pgm->nmsgs = 0;               /* start off with no messages */
2140                                 /* can use server-based sort? */
2141   if (LEVELSORT (stream) && !(flags & SE_NOSERVER) &&
2142       (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) {
2143     char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
2144     IMAPARG *args[4],apgm,achs,aspg;
2145     IMAPPARSEDREPLY *reply;
2146     SEARCHSET *ss = NIL;
2147     SEARCHPGM *tsp = NIL;
2148     apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
2149     achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
2150     aspg.type = SEARCHPROGRAM;
2151                                 /* did he provide a searchpgm? */
2152     if (!(aspg.text = (void *) spg)) {
2153       for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2154         if (mail_elt (stream,i)->searched) {
2155           if (ss) {             /* continuing a sequence */
2156             if (i == last + 1) last = i;
2157             else {              /* end of range */
2158               if (last != start) ss->last = last;
2159               (ss = ss->next = mail_newsearchset ())->first = i;
2160               start = last = i; /* begin a new range */
2161             }
2162           }
2163           else {                /* first time, start new searchpgm */
2164             (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2165             ss->first = start = last = i;
2166           }
2167         }
2168                                 /* nothing to sort if no messages */
2169       if (!(aspg.text = (void *) tsp)) return NIL;
2170                                 /* else install last sequence */
2171       if (last != start) ss->last = last;
2172     }
2173 \f
2174     args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2175                                 /* ask server to do it */
2176     reply = imap_send (stream,cmd,args);
2177     if (tsp) {                  /* was there a temporary searchpgm? */
2178       aspg.text = NIL;          /* yes, flush it */
2179       mail_free_searchpgm (&tsp);
2180                                 /* did server barf with that searchpgm? */
2181       if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2182         LOCAL->filter = T;      /* retry, filtering SORT/THREAD results */
2183         reply = imap_send (stream,cmd,args);
2184         LOCAL->filter = NIL;    /* turn off filtering */
2185       }
2186     }
2187                                 /* do locally if server barfs */
2188     if (!strcmp (reply->key,"BAD"))
2189       return (flags & SE_NOLOCAL) ? NIL :
2190         imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER);
2191                                 /* server sorted OK? */
2192     else if (imap_OK (stream,reply)) {
2193       pgm->nmsgs = LOCAL->sortsize;
2194       ret = LOCAL->sortdata;
2195       LOCAL->sortdata = NIL;    /* mail program is responsible for flushing */
2196     }
2197     else mm_log (reply->text,ERROR);
2198   }
2199 \f
2200                                 /* not much can do if short caching */
2201   else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
2202   else {                        /* try to be a bit more clever */
2203     char *s,*t;
2204     unsigned long len;
2205     MESSAGECACHE *elt;
2206     SORTCACHE **sc;
2207     SORTPGM *sp;
2208     long ftflags = 0;
2209                                 /* see if need envelopes */
2210     for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) {
2211     case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
2212       ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL);
2213     }
2214     if (spg) {                  /* only if a search needs to be done */
2215       int silent = stream->silent;
2216       stream->silent = T;       /* don't pass up mm_searched() events */
2217                                 /* search for messages */
2218       mail_search_full (stream,charset,spg,flags & SE_NOSERVER);
2219       stream->silent = silent;  /* restore silence state */
2220     }
2221                                 /* initialize progress counters */
2222     pgm->nmsgs = pgm->progress.cached = 0;
2223                                 /* pass 1: count messages to sort */
2224     for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
2225       if ((elt = mail_elt (stream,i))->searched) {
2226         pgm->nmsgs++;
2227         if (ftflags ? !elt->private.msg.env : !elt->day) {
2228           if (s) {              /* continuing a sequence */
2229             if (i == last + 1) last = i;
2230             else {              /* end of range */
2231               if (last != start) sprintf (t,":%lu,%lu",last,i);
2232               else sprintf (t,",%lu",i);
2233               start = last = i; /* begin a new range */
2234               if ((len - (j = ((t += strlen (t)) - s)) < 20)) {
2235                 fs_resize ((void **) &s,len += MAILTMPLEN);
2236                 t = s + j;      /* relocate current pointer */
2237               }
2238             }
2239           }
2240           else {                /* first time, start new buffer */
2241             s = (char *) fs_get (len = MAILTMPLEN);
2242             sprintf (s,"%lu",start = last = i);
2243             t = s + strlen (s); /* end of buffer */
2244           }
2245         }
2246       }
2247                                 /* last sequence */
2248     if (last != start) sprintf (t,":%lu",last);
2249     if (s) {                    /* load cache for all messages being sorted */
2250       imap_fetch (stream,s,ftflags);
2251       fs_give ((void **) &s);
2252     }
2253     if (pgm->nmsgs) {           /* pass 2: sort cache */
2254       sortresults_t sr = (sortresults_t)
2255         mail_parameters (NIL,GET_SORTRESULTS,NIL);
2256       sc = mail_sort_loadcache (stream,pgm);
2257                                 /* pass 3: sort messages */
2258       if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
2259       fs_give ((void **) &sc);  /* don't need sort vector any more */
2260                                 /* also return via callback if requested */
2261       if (sr) (*sr) (stream,ret,pgm->nmsgs);
2262     }
2263   }
2264   return ret;
2265 }
2266 \f
2267 /* IMAP thread messages
2268  * Accepts: mail stream
2269  *          thread type
2270  *          character set
2271  *          search program
2272  *          option flags
2273  * Returns: thread node tree or NIL if error
2274  */
2275
2276 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
2277                          SEARCHPGM *spg,long flags)
2278 {
2279   THREADER *thr;
2280   if (!(flags & SE_NOSERVER) &&
2281       (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger))))
2282                                 /* does server have this threader type? */
2283     for (thr = LOCAL->cap.threader; thr; thr = thr->next)
2284       if (!compare_cstring (thr->name,type)) 
2285         return imap_thread_work (stream,type,charset,spg,flags);
2286                                 /* server doesn't support it, do locally */
2287   return (flags & SE_NOLOCAL) ? NIL: 
2288     mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2289 }
2290 \f
2291 /* IMAP thread messages worker routine
2292  * Accepts: mail stream
2293  *          thread type
2294  *          character set
2295  *          search program
2296  *          option flags
2297  * Returns: thread node tree
2298  */
2299
2300 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
2301                               SEARCHPGM *spg,long flags)
2302 {
2303   unsigned long i,start,last;
2304   char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
2305   IMAPARG *args[4],apgm,achs,aspg;
2306   IMAPPARSEDREPLY *reply;
2307   THREADNODE *ret = NIL;
2308   SEARCHSET *ss = NIL;
2309   SEARCHPGM *tsp = NIL;
2310   apgm.type = ATOM; apgm.text = (void *) type;
2311   achs.type = ASTRING;
2312   achs.text = (void *) (charset ? charset : "US-ASCII");
2313   aspg.type = SEARCHPROGRAM;
2314                                 /* did he provide a searchpgm? */
2315   if (!(aspg.text = (void *) spg)) {
2316     for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2317       if (mail_elt (stream,i)->searched) {
2318         if (ss) {               /* continuing a sequence */
2319           if (i == last + 1) last = i;
2320           else {                /* end of range */
2321             if (last != start) ss->last = last;
2322             (ss = ss->next = mail_newsearchset ())->first = i;
2323             start = last =i;    /* begin a new range */
2324           }
2325         }
2326         else {                  /* first time, start new searchpgm */
2327           (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2328           ss->first = start = last = i;
2329         }
2330       }
2331                                 /* nothing to sort if no messages */
2332     if (!(aspg.text = (void *) tsp)) return NIL;
2333                                 /* else install last sequence */
2334     if (last != start) ss->last = last;
2335   }
2336 \f
2337   args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2338                                 /* ask server to do it */
2339   reply = imap_send (stream,cmd,args);
2340   if (tsp) {                    /* was there a temporary searchpgm? */
2341     aspg.text = NIL;            /* yes, flush it */
2342     mail_free_searchpgm (&tsp);
2343                                 /* did server barf with that searchpgm? */
2344     if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2345       LOCAL->filter = T;        /* retry, filtering SORT/THREAD results */
2346       reply = imap_send (stream,cmd,args);
2347       LOCAL->filter = NIL;      /* turn off filtering */
2348     }
2349   }
2350                                 /* do locally if server barfs */
2351   if (!strcmp (reply->key,"BAD"))
2352     ret = (flags & SE_NOLOCAL) ? NIL: 
2353     mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2354                                 /* server threaded OK? */
2355   else if (imap_OK (stream,reply)) {
2356     ret = LOCAL->threaddata;
2357     LOCAL->threaddata = NIL;    /* mail program is responsible for flushing */
2358   }
2359   else mm_log (reply->text,ERROR);
2360   return ret;
2361 }
2362 \f
2363 /* IMAP ping mailbox
2364  * Accepts: MAIL stream
2365  * Returns: T if stream still alive, else NIL
2366  */
2367
2368 long imap_ping (MAILSTREAM *stream)
2369 {
2370   return (LOCAL->netstream &&   /* send "NOOP" */
2371           imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
2372 }
2373
2374
2375 /* IMAP check mailbox
2376  * Accepts: MAIL stream
2377  */
2378
2379 void imap_check (MAILSTREAM *stream)
2380 {
2381                                 /* send "CHECK" */
2382   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
2383   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
2384 }
2385 \f
2386 /* IMAP expunge mailbox
2387  * Accepts: MAIL stream
2388  *          sequence to expunge if non-NIL
2389  *          expunge options
2390  * Returns: T if success, NIL if failure
2391  */
2392
2393 long imap_expunge (MAILSTREAM *stream,char *sequence,long options)
2394 {
2395   long ret = NIL;
2396   IMAPPARSEDREPLY *reply = NIL;
2397   if (sequence) {               /* wants selective expunging? */
2398     if (options & EX_UID) {     /* UID EXPUNGE form? */
2399       if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */
2400         IMAPARG *args[2],aseq;
2401         aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2402         args[0] = &aseq; args[1] = NIL;
2403         ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args));
2404       }
2405       else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR);
2406     }
2407                                 /* otherwise try to make into UID EXPUNGE */
2408     else if (mail_sequence (stream,sequence)) {
2409       unsigned long i,j;
2410       char *t = (char *) fs_get (IMAPTMPLEN);
2411       char *s = t;
2412                                 /* search through mailbox */
2413       for (*s = '\0', i = 1; i <= stream->nmsgs; ++i)
2414         if (mail_elt (stream,i)->sequence) {
2415           if (t[0]) *s++ = ','; /* prepend with comma if not first time */
2416           sprintf (s,"%lu",mail_uid (stream,j = i));
2417           s += strlen (s);      /* point at end of string */
2418                                 /* search for possible end of range */
2419           while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++;
2420           if (i != j) {         /* output end of range */
2421             sprintf (s,":%lu",mail_uid (stream,i));
2422             s += strlen (s);    /* point at end of string */
2423           }
2424           if ((s - t) > (IMAPTMPLEN - 50)) {
2425             mm_log ("Excessively complex sequence",ERROR);
2426                 fs_give ((void **) &s); //Prevent fix 10/05/2010 [santosh.br]
2427             return NIL;
2428           }
2429         }
2430                                 /* now do as UID EXPUNGE */
2431       ret = imap_expunge (stream,t,EX_UID);
2432       fs_give ((void **) &t);
2433     }
2434   }
2435                                 /* ordinary EXPUNGE */
2436   else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL));
2437   if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR);
2438   return ret;
2439 }
2440 \f
2441 /* IMAP copy message(s)
2442  * Accepts: MAIL stream
2443  *          sequence
2444  *          destination mailbox
2445  *          option flags
2446  * Returns: T if successful else NIL
2447  */
2448
2449 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
2450 {
2451   char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
2452   char *s;
2453   long ret = NIL;
2454   IMAPPARSEDREPLY *reply;
2455   IMAPARG *args[3],aseq,ambx;
2456   imapreferral_t ir =
2457     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2458   mailproxycopy_t pc =
2459     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
2460   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2461                                                      flags & CP_UID);
2462   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2463   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2464   args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
2465                                 /* note mailbox in case APPENDUID */
2466   LOCAL->appendmailbox = mailbox;
2467                                 /* send "COPY sequence mailbox" */
2468   ret = imap_OK (stream,reply = imap_send (stream,cmd,args));
2469   LOCAL->appendmailbox = NIL;   /* no longer appending */
2470   if (ret) {                    /* success, delete messages if move */
2471     if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
2472                                     ST_SET + ((flags&CP_UID) ? ST_UID : NIL));
2473   }
2474                                 /* failed, do referral action if any */
2475   else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
2476            (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
2477     ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL));
2478                                 /* otherwise issue error message */
2479   else mm_log (reply->text,ERROR);
2480   return ret;
2481 }
2482 \f
2483 /* IMAP mail append message from stringstruct
2484  * Accepts: MAIL stream
2485  *          destination mailbox
2486  *          append callback
2487  *          data for callback
2488  * Returns: T if append successful, else NIL
2489  */
2490
2491 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
2492 {
2493   MAILSTREAM *st = stream;
2494   IMAPARG *args[3],ambx,amap;
2495   IMAPPARSEDREPLY *reply = NIL;
2496   APPENDDATA map;
2497   char tmp[MAILTMPLEN];
2498   long debug = stream ? stream->debug : NIL;
2499   long ret = NIL;
2500   imapreferral_t ir =
2501     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2502                                 /* mailbox must be good */
2503   if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2504                                 /* create a stream if given one no good */
2505     if ((stream && LOCAL && LOCAL->netstream) ||
2506         (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2507                              (debug ? OP_DEBUG : NIL)))) {
2508                                 /* note mailbox in case APPENDUID */
2509       LOCAL->appendmailbox = mailbox;
2510                                 /* use multi-append? */
2511       if (LEVELMULTIAPPEND (stream)) {
2512         ambx.type = ASTRING; ambx.text = (void *) tmp;
2513         amap.type = MULTIAPPEND; amap.text = (void *) &map;
2514         map.af = af; map.data = data;
2515         args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2516                                 /* success if OK */
2517         ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args));
2518         LOCAL->appendmailbox = NIL;
2519       }
2520                                 /* do succession of single appends */
2521       else while ((*af) (stream,data,&map.flags,&map.date,&map.message) &&
2522                   map.message &&
2523                   (ret = imap_OK (stream,reply =
2524                                   imap_append_single (stream,tmp,map.flags,
2525                                                       map.date,map.message))));
2526       LOCAL->appendmailbox = NIL;
2527                                 /* don't do referrals if success or no reply */
2528       if (ret || !reply) mailbox = NIL;
2529                                 /* otherwise generate referral */
2530       else if (!(mailbox = (ir && LOCAL->referral) ?
2531                  (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2532         mm_log (reply->text,ERROR);
2533                                 /* close temporary stream */
2534       if (st != stream) stream = mail_close (stream);
2535       if (mailbox)              /* chase referral if any */
2536         ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date,
2537                                     map.message,&map,debug);
2538     }
2539     else mm_log ("Can't access server for append",ERROR);
2540   }
2541   return ret;                   /* return */
2542 }
2543 \f
2544 /* IMAP mail append message referral retry
2545  * Accepts: destination mailbox
2546  *          temporary buffer
2547  *          append callback
2548  *          data for callback
2549  *          flags from previous attempt
2550  *          date from previous attempt
2551  *          message stringstruct from previous attempt
2552  *          options (currently non-zero to set OP_DEBUG)
2553  * Returns: T if append successful, else NIL
2554  */
2555
2556 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
2557                            char *flags,char *date,STRING *message,
2558                            APPENDDATA *map,long options)
2559 {
2560   MAILSTREAM *stream;
2561   IMAPARG *args[3],ambx,amap;
2562   IMAPPARSEDREPLY *reply;
2563   imapreferral_t ir =
2564     (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL);
2565                                 /* barf if bad mailbox */
2566   while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2567                                 /* create a stream if given one no good */
2568     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2569                               (options ? OP_DEBUG : NIL)))) {
2570       sprintf (tmp,"Can't access referral server: %.80s",mailbox);
2571       mm_log (tmp,ERROR);
2572       return NIL;
2573     }
2574                                 /* got referral server, use multi-append? */
2575     if (LEVELMULTIAPPEND (stream)) {
2576       ambx.type = ASTRING; ambx.text = (void *) tmp;
2577       amap.type = MULTIAPPENDREDO; amap.text = (void *) map;
2578       args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2579                                 /* do multiappend on referral site */
2580       if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) {
2581         mail_close (stream);    /* multiappend OK, close stream */
2582         return LONGT;           /* all done */
2583       }
2584     }
2585                                 /* do multiple single appends */
2586     else while (imap_OK (stream,reply =
2587                          imap_append_single (stream,tmp,flags,date,message)))
2588       if (!((*af) (stream,data,&flags,&date,&message) && message)) {
2589         mail_close (stream);    /* last message, close stream */
2590         return LONGT;           /* all done */
2591       }
2592                                 /* generate error if no nested referral */
2593     if (!(mailbox = (ir && LOCAL->referral) ?
2594           (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2595       mm_log (reply->text,ERROR);
2596     mail_close (stream);        /* close previous referral stream */
2597   }
2598   return NIL;                   /* bogus mailbox */
2599 }
2600 \f
2601 /* IMAP append single message
2602  * Accepts: mail stream
2603  *          destination mailbox
2604  *          initial flags
2605  *          internal date
2606  *          stringstruct of message to append
2607  * Returns: reply from append
2608  */
2609
2610 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
2611                                      char *flags,char *date,STRING *message)
2612 {
2613   MESSAGECACHE elt;
2614   IMAPARG *args[5],ambx,aflg,adat,amsg;
2615   IMAPPARSEDREPLY *reply;
2616   char tmp[MAILTMPLEN];
2617   int i;
2618   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2619   args[i = 0] = &ambx;
2620   if (flags) {
2621     aflg.type = FLAGS; aflg.text = (void *) flags;
2622     args[++i] = &aflg;
2623   }
2624   if (date) {                   /* ensure date in INTERNALDATE format */
2625     if (!mail_parse_date (&elt,date)) {
2626                                 /* flush previous reply */
2627       if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
2628                                 /* build new fake reply */
2629       LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*");
2630       LOCAL->reply.key = "BAD";
2631       LOCAL->reply.text = "Bad date in append";
2632       return &LOCAL->reply;
2633     }
2634     adat.type = ASTRING;
2635     adat.text = (void *) (date = mail_date (tmp,&elt));
2636     args[++i] = &adat;
2637   }
2638   amsg.type = LITERAL; amsg.text = (void *) message;
2639   args[++i] = &amsg;
2640   args[++i] = NIL;
2641                                 /* easy if IMAP4[rev1] */
2642   if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args);
2643   else {                        /* try the IMAP2bis way */
2644     args[1] = &amsg; args[2] = NIL;
2645     reply = imap_send (stream,"APPEND",args);
2646   }
2647   return reply;
2648 }
2649 \f
2650 /* IMAP garbage collect stream
2651  * Accepts: Mail stream
2652  *          garbage collection flags
2653  */
2654
2655 void imap_gc (MAILSTREAM *stream,long gcflags)
2656 {
2657   unsigned long i;
2658   MESSAGECACHE *elt;
2659   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2660                                 /* make sure the cache is large enough */
2661   (*mc) (stream,stream->nmsgs,CH_SIZE);
2662   if (gcflags & GC_TEXTS) {     /* garbage collect texts? */
2663     if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
2664       if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT))
2665         imap_gc_body (elt->private.msg.body);
2666     imap_gc_body (stream->body);
2667   }
2668                                 /* gc cache if requested and unlocked */
2669   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
2670     if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
2671         (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
2672 }
2673 \f
2674 /* IMAP garbage collect body texts
2675  * Accepts: body to GC
2676  */
2677
2678 void imap_gc_body (BODY *body)
2679 {
2680   PART *part;
2681   if (body) {                   /* have a body? */
2682     if (body->mime.text.data)   /* flush MIME data */
2683       fs_give ((void **) &body->mime.text.data);
2684                                 /* flush text contents */
2685     if (body->contents.text.data)
2686       fs_give ((void **) &body->contents.text.data);
2687     body->mime.text.size = body->contents.text.size = 0;
2688                                 /* multipart? */
2689     if (body->type == TYPEMULTIPART)
2690       for (part = body->nested.part; part; part = part->next)
2691         imap_gc_body (&part->body);
2692                                 /* MESSAGE/RFC822? */
2693     else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
2694       imap_gc_body (body->nested.msg->body);
2695       if (body->nested.msg->full.text.data)
2696         fs_give ((void **) &body->nested.msg->full.text.data);
2697       if (body->nested.msg->header.text.data)
2698         fs_give ((void **) &body->nested.msg->header.text.data);
2699       if (body->nested.msg->text.text.data)
2700         fs_give ((void **) &body->nested.msg->text.text.data);
2701       body->nested.msg->full.text.size = body->nested.msg->header.text.size =
2702         body->nested.msg->text.text.size = 0;
2703     }
2704   }
2705 }
2706 \f
2707 /* IMAP get capabilities
2708  * Accepts: mail stream
2709  */
2710
2711 void imap_capability (MAILSTREAM *stream)
2712 {
2713   THREADER *thr,*t;
2714   LOCAL->gotcapability = NIL;   /* flush any previous capabilities */
2715                                 /* request new capabilities */
2716   imap_send (stream,"CAPABILITY",NIL);
2717   if (!LOCAL->gotcapability) {  /* did server get any? */
2718                                 /* no, flush threaders just in case */
2719     if (thr = LOCAL->cap.threader) while (t = thr) {
2720       fs_give ((void **) &t->name);
2721       thr = t->next;
2722       fs_give ((void **) &t);
2723     }
2724                                 /* zap most capabilities */
2725     memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
2726                                 /* assume IMAP2bis server if failure */
2727     LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
2728   }
2729 }
2730 \f
2731 /* IMAP set ACL
2732  * Accepts: mail stream
2733  *          mailbox name
2734  *          authentication identifer
2735  *          new access rights
2736  * Returns: T on success, NIL on failure
2737  */
2738
2739 long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights)
2740 {
2741   IMAPARG *args[4],ambx,aid,art;
2742   ambx.type = aid.type = art.type = ASTRING;
2743   ambx.text = (void *) mailbox; aid.text = (void *) id;
2744   art.text = (void *) rights;
2745   args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL;
2746   return imap_acl_work (stream,"SETACL",args);
2747 }
2748
2749
2750 /* IMAP delete ACL
2751  * Accepts: mail stream
2752  *          mailbox name
2753  *          authentication identifer
2754  * Returns: T on success, NIL on failure
2755  */
2756
2757 long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id)
2758 {
2759   IMAPARG *args[3],ambx,aid;
2760   ambx.type = aid.type = ASTRING;
2761   ambx.text = (void *) mailbox; aid.text = (void *) id;
2762   args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2763   return imap_acl_work (stream,"DELETEACL",args);
2764 }
2765
2766
2767 /* IMAP get ACL
2768  * Accepts: mail stream
2769  *          mailbox name
2770  * Returns: T on success with data returned via callback, NIL on failure
2771  */
2772
2773 long imap_getacl (MAILSTREAM *stream,char *mailbox)
2774 {
2775   IMAPARG *args[2],ambx;
2776   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2777     args[0] = &ambx; args[1] = NIL;
2778   return imap_acl_work (stream,"GETACL",args);
2779 }
2780 \f
2781 /* IMAP list rights
2782  * Accepts: mail stream
2783  *          mailbox name
2784  *          authentication identifer
2785  * Returns: T on success with data returned via callback, NIL on failure
2786  */
2787
2788 long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id)
2789 {
2790   IMAPARG *args[3],ambx,aid;
2791   ambx.type = aid.type = ASTRING;
2792   ambx.text = (void *) mailbox; aid.text = (void *) id;
2793   args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2794   return imap_acl_work (stream,"LISTRIGHTS",args);
2795 }
2796
2797
2798 /* IMAP my rights
2799  * Accepts: mail stream
2800  *          mailbox name
2801  * Returns: T on success with data returned via callback, NIL on failure
2802  */
2803
2804 long imap_myrights (MAILSTREAM *stream,char *mailbox)
2805 {
2806   IMAPARG *args[2],ambx;
2807   ambx.type = ASTRING; ambx.text = (void *) mailbox;
2808   args[0] = &ambx; args[1] = NIL;
2809   return imap_acl_work (stream,"MYRIGHTS",args);
2810 }
2811
2812
2813 /* IMAP ACL worker routine
2814  * Accepts: mail stream
2815  *          command
2816  *          command arguments
2817  * Returns: T on success, NIL on failure
2818  */
2819
2820 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[])
2821 {
2822   long ret = NIL;
2823   if (LEVELACL (stream)) {      /* send command */
2824     IMAPPARSEDREPLY *reply;
2825     if (imap_OK (stream,reply = imap_send (stream,command,args)))
2826       ret = LONGT;
2827     else mm_log (reply->text,ERROR);
2828   }
2829   else mm_log ("ACL not available on this IMAP server",ERROR);
2830   return ret;
2831 }
2832 \f
2833 /* IMAP set quota
2834  * Accepts: mail stream
2835  *          quota root name
2836  *          resource limit list as a stringlist
2837  * Returns: T on success with data returned via callback, NIL on failure
2838  */
2839
2840 long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits)
2841 {
2842   long ret = NIL;
2843   if (LEVELQUOTA (stream)) {    /* send "SETQUOTA" */
2844     IMAPPARSEDREPLY *reply;
2845     IMAPARG *args[3],aqrt,alim;
2846     aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2847     alim.type = SNLIST; alim.text = (void *) limits;
2848     args[0] = &aqrt; args[1] = &alim; args[2] = NIL;
2849     if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args)))
2850       ret = LONGT;
2851     else mm_log (reply->text,ERROR);
2852   }
2853   else mm_log ("Quota not available on this IMAP server",ERROR);
2854   return ret;
2855 }
2856 \f
2857 /* IMAP get quota
2858  * Accepts: mail stream
2859  *          quota root name
2860  * Returns: T on success with data returned via callback, NIL on failure
2861  */
2862
2863 long imap_getquota (MAILSTREAM *stream,char *qroot)
2864 {
2865   long ret = NIL;
2866   if (LEVELQUOTA (stream)) {    /* send "GETQUOTA" */
2867     IMAPPARSEDREPLY *reply;
2868     IMAPARG *args[2],aqrt;
2869     aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2870     args[0] = &aqrt; args[1] = NIL;
2871     if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args)))
2872       ret = LONGT;
2873     else mm_log (reply->text,ERROR);
2874   }
2875   else mm_log ("Quota not available on this IMAP server",ERROR);
2876   return ret;
2877 }
2878
2879
2880 /* IMAP get quota root
2881  * Accepts: mail stream
2882  *          mailbox name
2883  * Returns: T on success with data returned via callback, NIL on failure
2884  */
2885
2886 long imap_getquotaroot (MAILSTREAM *stream,char *mailbox)
2887 {
2888   long ret = NIL;
2889   if (LEVELQUOTA (stream)) {    /* send "GETQUOTAROOT" */
2890     IMAPPARSEDREPLY *reply;
2891     IMAPARG *args[2],ambx;
2892     ambx.type = ASTRING; ambx.text = (void *) mailbox;
2893     args[0] = &ambx; args[1] = NIL;
2894     if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args)))
2895       ret = LONGT;
2896     else mm_log (reply->text,ERROR);
2897   }
2898   else mm_log ("Quota not available on this IMAP server",ERROR);
2899   return ret;
2900 }
2901 \f
2902 /* Internal routines */
2903
2904
2905 /* IMAP send command
2906  * Accepts: MAIL stream
2907  *          command
2908  *          argument list
2909  * Returns: parsed reply
2910  */
2911
2912 #define CMDBASE LOCAL->tmp      /* command base */
2913
2914 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
2915 {
2916   IMAPPARSEDREPLY *reply;
2917   IMAPARG *arg,**arglst;
2918   SORTPGM *spg;
2919   STRINGLIST *list;
2920   SIZEDTEXT st;
2921   APPENDDATA *map;
2922   sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL);
2923   size_t i;
2924   void *a;
2925   char c,*s,*t,tag[10];
2926   stream->unhealthy = NIL;      /* make stream healthy again */
2927                                 /* gensym a new tag */
2928   sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
2929   if (!LOCAL->netstream)        /* make sure have a session */
2930     return imap_fake (stream,tag,"[CLOSED] IMAP connection lost");
2931   mail_lock (stream);           /* lock up the stream */
2932   if (sc)                       /* tell client sending a command */
2933     (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") &&
2934                         compare_cstring (cmd,"STORE") &&
2935                         compare_cstring (cmd,"SEARCH")) ? 
2936                        NIL : SC_EXPUNGEDEFERRED));
2937                                 /* ignore referral from previous command */
2938   if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
2939   sprintf (CMDBASE,"%s %s",tag,cmd);
2940   s = CMDBASE + strlen (CMDBASE);
2941   if (arglst = args) while (arg = *arglst++) {
2942     *s++ = ' ';                 /* delimit argument with space */
2943     switch (arg->type) {
2944     case ATOM:                  /* atom */
2945       for (t = (char *) arg->text; *t; *s++ = *t++);
2946       break;
2947     case NUMBER:                /* number */
2948       sprintf (s,"%lu",(unsigned long) arg->text);
2949       s += strlen (s);
2950       break;
2951     case FLAGS:                 /* flag list as a single string */
2952       if (*(t = (char *) arg->text) != '(') {
2953         *s++ = '(';             /* wrap parens around string */
2954         while (*t) *s++ = *t++;
2955         *s++ = ')';             /* wrap parens around string */
2956       }
2957       else while (*t) *s++ = *t++;
2958       break;
2959     case ASTRING:               /* atom or string, must be literal? */
2960       st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
2961       if (reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND))
2962         return reply;
2963       break;
2964     case LITERAL:               /* literal, as a stringstruct */
2965       if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply;
2966       break;
2967 \f
2968     case LIST:                  /* list of strings */
2969       list = (STRINGLIST *) arg->text;
2970       c = '(';                  /* open paren */
2971       do {                      /* for each list item */
2972         *s++ = c;               /* write prefix character */
2973         if (reply = imap_send_astring (stream,tag,&s,&list->text,NIL,
2974                                        CMDBASE+MAXCOMMAND)) return reply;
2975         c = ' ';                /* prefix character for subsequent strings */
2976       }
2977       while (list = list->next);
2978       *s++ = ')';               /* close list */
2979       break;
2980     case SEARCHPROGRAM:         /* search program */
2981       if (reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
2982                                   CMDBASE+MAXCOMMAND))
2983         return reply;
2984       break;
2985     case SORTPROGRAM:           /* search program */
2986       c = '(';                  /* open paren */
2987       for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) {
2988         *s++ = c;               /* write prefix */
2989         if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++);
2990         switch (spg->function) {
2991         case SORTDATE:
2992           for (t = "DATE"; *t; *s++ = *t++);
2993           break;
2994         case SORTARRIVAL:
2995           for (t = "ARRIVAL"; *t; *s++ = *t++);
2996           break;
2997         case SORTFROM:
2998           for (t = "FROM"; *t; *s++ = *t++);
2999           break;
3000         case SORTSUBJECT:
3001           for (t = "SUBJECT"; *t; *s++ = *t++);
3002           break;
3003         case SORTTO:
3004           for (t = "TO"; *t; *s++ = *t++);
3005           break;
3006         case SORTCC:
3007           for (t = "CC"; *t; *s++ = *t++);
3008           break;
3009         case SORTSIZE:
3010           for (t = "SIZE"; *t; *s++ = *t++);
3011           break;
3012         default:
3013           fatal ("Unknown sort program function in imap_send()!");
3014         }
3015         c = ' ';                /* prefix character for subsequent items */
3016       }
3017       *s++ = ')';               /* close list */
3018       break;
3019 \f
3020     case BODYTEXT:              /* body section */
3021       for (t = "BODY["; *t; *s++ = *t++);
3022       for (t = (char *) arg->text; *t; *s++ = *t++);
3023       break;
3024     case BODYPEEK:              /* body section */
3025       for (t = "BODY.PEEK["; *t; *s++ = *t++);
3026       for (t = (char *) arg->text; *t; *s++ = *t++);
3027       break;
3028     case BODYCLOSE:             /* close bracket and possible length */
3029       s[-1] = ']';              /* no leading space */
3030       for (t = (char *) arg->text; *t; *s++ = *t++);
3031       break;
3032     case SEQUENCE:              /* sequence */
3033       if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND)
3034         while (*t) *s++ = *t++; /* easy case */
3035       else {
3036         mail_unlock (stream);   /* unlock stream */
3037         a = arg->text;          /* save original sequence pointer */
3038         arg->type = ATOM;       /* make recursive call be faster */
3039         do {                    /* break up into multiple commands */
3040           if (i <= MAXCOMMAND) {/* final part? */
3041             reply = imap_send (stream,cmd,args);
3042             i = 0;              /* and mark as done */
3043           }
3044           else {                /* still needs to be split further */
3045             if (!(t = strchr (t + MAXCOMMAND - 30,',')) ||
3046                 ((t - (char *) arg->text) > MAXCOMMAND))
3047               fatal ("impossible over-long sequence");
3048             *t = '\0';          /* tie off sequence at point of split*/
3049                                 /* recurse to do this part */
3050             reply = imap_send (stream,cmd,args);
3051             *t++ = ',';         /* restore the comma in case something cares */
3052                                 /* punt if error */
3053             if (!imap_OK (stream,reply)) break;
3054                                 /* calculate size of remaining sequence */
3055             i -= (t - (char *) arg->text);
3056                                 /* point to new remaining sequence */
3057             arg->text = (void *) t;
3058           }
3059         } while (i);
3060         arg->type = SEQUENCE;   /* restore in case something cares */
3061         arg->text = a;
3062         return reply;           /* return result */
3063       }
3064       break;
3065     case LISTMAILBOX:           /* astring with wildcards */
3066       st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3067       if (reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND))
3068         return reply;
3069       break;
3070 \f
3071     case MULTIAPPEND:           /* append multiple messages */
3072                                 /* get package pointer */
3073       map = (APPENDDATA *) arg->text;
3074       if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)||
3075           !map->message) {
3076         STRING es;
3077         INIT (&es,mail_string,"",0);
3078         return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3079           reply : imap_fake (stream,tag,"Server zero-length literal error");
3080       }
3081     case MULTIAPPENDREDO:       /* redo multiappend */
3082                                 /* get package pointer */
3083       map = (APPENDDATA *) arg->text;
3084       do {                      /* make sure date valid if given */
3085         char datetmp[MAILTMPLEN];
3086         MESSAGECACHE elt;
3087         STRING es;
3088         if (!map->date || mail_parse_date (&elt,map->date)) {
3089           if (t = map->flags) { /* flags given? */
3090             if (*t != '(') {
3091               *s++ = '(';       /* wrap parens around string */
3092               while (*t) *s++ = *t++;
3093               *s++ = ')';       /* wrap parens around string */
3094             }
3095             else while (*t) *s++ = *t++;
3096             *s++ = ' ';         /* delimit with space */
3097           }
3098           if (map->date) {      /* date given? */
3099             st.size = strlen ((char *) (st.data = (unsigned char *)
3100                                         mail_date (datetmp,&elt)));
3101             if (reply = imap_send_astring (stream,tag,&s,&st,NIL,
3102                                            CMDBASE+MAXCOMMAND)) return reply;
3103             *s++ = ' ';         /* delimit with space */
3104           }
3105           if (reply = imap_send_literal (stream,tag,&s,map->message))
3106             return reply;
3107                                 /* get next message */
3108           if ((*map->af) (stream,map->data,&map->flags,&map->date,
3109                           &map->message)) {
3110                                 /* have a message, delete next in command */
3111             if (map->message) *s++ = ' ';
3112             continue;           /* loop back for next message */
3113           }
3114         }
3115                                 /* bad date or need to abort */
3116         INIT (&es,mail_string,"",0);
3117         return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3118           reply : imap_fake (stream,tag,"Server zero-length literal error");
3119         break;                  /* exit the loop */
3120       } while (map->message);
3121       break;
3122 \f
3123     case SNLIST:                /* list of string/number pairs */
3124       list = (STRINGLIST *) arg->text;
3125       c = '(';                  /* open paren */
3126       do {                      /* for each list item */
3127         *s++ = c;               /* write prefix character */
3128         if (list) {             /* sigh, QUOTA has bizarre syntax! */
3129           for (t = (char *) list->text.data; *t; *s++ = *t++);
3130           sprintf (s," %lu",list->text.size);
3131           s += strlen (s);
3132           c = ' ';              /* prefix character for subsequent strings */
3133         }
3134       }
3135       while (list = list->next);
3136       *s++ = ')';               /* close list */
3137       break;
3138     default:
3139       fatal ("Unknown argument type in imap_send()!");
3140     }
3141   }
3142                                 /* send the command */
3143   reply = imap_sout (stream,tag,CMDBASE,&s);
3144   mail_unlock (stream);         /* unlock stream */
3145   return reply;
3146 }
3147 \f
3148 /* IMAP send atom-string
3149  * Accepts: MAIL stream
3150  *          reply tag
3151  *          pointer to current position pointer of output bigbuf
3152  *          atom-string to output
3153  *          flag if list_wildcards allowed
3154  *          maximum to write as atom or qstring
3155  * Returns: error reply or NIL if success
3156  */
3157
3158 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
3159                                     SIZEDTEXT *as,long wildok,char *limit)
3160 {
3161   unsigned long j;
3162   char c;
3163   STRING st;
3164                                 /* default to atom unless empty or loser */
3165   int qflag = (as->size && !LOCAL->loser) ? NIL : T;
3166                                 /* in case needed */
3167   INIT (&st,mail_string,(void *) as->data,as->size);
3168                                 /* always write literal if no space */
3169   if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st);
3170   for (j = 0; j < as->size; j++) switch (c = as->data[j]) {
3171   default:                      /* all other characters */
3172     if (!(c & 0x80)) {          /* must not be 8bit */
3173       if (c <= ' ') qflag = T;  /* must quote if a CTL */
3174       break;
3175     }
3176   case '\0':                    /* not a CHAR */
3177   case '\012': case '\015':     /* not a TEXT-CHAR */
3178   case '"': case '\\':          /* quoted-specials (IMAP2 required this) */
3179     return imap_send_literal (stream,tag,s,&st);
3180   case '*': case '%':           /* list_wildcards */
3181     if (wildok) break;          /* allowed if doing the wild thing */
3182                                 /* atom_specials */
3183   case '(': case ')': case '{': case ' ': case 0x7f:
3184 #if 0
3185   case '"': case '\\':          /* quoted-specials (could work in IMAP4) */
3186 #endif
3187     qflag = T;                  /* must use quoted string format */
3188     break;
3189   }
3190   if (qflag) *(*s)++ = '"';     /* write open quote */
3191   for (j = 0; j < as->size; j++) *(*s)++ = as->data[j];
3192   if (qflag) *(*s)++ = '"';     /* write close quote */
3193   return NIL;
3194 }
3195 \f
3196 /* IMAP send literal
3197  * Accepts: MAIL stream
3198  *          reply tag
3199  *          pointer to current position pointer of output bigbuf
3200  *          literal to output as stringstruct
3201  * Returns: error reply or NIL if success
3202  */
3203
3204 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
3205                                     STRING *st)
3206 {
3207   IMAPPARSEDREPLY *reply;
3208   unsigned long i = SIZE (st);
3209   unsigned long j;
3210   sprintf (*s,"{%lu}",i);       /* write literal count */
3211   *s += strlen (*s);            /* size of literal count */
3212                                 /* send the command */
3213   reply = imap_sout (stream,tag,CMDBASE,s);
3214   if (strcmp (reply->tag,"+")) {/* prompt for more data? */
3215     mail_unlock (stream);       /* no, give up */
3216     return reply;
3217   }
3218   while (i) {                   /* dump the text */
3219     if (st->cursize) {          /* if text to do in this chunk */
3220       /* RFC 3501 technically forbids NULs in literals.  Normally, the
3221        * delivering MTA would take care of MIME converting the message text
3222        * so that it is NUL-free.  If it doesn't, then we have the choice of
3223        * either violating IMAP by sending NULs, corrupting the data, or going
3224        * to lots of work to do MIME conversion in the IMAP server.
3225        *
3226        * No current stringstruct driver objects to having its buffer patched.
3227        * If this ever changes, it will be necessary to change this kludge.
3228        */
3229                                 /* patch NULs to C1 control */
3230       for (j = 0; j < st->cursize; ++j)
3231         if (!st->curpos[j]) st->curpos[j] = 0x80;
3232       if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) {
3233         mail_unlock (stream);
3234         return imap_fake (stream,tag,"[CLOSED] IMAP connection broken (data)");
3235       }
3236       i -= st->cursize;         /* note that we wrote out this much */
3237       st->curpos += (st->cursize - 1);
3238       st->cursize = 0;
3239     }
3240     (*st->dtb->next) (st);      /* advance to next buffer's worth */
3241   }
3242   return NIL;                   /* success */
3243 }
3244 \f
3245 /* IMAP send search program
3246  * Accepts: MAIL stream
3247  *          reply tag
3248  *          base pointer if trimming needed
3249  *          pointer to current position pointer of output bigbuf
3250  *          search program to output
3251  *          pointer to limit guideline
3252  * Returns: error reply or NIL if success
3253  */
3254
3255
3256 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
3257                                  char **s,SEARCHPGM *pgm,char *limit)
3258 {
3259   IMAPPARSEDREPLY *reply;
3260   SEARCHHEADER *hdr;
3261   SEARCHOR *pgo;
3262   SEARCHPGMLIST *pgl;
3263   char *t;
3264                                 /* trim if called recursively */
3265   if (base) *s = imap_send_spgm_trim (base,*s,NIL);
3266   base = *s;                    /* this is the new base */
3267                                 /* default searchpgm */
3268   for (t = "ALL"; *t; *(*s)++ = *t++);
3269   if (!pgm) return NIL;         /* done if NIL searchpgm */
3270   if ((pgm->msgno &&            /* message sequences */
3271        (pgm->msgno->next ||     /* trim away first:last */
3272         (pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) &&
3273        (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) ||
3274       (pgm->uid &&
3275        (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit))))
3276     return reply;
3277                                 /* message sizes */
3278   if (pgm->larger) {
3279     sprintf (*s," LARGER %lu",pgm->larger);
3280     *s += strlen (*s);
3281   }
3282   if (pgm->smaller) {
3283     sprintf (*s," SMALLER %lu",pgm->smaller);
3284     *s += strlen (*s);
3285   }
3286 \f
3287                                 /* message flags */
3288   if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++);
3289   if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++);
3290   if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++);
3291   if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++);
3292   if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++);
3293   if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++);
3294   if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++);
3295   if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++);
3296   if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++);
3297   if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++);
3298   if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++);
3299   if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++);
3300   if ((pgm->keyword &&          /* keywords */
3301        (reply = imap_send_slist (stream,tag,base,s," KEYWORD ",pgm->keyword,
3302                                  limit))) ||
3303       (pgm->unkeyword &&
3304        (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ",
3305                                  pgm->unkeyword,limit))))
3306     return reply;
3307                                 /* sent date ranges */
3308   if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
3309   if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
3310   if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
3311                                 /* internal date ranges */
3312   if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
3313   if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
3314   if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
3315   if (pgm->older) {
3316     sprintf (*s," OLDER %lu",pgm->older);
3317     *s += strlen (*s);
3318   }
3319   if (pgm->younger) {
3320     sprintf (*s," YOUNGER %lu",pgm->younger);
3321     *s += strlen (*s);
3322   }
3323                                 /* search texts */
3324   if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ",
3325                                              pgm->bcc,limit))) ||
3326       (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc,
3327                                             limit))) ||
3328       (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ",
3329                                               pgm->from,limit))) ||
3330       (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to,
3331                                             limit))))
3332     return reply;
3333   if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ",
3334                                                  pgm->subject,limit))) ||
3335       (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ",
3336                                               pgm->body,limit))) ||
3337       (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ",
3338                                               pgm->text,limit))))
3339     return reply;
3340 \f
3341   /* Note that these criteria are not supported by IMAP and have to be
3342      emulated */
3343   if ((pgm->return_path &&
3344        (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ",
3345                                  pgm->return_path,limit))) ||
3346       (pgm->sender &&
3347        (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ",
3348                                  pgm->sender,limit))) ||
3349       (pgm->reply_to &&
3350        (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ",
3351                                  pgm->reply_to,limit))) ||
3352       (pgm->in_reply_to &&
3353        (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ",
3354                                  pgm->in_reply_to,limit))) ||
3355       (pgm->message_id &&
3356        (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ",
3357                                  pgm->message_id,limit))) ||
3358       (pgm->newsgroups &&
3359        (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ",
3360                                  pgm->newsgroups,limit))) ||
3361       (pgm->followup_to &&
3362        (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ",
3363                                  pgm->followup_to,limit))) ||
3364       (pgm->references &&
3365        (reply = imap_send_slist (stream,tag,base,s," HEADER References ",
3366                                  pgm->references,limit)))) return reply;
3367 \f
3368                                 /* all other headers */
3369   if (hdr = pgm->header) do {
3370     *s = imap_send_spgm_trim (base,*s," HEADER ");
3371     if (reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit))
3372       return reply;
3373     *(*s)++ = ' ';
3374     if (reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit))
3375       return reply;
3376   } while (hdr = hdr->next);
3377   for (pgo = pgm->or; pgo; pgo = pgo->next) {
3378     *s = imap_send_spgm_trim (base,*s," OR (");
3379     if (reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit))
3380       return reply;
3381     for (t = ") ("; *t; *(*s)++ = *t++);
3382     if (reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit))
3383       return reply;
3384     *(*s)++ = ')';
3385   }
3386   for (pgl = pgm->not; pgl; pgl = pgl->next) {
3387     *s = imap_send_spgm_trim (base,*s," NOT (");
3388     if (reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit))
3389       return reply;
3390     *(*s)++ = ')';
3391   }
3392                                 /* trim if needed */
3393   *s = imap_send_spgm_trim (base,*s,NIL);
3394   return NIL;                   /* search program written OK */
3395 }
3396
3397
3398 /* Write new text and trim extraneous "ALL" from searchpgm
3399  * Accepts: pointer to start of searchpgm or NIL
3400  *          current end pointer
3401  *          new text to write or NIL
3402  * Returns: new end pointer, trimmed if needed
3403  */
3404
3405 char *imap_send_spgm_trim (char *base,char *s,char *text)
3406 {
3407   char *t;
3408                                 /* write new text */
3409   if (text) for (t = text; *t; *s++ = *t++);
3410                                 /* need to trim? */
3411   if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') &&
3412       (base[2] == 'L') && (base[3] == ' ')) {
3413     memmove (base,t,s - t);     /* yes, blat down remaining text */
3414     s -= 4;                     /* and reduce current pointer */
3415   }
3416   return s;                     /* return new end pointer */
3417 }
3418 \f
3419 /* IMAP send search set
3420  * Accepts: MAIL stream
3421  *          current command tag
3422  *          base pointer if trimming needed
3423  *          pointer to current position pointer of output bigbuf
3424  *          search set to output
3425  *          message prefix
3426  *          maximum output pointer
3427  * Returns: NIL if success, error reply if error
3428  */
3429
3430 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
3431                                  char **s,SEARCHSET *set,char *prefix,
3432                                  char *limit)
3433 {
3434   IMAPPARSEDREPLY *reply;
3435   STRING st;
3436   char c,*t;
3437   char *start = *s;
3438                                 /* trim and write prefix */
3439   *s = imap_send_spgm_trim (base,*s,prefix);
3440                                 /* run down search list */
3441   for (c = NIL; set && (*s < limit); set = set->next, c = ',') {
3442     if (c) *(*s)++ = c;         /* write delimiter and first value */
3443     if (set->first == 0xffffffff) *(*s)++ = '*';
3444     else {
3445       sprintf (*s,"%lu",set->first);
3446       *s += strlen (*s);
3447     }
3448                                 /* have a second value? */
3449     if (set->last && (set->first != set->last)) {
3450       *(*s)++ = ':';            /* write delimiter and second value */
3451       if (set->last == 0xffffffff) *(*s)++ = '*';
3452       else {
3453         sprintf (*s,"%lu",set->last);
3454         *s += strlen (*s);
3455       }
3456     }
3457   }
3458   if (set) {                    /* insert "OR" in front of incomplete set */
3459     memmove (start + 3,start,*s - start);
3460     memcpy (start," OR",3);
3461     *s += 3;                    /* point to end of buffer */
3462                                 /* write glue that is equivalent to ALL */
3463     for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++);
3464                                 /* but broken by a literal */
3465     INIT (&st,mail_string,(void *) "FOO",3);
3466     if (reply = imap_send_literal (stream,tag,s,&st)) return reply;
3467     *(*s)++ = ')';              /* close glue */
3468     if (reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit))
3469       return reply;
3470     *(*s)++ = ')';              /* close second OR argument */
3471   }
3472   return NIL;
3473 }
3474 \f
3475 /* IMAP send search list
3476  * Accepts: MAIL stream
3477  *          reply tag
3478  *          base pointer if trimming needed
3479  *          pointer to current position pointer of output bigbuf
3480  *          name of search list
3481  *          search list to output
3482  *          maximum output pointer
3483  * Returns: NIL if success, error reply if error
3484  */
3485
3486 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
3487                                   char **s,char *name,STRINGLIST *list,
3488                                   char *limit)
3489 {
3490   IMAPPARSEDREPLY *reply;
3491   do {
3492     *s = imap_send_spgm_trim (base,*s,name);
3493     base = NIL;                 /* no longer need trimming */
3494     reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit);
3495   }
3496   while (!reply && (list = list->next));
3497   return reply;
3498 }
3499
3500
3501 /* IMAP send search date
3502  * Accepts: pointer to current position pointer of output bigbuf
3503  *          field name
3504  *          search date to output
3505  */
3506
3507 void imap_send_sdate (char **s,char *name,unsigned short date)
3508 {
3509   sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
3510            months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
3511   *s += strlen (*s);
3512 }
3513 \f
3514 /* IMAP send buffered command to sender
3515  * Accepts: MAIL stream
3516  *          reply tag
3517  *          string
3518  *          pointer to string tail pointer
3519  * Returns: reply
3520  */
3521
3522 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s)
3523 {
3524   IMAPPARSEDREPLY *reply;
3525   if (stream->debug) {          /* output debugging telemetry */
3526     **s = '\0';
3527     mail_dlog (base,LOCAL->sensitive);
3528   }
3529   *(*s)++ = '\015';             /* append CRLF */
3530   *(*s)++ = '\012';
3531   **s = '\0';
3532   reply = net_sout (LOCAL->netstream,base,*s - base) ?
3533     imap_reply (stream,tag) :
3534       imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)");
3535   *s = base;                    /* restart buffer */
3536   return reply;
3537 }
3538
3539
3540 /* IMAP send null-terminated string to sender
3541  * Accepts: MAIL stream
3542  *          string
3543  * Returns: T if success, else NIL
3544  */
3545
3546 long imap_soutr (MAILSTREAM *stream,char *string)
3547 {
3548   long ret;
3549   unsigned long i;
3550   char *s;
3551   if (stream->debug) mm_dlog (string);
3552   sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1),
3553            "%s\015\012",string);
3554   ret = net_sout (LOCAL->netstream,s,i);
3555   fs_give ((void **) &s);
3556   return ret;
3557 }
3558 \f
3559 /* IMAP get reply
3560  * Accepts: MAIL stream
3561  *          tag to search or NIL if want a greeting
3562  * Returns: parsed reply, never NIL
3563  */
3564
3565 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
3566 {
3567   IMAPPARSEDREPLY *reply;
3568   while (LOCAL->netstream) {    /* parse reply from server */
3569     if (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) {
3570                                 /* continuation ready? */
3571       if (!strcmp (reply->tag,"+")) return reply;
3572                                 /* untagged data? */
3573       else if (!strcmp (reply->tag,"*")) {
3574         imap_parse_unsolicited (stream,reply);
3575         if (!tag) return reply; /* return if just wanted greeting */
3576       }
3577       else {                    /* tagged data */
3578         if (tag && !compare_cstring (tag,reply->tag)) return reply;
3579                                 /* report bogon */
3580         sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
3581                  (char *) reply->tag,(char *) reply->key,(char *) reply->text);
3582         mm_notify (stream,LOCAL->tmp,WARN);
3583         stream->unhealthy = T;
3584       }
3585     }
3586
3587         /* [santosh.br@samsung.com]  The below four lines are a hack for the timeout issue in email service. When there is blank line response for some commands, email service
3588           * waits for 30s hoping some valid response will follow. But if the stream->unhealthy parameter is set then observation shows no benefit in waiting
3589           * for any response as the response has never come after this parameter is set.Added for email performance improvement. 
3590           * 
3591           * [g.shyamakshi@samsung.com] Stream->unhealthy check is required to recognize other conditions as well - Unknown body/RFC822 message property, Unexpected tagged response, Junk data (stream invalid), etc 
3592           * On recognizing stream as unhealthy, no further reply should be parsed.
3593           */
3594         if(stream->unhealthy)
3595         {
3596                 break;
3597         }
3598   }
3599   return imap_fake (stream,tag,
3600                     "[CLOSED] IMAP connection broken (server response)");
3601 }
3602 \f
3603 /* IMAP parse reply
3604  * Accepts: MAIL stream
3605  *          text of reply
3606  * Returns: parsed reply, or NIL if can't parse at least a tag and key
3607  */
3608
3609
3610 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
3611 {
3612   char *r;
3613   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
3614                                 /* init fields in case error */
3615   LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL;
3616   if (!(LOCAL->reply.line = text)) {
3617                                 /* NIL text means the stream died */
3618     if (LOCAL->netstream) net_close (LOCAL->netstream);
3619     LOCAL->netstream = NIL;
3620     return NIL;
3621   }
3622   if (stream->debug) mm_dlog (LOCAL->reply.line);
3623   if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) {
3624     mm_notify (stream,"IMAP server sent a blank line",WARN);
3625     stream->unhealthy = T;
3626     return NIL;
3627   }
3628                                 /* non-continuation replies */
3629   if (strcmp (LOCAL->reply.tag,"+")) {
3630                                 /* parse key */
3631     if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) {
3632                                 /* determine what is missing */
3633       sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",
3634                (char *) LOCAL->reply.tag);
3635       mm_notify (stream,LOCAL->tmp,WARN);
3636       stream->unhealthy = T;
3637       return NIL;               /* can't parse this text */
3638     }
3639     ucase (LOCAL->reply.key);   /* canonicalize key to upper */
3640                                 /* get text as well, allow empty text */
3641     if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
3642       LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
3643   }
3644   else {                        /* special handling of continuation */
3645     LOCAL->reply.key = "BAD";   /* so it barfs if not expecting continuation */
3646     if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
3647       LOCAL->reply.text = "";
3648   }
3649   return &LOCAL->reply;         /* return parsed reply */
3650 }
3651 \f
3652 /* IMAP fake reply when stream determined to be dead
3653  * Accepts: MAIL stream
3654  *          tag
3655  *          text of fake reply (must start with "[CLOSED]")
3656  * Returns: parsed reply
3657  */
3658
3659 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
3660 {
3661   mm_notify (stream,text,BYE);  /* send bye alert */
3662   if (LOCAL->netstream) net_close (LOCAL->netstream);
3663   LOCAL->netstream = NIL;       /* farewell, dear NET stream... */
3664                                 /* flush previous reply */
3665   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
3666                                 /* build new fake reply */
3667   LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*");
3668   LOCAL->reply.key = "NO";
3669   LOCAL->reply.text = text;
3670   return &LOCAL->reply;         /* return parsed reply */
3671 }
3672
3673
3674 /* IMAP check for OK response in tagged reply
3675  * Accepts: MAIL stream
3676  *          parsed reply
3677  * Returns: T if OK else NIL
3678  */
3679
3680 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
3681 {
3682   long ret = NIL;
3683                                 /* OK - operation succeeded */
3684   if (!strcmp (reply->key,"OK")) {
3685     imap_parse_response (stream,reply->text,NIL,NIL);
3686     ret = T;
3687   }
3688                                 /* NO - operation failed */
3689   else if (!strcmp (reply->key,"NO"))
3690     imap_parse_response (stream,reply->text,WARN,NIL);
3691   else {                        /* BAD - operation rejected */
3692     if (!strcmp (reply->key,"BAD")) {
3693       imap_parse_response (stream,reply->text,ERROR,NIL);
3694       sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text);
3695     }
3696                                 /* bad protocol received */
3697     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
3698                   (char *) reply->key,(char *) reply->text);
3699     mm_log (LOCAL->tmp,ERROR);  /* either way, this is not good */
3700   }
3701   return ret;
3702 }
3703 \f
3704 /* IMAP parse and act upon unsolicited reply
3705  * Accepts: MAIL stream
3706  *          parsed reply
3707  */
3708
3709 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
3710 {
3711   unsigned long i = 0;
3712   unsigned long j,msgno;
3713   unsigned char *s,*t;
3714   char *r;
3715                                 /* see if key is a number */
3716   if (isdigit (*reply->key)) {
3717     msgno = strtoul (reply->key,(char **) &s,10);
3718     if (*s) {                   /* better be nothing after number */
3719       sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
3720                (char *) reply->key);
3721       mm_notify (stream,LOCAL->tmp,WARN);
3722       stream->unhealthy = T;
3723       return;
3724     }
3725     if (!reply->text) {         /* better be some data */
3726       mm_notify (stream,"Missing message data",WARN);
3727       stream->unhealthy = T;
3728       return;
3729     }
3730                                 /* get message data type, canonicalize upper */
3731     s = ucase (strtok_r (reply->text," ",&r));
3732                                 /* and locate the text after it */
3733     t = strtok_r (NIL,"\n",&r);
3734                                 /* now take the action */
3735                                 /* change in size of mailbox */
3736     if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs))
3737       mail_exists (stream,msgno);
3738     else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs))
3739       mail_recent (stream,msgno);
3740     else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) {
3741       mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
3742       MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
3743       if (elt) imap_gc_body (elt->private.msg.body);
3744                                 /* notify upper level */
3745       mail_expunged (stream,msgno);
3746     }
3747 \f
3748     else if ((!strcmp (s,"FETCH") || !strcmp (s,"STORE")) &&
3749              msgno && (msgno <= stream->nmsgs)) {
3750       char *prop;
3751       GETS_DATA md;
3752       ENVELOPE **e;
3753       MESSAGECACHE *elt = mail_elt (stream,msgno);
3754       ENVELOPE *env = NIL;
3755       imapenvelope_t ie =
3756         (imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL);
3757       ++t;                      /* skip past open parenthesis */
3758                                 /* parse Lisp-form property list */
3759       while (prop = (strtok_r (t," )",&r))) {
3760         t = strtok_r (NIL,"\n",&r);
3761         INIT_GETS (md,stream,elt->msgno,NIL,0,0);
3762         e = NIL;                /* not pointing at any envelope yet */
3763                                 /* canonicalize property, parse it */
3764         if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t);
3765         else if (!strcmp (prop,"INTERNALDATE") &&
3766                  (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) {
3767           if (!mail_parse_date (elt,s)) {
3768             sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s);
3769             mm_notify (stream,LOCAL->tmp,WARN);
3770             stream->unhealthy = T;
3771                                 /* slam in default so we don't try again */
3772             mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000");
3773           }
3774           fs_give ((void **) &s);
3775         }
3776                                 /* unique identifier */
3777         else if (!strcmp (prop,"UID")) {
3778           LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10);
3779           LOCAL->lastuid.msgno = elt->msgno;
3780         }
3781         else if (!strcmp (prop,"ENVELOPE")) {
3782           if (stream->scache) { /* short cache, flush old stuff */
3783             mail_free_body (&stream->body);
3784             stream->msgno = elt->msgno;
3785             e = &stream->env;   /* get pointer to envelope */
3786           }
3787           else e = &elt->private.msg.env;
3788           imap_parse_envelope (stream,e,&t,reply);
3789         }
3790         else if (!strncmp (prop,"BODY",4)) {
3791           if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) {
3792             BODY **body;
3793             if (stream->scache){/* short cache, flush old stuff */
3794               if (stream->msgno != msgno) {
3795                 mail_free_envelope (&stream->env);
3796                 sprintf (LOCAL->tmp,"Body received for %lu but current is %lu",
3797                          msgno,stream->msgno);
3798                 stream->msgno = msgno;
3799               }
3800                                 /* get pointer to body */
3801               body = &stream->body;
3802             }
3803             else body = &elt->private.msg.body;
3804                                 /* flush any prior body */
3805             mail_free_body (body);
3806                                 /* instantiate and parse a new body */
3807             imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply);
3808           }
3809 \f
3810           else if (prop[4] == '[') {
3811             STRINGLIST *stl = NIL;
3812             SIZEDTEXT text;
3813                                 /* will want to return envelope data */
3814             if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") ||
3815                 !strcmp (md.what,"0]"))
3816               e = stream->scache ? &stream->env : &elt->private.msg.env;
3817             LOCAL->tmp[0] ='\0';/* no errors yet */
3818                                 /* found end of section? */
3819             if (!(s = strchr (md.what,']'))) {
3820                                 /* skip leading nesting */
3821               for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++);
3822                                 /* better be one of these */
3823               if (strncmp (s,"HEADER.FIELDS",13) &&
3824                   (!s[13] || strcmp (s+13,".NOT")))
3825                 sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what);
3826                                 /* get list of headers */
3827               else if (!(stl = imap_parse_stringlist (stream,&t,reply)))
3828                 sprintf (LOCAL->tmp,"Bogus header field list: %.80s",
3829                          (char *) t);
3830               else if (*t != ']')
3831                 sprintf (LOCAL->tmp,"Unterminated header section: %.80s",
3832                          (char *) t);
3833                                 /* point after the text */
3834               else if (t = strchr (s = t,' ')) *t++ = '\0';
3835             }
3836             if (s && !LOCAL->tmp[0]) {
3837               *s++ = '\0';      /* tie off section specifier */
3838               if (*s == '<') {  /* partial specifier? */
3839                 md.first = strtoul (s+1,(char **) &s,10) + 1;
3840                 if (*s++ != '>')        /* make sure properly terminated */
3841                   sprintf (LOCAL->tmp,"Unterminated partial data: %.80s",
3842                            (char *) s-1);
3843               }
3844               if (!LOCAL->tmp[0] && *s)
3845                 sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s);
3846             }
3847             if (LOCAL->tmp[0]) { /* got any errors? */
3848               mm_notify (stream,LOCAL->tmp,WARN);
3849               stream->unhealthy = T;
3850               mail_free_stringlist (&stl);
3851             }
3852             else {              /* parse text from server */
3853               text.data = (unsigned char *)
3854                 imap_parse_string (stream,&t,reply,
3855                                    ((md.what[0] && (md.what[0] != 'H')) ||
3856                                     md.first || md.last) ? &md : NIL,
3857                                    &text.size,NIL);
3858                                 /* all done if partial */
3859               if (md.first || md.last) mail_free_stringlist (&stl);
3860                                 /* otherwise register it in the cache */
3861               else imap_cache (stream,msgno,md.what,stl,&text);
3862             }
3863             fs_give ((void **) &md.what);
3864           }
3865           else {
3866             sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop);
3867             mm_notify (stream,LOCAL->tmp,WARN);
3868             stream->unhealthy = T;
3869           }
3870         }
3871 \f
3872                                 /* one of the RFC822 props? */
3873         else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){
3874           SIZEDTEXT text;
3875           if (!prop[6]) {       /* cache full message */
3876             md.what = "";
3877             text.data = (unsigned char *)
3878               imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
3879             imap_cache (stream,msgno,md.what,NIL,&text);
3880           }
3881           else if (!strcmp (prop+7,"SIZE"))
3882             elt->rfc822_size = strtoul (t,(char **) &t,10);
3883                                 /* legacy properties */
3884           else if (!strcmp (prop+7,"HEADER")) {
3885             text.data = (unsigned char *)
3886               imap_parse_string (stream,&t,reply,NIL,&text.size,NIL);
3887             imap_cache (stream,msgno,"HEADER",NIL,&text);
3888             e = stream->scache ? &stream->env : &elt->private.msg.env;
3889           }
3890           else if (!strcmp (prop+7,"TEXT")) {
3891             md.what = "TEXT";
3892             text.data = (unsigned char *)
3893               imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
3894             imap_cache (stream,msgno,md.what,NIL,&text);
3895           }
3896           else {
3897             sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop);
3898             mm_notify (stream,LOCAL->tmp,WARN);
3899             stream->unhealthy = T;
3900           }
3901         }
3902         else {
3903           sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
3904           mm_notify (stream,LOCAL->tmp,WARN);
3905           stream->unhealthy = T;
3906         }
3907         if (e && *e) env = *e;  /* note envelope if we got one */
3908       }
3909                                 /* do callback if requested */
3910       if (ie && env) (*ie) (stream,msgno,env);
3911     }
3912                                 /* obsolete response to COPY */
3913     else if (strcmp (s,"COPY")) {
3914       sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s);
3915       mm_notify (stream,LOCAL->tmp,WARN);
3916       stream->unhealthy = T;
3917     }
3918   }
3919 \f
3920   else if (!strcmp (reply->key,"FLAGS") && reply->text &&
3921            (*reply->text == '(') &&
3922            (s = strtok_r (reply->text+1," )",&r)))
3923     do if (*s != '\\') {
3924       for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] &&
3925              compare_cstring (s,stream->user_flags[i]); i++);
3926       if (i > NUSERFLAGS) {
3927         sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s",
3928                  (char *) s);
3929         mm_notify (stream,LOCAL->tmp,WARN);
3930       }
3931       else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s);
3932     }
3933     while (s = strtok_r (NIL," )",&r));
3934   else if (!strcmp (reply->key,"SEARCH")) {
3935                                 /* only do something if have text */
3936     if (reply->text && (t = strtok_r (reply->text," ",&r))) do
3937       if (i = strtoul (t,NIL,10)) {
3938                                 /* UIDs always passed to main program */
3939         if (LOCAL->uidsearch) mm_searched (stream,i);
3940                                 /* should be a msgno then */
3941         else if ((i <= stream->nmsgs) &&
3942                  (!LOCAL->filter || mail_elt (stream,i)->private.filter)) {
3943           mail_elt (stream,i)->searched = T;
3944           if (!stream->silent) mm_searched (stream,i);
3945         }
3946       } while (t = strtok_r (NIL," ",&r));
3947   }
3948   else if (!strcmp (reply->key,"SORT")) {
3949     sortresults_t sr = (sortresults_t)
3950       mail_parameters (NIL,GET_SORTRESULTS,NIL);
3951     LOCAL->sortsize = 0;        /* initialize sort data */
3952     if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
3953     LOCAL->sortdata = (unsigned long *)
3954       fs_get ((stream->nmsgs + 1) * sizeof (unsigned long));
3955                                 /* only do something if have text */
3956     if (reply->text && (t = strtok_r (reply->text," ",&r))) {
3957       do if ((i = atol (t)) && (LOCAL->filter ?
3958                                 mail_elt (stream,i)->searched : T))
3959         LOCAL->sortdata[LOCAL->sortsize++] = i;
3960       while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs));
3961     }
3962     LOCAL->sortdata[LOCAL->sortsize] = 0;
3963                                 /* also return via callback if requested */
3964     if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize);
3965   }
3966   else if (!strcmp (reply->key,"THREAD")) {
3967     threadresults_t tr = (threadresults_t)
3968       mail_parameters (NIL,GET_THREADRESULTS,NIL);
3969     if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
3970     if (s = reply->text) {
3971       LOCAL->threaddata = imap_parse_thread (stream,&s);
3972       if (tr) (*tr) (stream,LOCAL->threaddata);
3973       if (s && *s) {
3974         sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s);
3975         mm_notify (stream,LOCAL->tmp,WARN);
3976         stream->unhealthy = T;
3977       }
3978     }
3979   }
3980 \f
3981   else if (!strcmp (reply->key,"STATUS") && reply->text) {
3982     MAILSTATUS status;
3983     unsigned char *txt = reply->text;
3984     if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt &&
3985         (*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) &&
3986         (s - txt) && !s[1]) {
3987       *s = '\0';                /* tie off status data */
3988                                 /* initialize data block */
3989       status.flags = status.messages = status.recent = status.unseen =
3990         status.uidnext = status.uidvalidity = 0;
3991       while (*txt && (s = strchr (txt,' '))) {
3992         *s++ = '\0';            /* tie off status attribute name */
3993                                 /* get attribute value */
3994         i = strtoul (s,(char **) &s,10);
3995         if (!compare_cstring (txt,"MESSAGES")) {
3996           status.flags |= SA_MESSAGES;
3997           status.messages = i;
3998         }
3999         else if (!compare_cstring (txt,"RECENT")) {
4000           status.flags |= SA_RECENT;
4001           status.recent = i;
4002         }
4003         else if (!compare_cstring (txt,"UNSEEN")) {
4004           status.flags |= SA_UNSEEN;
4005           status.unseen = i;
4006         }
4007         else if (!compare_cstring (txt,"UIDNEXT")) {
4008           status.flags |= SA_UIDNEXT;
4009           status.uidnext = i;
4010         }
4011         else if (!compare_cstring (txt,"UIDVALIDITY")) {
4012           status.flags |= SA_UIDVALIDITY;
4013           status.uidvalidity = i;
4014         }
4015                                 /* next attribute */
4016         txt = (*s == ' ') ? s + 1 : s;
4017       }
4018       if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) <
4019           IMAPTMPLEN) {
4020         strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t);
4021                                 /* pass status to main program */
4022         mm_status (stream,LOCAL->tmp,&status);
4023       }
4024     }
4025     if (t) fs_give ((void **) &t);
4026   }
4027 \f
4028   else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
4029            reply->text && (*reply->text == '(') &&
4030            (s = strchr (reply->text,')')) && (s[1] == ' ')) {
4031     char delimiter = '\0';
4032     *s++ = '\0';                /* tie off attribute list */
4033                                 /* parse attribute list */
4034     if (t = strtok_r (reply->text+1," ",&r)) do {
4035       if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS;
4036       else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT;
4037       else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED;
4038       else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED;
4039       else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN;
4040       else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN;
4041                                 /* ignore extension flags */
4042     }
4043     while (t = strtok_r (NIL," ",&r));
4044     switch (*++s) {             /* process delimiter */
4045     case 'N':                   /* NIL */
4046     case 'n':
4047       s += 4;                   /* skip over NIL<space> */
4048       break;
4049     case '"':                   /* have a delimiter */
4050       delimiter = (*++s == '\\') ? *++s : *s;
4051       s += 3;                   /* skip over <delimiter><quote><space> */
4052     }
4053                                 /* parse the mailbox name */
4054     if (t = imap_parse_astring (stream,&s,reply,&j)) {
4055                                 /* prepend prefix if requested */
4056       if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN))
4057         sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t);
4058       else s = t;               /* otherwise just mailbox name */
4059                                 /* pass data to main program */
4060       if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i);
4061       else mm_list (stream,delimiter,s,i);
4062       fs_give ((void **) &t);   /* flush mailbox name */
4063     }
4064   }
4065   else if (!strcmp (reply->key,"NAMESPACE")) {
4066     if (LOCAL->namespace) {
4067       mail_free_namespace (&LOCAL->namespace[0]);
4068       mail_free_namespace (&LOCAL->namespace[1]);
4069       mail_free_namespace (&LOCAL->namespace[2]);
4070     }
4071     else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *));
4072     if (s = reply->text) {      /* parse namespace results */
4073       LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply);
4074       LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply);
4075       LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply);
4076       if (s && *s) {
4077         sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s);
4078         mm_notify (stream,LOCAL->tmp,WARN);
4079         stream->unhealthy = T;
4080       }
4081     }
4082     else {
4083       mm_notify (stream,"Missing namespace list",WARN);
4084       stream->unhealthy = T;
4085     }
4086   }
4087 \f
4088   else if (!strcmp (reply->key,"ACL") && (s = reply->text) &&
4089            (t = imap_parse_astring (stream,&s,reply,NIL))) {
4090     getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL);
4091     if (s && (*s++ == ' ')) {
4092       ACLLIST *al = mail_newacllist ();
4093       ACLLIST *ac = al;
4094       do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) &&
4095              s && (*s++ == ' '))
4096         ac->rights = imap_parse_astring (stream,&s,reply,NIL);
4097       while (ac->rights && s && (*s == ' ') && s++ &&
4098              (ac = ac->next = mail_newacllist ()));
4099       if (!ac->rights || (s && *s)) {
4100         sprintf (LOCAL->tmp,"Invalid ACL identifer/rights for %.80s",
4101                  (char *) t);
4102         mm_notify (stream,LOCAL->tmp,WARN);
4103         stream->unhealthy = T;
4104       }
4105       else if (ar) (*ar) (stream,t,al);
4106       mail_free_acllist (&al);  /* clean up */
4107     }
4108                                 /* no optional rights */
4109     else if (ar) (*ar) (stream,t,NIL);
4110     fs_give ((void **) &t);     /* free mailbox name */
4111   }
4112 \f
4113   else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) &&
4114            (t = imap_parse_astring (stream,&s,reply,NIL))) {
4115     listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL);
4116     char *id,*r;
4117     if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){
4118       if (s && (*s++ == ' ') &&
4119           (r = imap_parse_astring (stream,&s,reply,NIL))) {
4120         if (s && (*s++ == ' ')) {
4121           STRINGLIST *rl = mail_newstringlist ();
4122           STRINGLIST *rc = rl;
4123           do rc->text.data = (unsigned char *)
4124             imap_parse_astring (stream,&s,reply,&rc->text.size);
4125           while (rc->text.data && s && (*s == ' ') && s++ &&
4126                  (rc = rc->next = mail_newstringlist ()));
4127           if (!rc->text.data || (s && *s)) {
4128             sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s",
4129                      (char *) t);
4130             mm_notify (stream,LOCAL->tmp,WARN);
4131             stream->unhealthy = T;
4132           }
4133           else if (lr) (*lr) (stream,t,id,r,rl);
4134                                 /* clean up */
4135           mail_free_stringlist (&rl);
4136         }
4137                                 /* no optional rights */
4138         else if (lr) (*lr) (stream,t,id,r,NIL);
4139         fs_give ((void **) &r); /* free rights */
4140       }
4141       else {
4142         sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t);
4143         mm_notify (stream,LOCAL->tmp,WARN);
4144         stream->unhealthy = T;
4145       }
4146       fs_give ((void **) &id);  /* free identifier */
4147     }
4148     else {
4149       sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifer for %.80s",(char *) t);
4150       mm_notify (stream,LOCAL->tmp,WARN);
4151       stream->unhealthy = T;
4152     }
4153     fs_give ((void **) &t);     /* free mailbox name */
4154   }
4155 \f
4156   else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) &&
4157            (t = imap_parse_astring (stream,&s,reply,NIL))) {
4158     myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL);
4159     char *r;
4160     if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) {
4161       if (s && *s) {
4162         sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t);
4163         mm_notify (stream,LOCAL->tmp,WARN);
4164         stream->unhealthy = T;
4165       }
4166       else if (mr) (*mr) (stream,t,r);
4167       fs_give ((void **) &r);   /* free rights */
4168     }
4169     else {
4170       sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t);
4171       mm_notify (stream,LOCAL->tmp,WARN);
4172       stream->unhealthy = T;
4173     }
4174     fs_give ((void **) &t);     /* free mailbox name */
4175   }
4176 \f
4177                                 /* this response has a bizarre syntax! */
4178   else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) &&
4179            (t = imap_parse_astring (stream,&s,reply,NIL))) {
4180                                 /* in case error */
4181     sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t);
4182     if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) {
4183       quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL);
4184       QUOTALIST *ql = NIL;
4185       QUOTALIST *qc;
4186                                 /* parse non-empty quota resource list */
4187       if (*s != ')') for (ql = qc = mail_newquotalist (); T;
4188                           qc = qc->next = mail_newquotalist ()) {
4189         if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s &&
4190             (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) {
4191           if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10);
4192           else if (t = strchr (s,' ')) t = s;
4193           if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
4194             if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
4195             else if (t = strpbrk (s," )")) t = s;
4196                                 /* another resource follows? */
4197             if (*s == ' ') continue;
4198                                 /* end of resource list? */
4199             if ((*s == ')') && !s[1]) {
4200               if (qt) (*qt) (stream,t,ql);
4201               break;            /* all done */
4202             }
4203           }
4204         }
4205                                 /* something bad happened */
4206         mm_notify (stream,LOCAL->tmp,WARN);
4207         stream->unhealthy = T;
4208         break;                  /* parse failed */
4209       }
4210                                 /* all done with quota resource list now */
4211       if (ql) mail_free_quotalist (&ql);
4212     }
4213     else {
4214       mm_notify (stream,LOCAL->tmp,WARN);
4215       stream->unhealthy = T;
4216     }
4217     fs_give ((void **) &t);     /* free root name */
4218   }
4219   else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) &&
4220            (t = imap_parse_astring (stream,&s,reply,NIL))) {
4221     sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t);
4222     if (s && (*s++ == ' ')) {
4223       quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL);
4224       STRINGLIST *rl = mail_newstringlist ();
4225       STRINGLIST *rc = rl;
4226       do rc->text.data = (unsigned char *)
4227         imap_parse_astring (stream,&s,reply,&rc->text.size);
4228       while (rc->text.data && *s && (*s++ == ' ') &&
4229                (rc = rc->next = mail_newstringlist ()));
4230       if (!rc->text.data || (s && *s)) {
4231         mm_notify (stream,LOCAL->tmp,WARN);
4232         stream->unhealthy = T;
4233       }
4234       else if (qr) (*qr) (stream,t,rl);
4235                                 /* clean up */
4236       mail_free_stringlist (&rl);
4237     }
4238     else {
4239       mm_notify (stream,LOCAL->tmp,WARN);
4240       stream->unhealthy = T;
4241     }
4242     fs_give ((void **) &t);
4243   }
4244 \f
4245   else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
4246     imap_parse_response (stream,reply->text,NIL,T);
4247   else if (!strcmp (reply->key,"NO"))
4248     imap_parse_response (stream,reply->text,WARN,T);
4249   else if (!strcmp (reply->key,"BAD"))
4250     imap_parse_response (stream,reply->text,ERROR,T);
4251   else if (!strcmp (reply->key,"BYE")) {
4252     LOCAL->byeseen = T;         /* note that a BYE seen */
4253     imap_parse_response (stream,reply->text,BYE,T);
4254   }
4255   else if (!strcmp (reply->key,"CAPABILITY") && reply->text)
4256     imap_parse_capabilities (stream,reply->text);
4257   else if (!strcmp (reply->key,"MAILBOX") && reply->text) {
4258     if (LOCAL->prefix &&
4259         ((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN))
4260       sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text);
4261     else t = reply->text;
4262     mm_list (stream,NIL,t,NIL);
4263   }
4264   else {
4265     sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4266              (char *) reply->key);
4267     mm_notify (stream,LOCAL->tmp,WARN);
4268     stream->unhealthy = T;
4269   }
4270 }
4271 \f
4272 /* Parse human-readable response text
4273  * Accepts: mail stream
4274  *          text
4275  *          error level for mm_notify()
4276  *          non-NIL if want mm_notify() event even if no response code
4277  */
4278
4279 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy)
4280 {
4281   char *s,*t,*r;
4282   size_t i;
4283   unsigned long j;
4284   MESSAGECACHE *elt;
4285   copyuid_t cu;
4286   appenduid_t au;
4287   SEARCHSET *source = NIL;
4288   SEARCHSET *dest = NIL;
4289   if (text && (*text == '[') && (t = strchr (s = text + 1,']')) &&
4290       ((i = t - s) < IMAPTMPLEN)) {
4291     LOCAL->tmp[i] = '\0';       /* make mungable copy of text code */
4292     if (s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) *s++ = '\0';
4293     if (s) {                    /* have argument? */
4294       ntfy = NIL;               /* suppress mm_notify if normal SELECT data */
4295       if (!compare_cstring (t,"UIDVALIDITY") &&
4296           ((j = strtoul (s,NIL,10)) != stream->uid_validity)) {
4297         mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4298         stream->uid_validity = j;
4299                                 /* purge any UIDs in cache */
4300         for (j = 1; j <= stream->nmsgs; j++)
4301           if (elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT))
4302             elt->private.uid = 0;
4303       }
4304       else if (!compare_cstring (t,"UIDNEXT"))
4305         stream->uid_last = strtoul (s,NIL,10) - 1;
4306       else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') &&
4307                (t[i-1] == ')')) {
4308         t[i-1] = '\0';          /* tie off flags */
4309         stream->perm_seen = stream->perm_deleted = stream->perm_answered =
4310           stream->perm_draft = stream->kwd_create = NIL;
4311         stream->perm_user_flags = NIL;
4312         if (s = strtok_r (s+1," ",&r)) do {
4313           if (*s == '\\') {     /* system flags */
4314             if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T;
4315             else if (!compare_cstring (s,"\\Deleted"))
4316               stream->perm_deleted = T;
4317             else if (!compare_cstring (s,"\\Flagged"))
4318               stream->perm_flagged = T;
4319             else if (!compare_cstring (s,"\\Answered"))
4320               stream->perm_answered = T;
4321             else if (!compare_cstring (s,"\\Draft")) stream->perm_draft = T;
4322             else if (!strcmp (s,"\\*")) stream->kwd_create = T;
4323           }
4324           else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
4325         }
4326         while (s = strtok_r (NIL," ",&r));
4327       }
4328 \f
4329       else if (!compare_cstring (t,"CAPABILITY"))
4330         imap_parse_capabilities (stream,s);
4331       else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) &&
4332                !compare_cstring (t,"COPYUID") &&
4333                (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) &&
4334                isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4335                (source = mail_parse_set (s,&s)) && (*s++ == ' ') &&
4336                (dest = mail_parse_set (s,&s)) && !*s)
4337         (*cu) (stream,LOCAL->appendmailbox,j,source,dest);
4338       else if (j && !compare_cstring (t,"APPENDUID") &&
4339                (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) &&
4340                isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4341                (dest = mail_parse_set (s,&s)) && !*s)
4342         (*au) (LOCAL->appendmailbox,j,dest);
4343       else {                    /* all other response code events */
4344         ntfy = T;               /* must mm_notify() */
4345         if (!compare_cstring (t,"REFERRAL"))
4346           LOCAL->referral = cpystr (t + 9);
4347       }
4348       mail_free_searchset (&source);
4349       mail_free_searchset (&dest);
4350     }
4351     else {                      /* no arguments */
4352       if (!compare_cstring (t,"UIDNOTSTICKY")) {
4353         ntfy = NIL;
4354         stream->uid_nosticky = T;
4355       }
4356       else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T;
4357       else if (!compare_cstring (t,"READ-WRITE"))
4358         stream->rdonly = NIL;
4359       else if (!compare_cstring (t,"PARSE") && !errflg)
4360         errflg = PARSE;
4361     }
4362   }
4363                                 /* give event to main program */
4364   if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg);
4365 }
4366 \f
4367 /* Parse a namespace
4368  * Accepts: mail stream
4369  *          current text pointer
4370  *          parsed reply
4371  * Returns: namespace list, text pointer updated
4372  */
4373
4374 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
4375                                  IMAPPARSEDREPLY *reply)
4376 {
4377   NAMESPACE *ret = NIL;
4378   NAMESPACE *nam = NIL;
4379   NAMESPACE *prev = NIL;
4380   PARAMETER *par = NIL;
4381   if (*txtptr) {                /* only if argument given */
4382                                 /* ignore leading space */
4383     while (**txtptr == ' ') ++*txtptr;
4384     switch (**txtptr) {
4385     case 'N':                   /* if NIL */
4386     case 'n':
4387       ++*txtptr;                /* bump past "N" */
4388       ++*txtptr;                /* bump past "I" */
4389       ++*txtptr;                /* bump past "L" */
4390       break;
4391     case '(':
4392       ++*txtptr;                /* skip past open paren */
4393       while (**txtptr == '(') {
4394         ++*txtptr;              /* skip past open paren */
4395         prev = nam;             /* note previous if any */
4396         nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0,
4397                                   sizeof (NAMESPACE));
4398         if (!ret) ret = nam;    /* if first time note first namespace */
4399                                 /* if previous link new block to it */
4400         if (prev) prev->next = nam;
4401         nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL);
4402                                 /* ignore whitespace */
4403         while (**txtptr == ' ') ++*txtptr;
4404         switch (**txtptr) {     /* parse delimiter */
4405         case 'N':
4406         case 'n':
4407           *txtptr += 3;         /* bump past "NIL" */
4408           break;
4409         case '"':
4410           if (*++*txtptr == '\\') nam->delimiter = *++*txtptr;
4411           else nam->delimiter = **txtptr;
4412           *txtptr += 2;         /* bump past character and closing quote */
4413           break;
4414         default:
4415           sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s",
4416                    (char *) *txtptr);
4417           mm_notify (stream,LOCAL->tmp,WARN);
4418           stream->unhealthy = T;
4419           *txtptr = NIL;        /* stop parse */
4420           return ret;
4421         }
4422 \f
4423         while (**txtptr == ' '){/* append new parameter to tail */
4424           if (nam->param) par = par->next = mail_newbody_parameter ();
4425           else nam->param = par = mail_newbody_parameter ();
4426           if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL,
4427                                                     NIL,NIL))) {
4428             mm_notify (stream,"Missing namespace extension attribute",WARN);
4429             stream->unhealthy = T;
4430             par->attribute = cpystr ("UNKNOWN");
4431           }
4432                                 /* skip space */
4433           while (**txtptr == ' ') ++*txtptr;
4434           if (**txtptr == '(') {/* have value list?  */
4435             char *att = par->attribute;
4436             ++*txtptr;          /* yes */
4437             do {                /* parse each value */
4438               if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,
4439                                                     NIL,LONGT))) {
4440                 sprintf (LOCAL->tmp,
4441                          "Missing value for namespace attribute %.80s",att);
4442                 mm_notify (stream,LOCAL->tmp,WARN);
4443                 stream->unhealthy = T;
4444                 par->value = cpystr ("UNKNOWN");
4445               }
4446                                 /* is there another value? */
4447               if (**txtptr == ' ') par = par->next = mail_newbody_parameter ();
4448             } while (!par->value);
4449           }
4450           else {
4451             sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s",
4452                      par->attribute);
4453             mm_notify (stream,LOCAL->tmp,WARN);
4454             stream->unhealthy = T;
4455             par->value = cpystr ("UNKNOWN");
4456           }
4457         }
4458         if (**txtptr == ')') ++*txtptr;
4459         else {                  /* missing trailing paren */
4460           sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s",
4461                    (char *) *txtptr);
4462           mm_notify (stream,LOCAL->tmp,WARN);
4463           stream->unhealthy = T;
4464           return ret;
4465         }
4466       }
4467       if (**txtptr == ')') {    /* expected trailing paren? */
4468         ++*txtptr;              /* got it! */
4469         break;
4470       }
4471     default:
4472       sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr);
4473       mm_notify (stream,LOCAL->tmp,WARN);
4474       stream->unhealthy = T;
4475       *txtptr = NIL;            /* stop parse now */
4476       break;
4477     }
4478   }
4479   return ret;
4480 }
4481 \f
4482 /* Parse a thread node list
4483  * Accepts: mail stream
4484  *          current text pointer
4485  * Returns: thread node list, text pointer updated
4486  */
4487
4488 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr)
4489 {
4490   char *s;
4491   THREADNODE *ret = NIL;        /* returned tree */
4492   THREADNODE *last = NIL;       /* last branch in this tree */
4493   THREADNODE *parent = NIL;     /* parent of current node */
4494   THREADNODE *cur;              /* current node */
4495   while (**txtptr == '(') {     /* see a thread? */
4496     ++*txtptr;                  /* skip past open paren */
4497     while (**txtptr != ')') {   /* parse thread */
4498       if (**txtptr == '(') {    /* thread branch */
4499         cur = imap_parse_thread (stream,txtptr);
4500                                 /* add to parent */
4501         if (parent) parent = parent->next = cur;
4502         else {                  /* no parent, create dummy */
4503           if (last) last = last->branch = mail_newthreadnode (NIL);
4504                                 /* new tree */
4505           else ret = last = mail_newthreadnode (NIL);
4506                                 /* add to dummy parent */
4507           last->next = parent = cur;
4508         }
4509       }
4510                                 /* threaded message number */
4511       else if (isdigit (*(s = *txtptr)) &&
4512                ((cur = mail_newthreadnode (NIL))->num =
4513                 strtoul (*txtptr,(char **) txtptr,10))) {
4514         if (LOCAL->filter && !mail_elt (stream,cur->num)->searched)
4515           cur->num = NIL;       /* make dummy if filtering and not searched */
4516                                 /* add to parent */
4517         if (parent) parent = parent->next = cur;
4518                                 /* no parent, start new thread */
4519         else if (last) last = last->branch = parent = cur;
4520                                 /* create new tree */
4521         else ret = last = parent = cur;
4522       }
4523       else {                    /* anything else is a bogon */
4524         char tmp[MAILTMPLEN];
4525         sprintf (tmp,"Bogus thread member: %.80s",s);
4526         mm_notify (stream,tmp,WARN);
4527         stream->unhealthy = T;
4528         return ret;
4529       }
4530                                 /* skip past any space */
4531       if (**txtptr == ' ') ++*txtptr;
4532     }
4533     ++*txtptr;                  /* skip pase end of thread */
4534     parent = NIL;               /* close this thread */
4535   }
4536   return ret;                   /* return parsed thread */
4537 }
4538 \f
4539 /* Parse RFC822 message header
4540  * Accepts: MAIL stream
4541  *          envelope to parse into
4542  *          header as sized text
4543  *          stringlist if partial header
4544  */
4545
4546 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
4547                         STRINGLIST *stl)
4548 {
4549   ENVELOPE *nenv;
4550                                 /* parse what we can from this header */
4551   rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL,
4552                     net_host (LOCAL->netstream),stream->dtb->flags);
4553   if (*env) {                   /* need to merge this header into envelope? */
4554     if (!(*env)->newsgroups) {  /* need Newsgroups? */
4555       (*env)->newsgroups = nenv->newsgroups;
4556       nenv->newsgroups = NIL;
4557     }
4558     if (!(*env)->followup_to) { /* need Followup-To? */
4559       (*env)->followup_to = nenv->followup_to;
4560       nenv->followup_to = NIL;
4561     }
4562     if (!(*env)->references) {  /* need References? */
4563       (*env)->references = nenv->references;
4564       nenv->references = NIL;
4565     }
4566     if (!(*env)->sparep) {      /* need spare pointer? */
4567       (*env)->sparep = nenv->sparep;
4568       nenv->sparep = NIL;
4569     }
4570     mail_free_envelope (&nenv);
4571     (*env)->imapenvonly = NIL;  /* have complete envelope now */
4572   }
4573                                 /* otherwise set it to this envelope */
4574   else (*env = nenv)->incomplete = stl ? T : NIL;
4575 }
4576 \f
4577 /* IMAP parse envelope
4578  * Accepts: MAIL stream
4579  *          pointer to envelope pointer
4580  *          current text pointer
4581  *          parsed reply
4582  *
4583  * Updates text pointer
4584  */
4585
4586 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
4587                           unsigned char **txtptr,IMAPPARSEDREPLY *reply)
4588 {
4589   ENVELOPE *oenv = *env;
4590   char c = *((*txtptr)++);      /* grab first character */
4591                                 /* ignore leading spaces */
4592   while (c == ' ') c = *((*txtptr)++);
4593   switch (c) {                  /* dispatch on first character */
4594   case '(':                     /* if envelope S-expression */
4595     *env = mail_newenvelope (); /* parse the new envelope */
4596     (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4597     (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4598     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
4599     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
4600     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
4601     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
4602     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
4603     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
4604     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL,
4605                                              LONGT);
4606     (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4607     if (oenv) {                 /* need to merge old envelope? */
4608       (*env)->newsgroups = oenv->newsgroups;
4609       oenv->newsgroups = NIL;
4610       (*env)->followup_to = oenv->followup_to;
4611       oenv->followup_to = NIL;
4612       (*env)->references = oenv->references;
4613       oenv->references = NIL;
4614       mail_free_envelope(&oenv);/* free old envelope */
4615     }
4616                                 /* have IMAP envelope components only */
4617     else (*env)->imapenvonly = T;
4618     if (**txtptr != ')') {
4619       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr);
4620       mm_notify (stream,LOCAL->tmp,WARN);
4621       stream->unhealthy = T;
4622     }
4623     else ++*txtptr;             /* skip past delimiter */
4624     break;
4625   case 'N':                     /* if NIL */
4626   case 'n':
4627     ++*txtptr;                  /* bump past "I" */
4628     ++*txtptr;                  /* bump past "L" */
4629     break;
4630   default:
4631     sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr);
4632     mm_notify (stream,LOCAL->tmp,WARN);
4633     stream->unhealthy = T;
4634     break;
4635   }
4636 }
4637 \f
4638 /* IMAP parse address list
4639  * Accepts: MAIL stream
4640  *          current text pointer
4641  *          parsed reply
4642  * Returns: address list, NIL on failure
4643  *
4644  * Updates text pointer
4645  */
4646
4647 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
4648                              IMAPPARSEDREPLY *reply)
4649 {
4650   ADDRESS *adr = NIL;
4651   char c = **txtptr;            /* sniff at first character */
4652                                 /* ignore leading spaces */
4653   while (c == ' ') c = *++*txtptr;
4654   ++*txtptr;                    /* skip past open paren */
4655   switch (c) {
4656   case '(':                     /* if envelope S-expression */
4657     adr = imap_parse_address (stream,txtptr,reply);
4658     if (**txtptr != ')') {
4659       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",
4660                (char *) *txtptr);
4661       mm_notify (stream,LOCAL->tmp,WARN);
4662       stream->unhealthy = T;
4663     }
4664     else ++*txtptr;             /* skip past delimiter */
4665     break;
4666   case 'N':                     /* if NIL */
4667   case 'n':
4668     ++*txtptr;                  /* bump past "I" */
4669     ++*txtptr;                  /* bump past "L" */
4670     break;
4671   default:
4672     sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
4673     mm_notify (stream,LOCAL->tmp,WARN);
4674     stream->unhealthy = T;
4675     break;
4676   }
4677   return adr;
4678 }
4679 \f
4680 /* IMAP parse address
4681  * Accepts: MAIL stream
4682  *          current text pointer
4683  *          parsed reply
4684  * Returns: address, NIL on failure
4685  *
4686  * Updates text pointer
4687  */
4688
4689 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
4690                              IMAPPARSEDREPLY *reply)
4691 {
4692   long ingroup = 0;
4693   ADDRESS *adr = NIL;
4694   ADDRESS *ret = NIL;
4695   ADDRESS *prev = NIL;
4696   char c = **txtptr;            /* sniff at first address character */
4697   switch (c) {
4698   case '(':                     /* if envelope S-expression */
4699     while (c == '(') {          /* recursion dies on small stack machines */
4700       ++*txtptr;                /* skip past open paren */
4701       if (adr) prev = adr;      /* note previous if any */
4702       adr = mail_newaddr ();    /* instantiate address and parse its fields */
4703       adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4704       adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4705       adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4706       adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4707       if (**txtptr != ')') {    /* handle trailing paren */
4708         sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr);
4709         mm_notify (stream,LOCAL->tmp,WARN);
4710         stream->unhealthy = T;
4711       }
4712       else ++*txtptr;           /* skip past close paren */
4713       c = **txtptr;             /* set up for while test */
4714                                 /* ignore leading spaces in front of next */
4715       while (c == ' ') c = *++*txtptr;
4716 \f
4717       if (!adr->mailbox) {      /* end of group? */
4718                                 /* decrement group if all looks well */
4719         if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup;
4720         else {
4721           if (ingroup) {        /* in a group? */
4722             sprintf (LOCAL->tmp,/* yes, must be bad syntax */
4723                      "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
4724                      adr->personal ? adr->personal : "",
4725                      adr->adl ? adr->adl : "",
4726                      adr->host ? adr->host : "");
4727             mm_notify (stream,LOCAL->tmp,WARN);
4728           }
4729           else mm_notify (stream,"End of group encountered when not in group",
4730                           WARN);
4731           stream->unhealthy = T;
4732           mail_free_address (&adr);
4733           adr = prev;
4734           prev = NIL;
4735         }
4736       }
4737       else if (!adr->host) {    /* start of group? */
4738                 // Exception handling is removed. 
4739                 /* 
4740         if (adr->personal || adr->adl) {
4741           sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s",
4742                    adr->personal ? adr->personal : "",
4743                    adr->adl ? adr->adl : "");
4744           mm_notify (stream,LOCAL->tmp,WARN);
4745           stream->unhealthy = T;
4746           mail_free_address (&adr);
4747           adr = prev;
4748           prev = NIL;
4749         }
4750         else*/ ++ingroup;                // in a group now 
4751       }
4752       if (adr) {                /* good address */
4753         if (!ret) ret = adr;    /* if first time note first adr */
4754                                 /* if previous link new block to it */
4755         if (prev) prev->next = adr;
4756                                 /* flush bogus personal name */
4757         if (LOCAL->loser && adr->personal && strchr (adr->personal,'@'))
4758           fs_give ((void **) &adr->personal);
4759       }
4760     }
4761     break;
4762   case 'N':                     /* if NIL */
4763   case 'n':
4764     *txtptr += 3;               /* bump past NIL */
4765     break;
4766   default:
4767     sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
4768     mm_notify (stream,LOCAL->tmp,WARN);
4769     stream->unhealthy = T;
4770     break;
4771   }
4772   return ret;
4773 }
4774 \f
4775 /* IMAP parse flags
4776  * Accepts: current message cache
4777  *          current text pointer
4778  *
4779  * Updates text pointer
4780  */
4781
4782 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
4783                        unsigned char **txtptr)
4784 {
4785   char *flag;
4786   char c = '\0';
4787   struct {                      /* old flags */
4788     unsigned int valid : 1;
4789     unsigned int seen : 1;
4790     unsigned int deleted : 1;
4791     unsigned int flagged : 1;
4792     unsigned int answered : 1;
4793     unsigned int draft : 1;
4794     unsigned long user_flags;
4795   } old;
4796   old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted;
4797   old.flagged = elt->flagged; old.answered = elt->answered;
4798   old.draft = elt->draft; old.user_flags = elt->user_flags;
4799   elt->valid = T;               /* mark have valid flags now */
4800   elt->user_flags = NIL;        /* zap old flag values */
4801   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->draft =
4802     elt->recent = NIL;
4803   while (c != ')') {            /* parse list of flags */
4804                                 /* point at a flag */
4805     while (*(flag = ++*txtptr) == ' ');
4806                                 /* scan for end of flag */
4807     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
4808     c = **txtptr;               /* save delimiter */
4809     **txtptr = '\0';            /* tie off flag */
4810     if (!*flag) break;          /* null flag */
4811                                 /* if starts with \ must be sys flag */
4812     else if (*flag == '\\') {
4813       if (!compare_cstring (flag,"\\Seen")) elt->seen = T;
4814       else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T;
4815       else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T;
4816       else if (!compare_cstring (flag,"\\Answered")) elt->answered = T;
4817       else if (!compare_cstring (flag,"\\Recent")) elt->recent = T;
4818       else if (!compare_cstring (flag,"\\Draft")) elt->draft = T;
4819     }
4820                                 /* otherwise user flag */
4821     else elt->user_flags |= imap_parse_user_flag (stream,flag);
4822   }
4823   ++*txtptr;                    /* bump past delimiter */
4824   if (!old.valid || (old.seen != elt->seen) ||
4825       (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
4826       (old.answered != elt->answered) || (old.draft != elt->draft) ||
4827       (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno);
4828 }
4829
4830
4831 /* IMAP parse user flag
4832  * Accepts: MAIL stream
4833  *          flag name
4834  * Returns: flag bit position
4835  */
4836
4837 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
4838 {
4839   long i;
4840                                 /* sniff through all user flags */
4841   for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
4842     if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i);
4843   return (unsigned long) 0;     /* not found */
4844 }
4845 \f
4846 /* IMAP parse atom-string
4847  * Accepts: MAIL stream
4848  *          current text pointer
4849  *          parsed reply
4850  *          returned string length
4851  * Returns: string
4852  *
4853  * Updates text pointer
4854  */
4855
4856 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
4857                                    IMAPPARSEDREPLY *reply,unsigned long *len)
4858 {
4859   unsigned long i;
4860   unsigned char c,*s,*ret;
4861                                 /* ignore leading spaces */
4862   for (c = **txtptr; c == ' '; c = *++*txtptr);
4863   switch (c) {
4864   case '"':                     /* quoted string? */
4865   case '{':                     /* literal? */
4866     ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL);
4867     break;
4868   default:                      /* must be atom */
4869     for (c = *(s = *txtptr);    /* find end of atom */
4870          c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') &&
4871            (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80);
4872          c = *++*txtptr);
4873     if (i = *txtptr - s) {      /* atom ends at atom_special */
4874       if (len) *len = i;        /* return length of atom */
4875       ret = strncpy ((char *) fs_get (i + 1),s,i);
4876       ret[i] = '\0';            /* tie off string */
4877     }
4878     else {                      /* no atom found */
4879       sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr);
4880       mm_notify (stream,LOCAL->tmp,WARN);
4881       stream->unhealthy = T;
4882       if (len) *len = 0;
4883       ret = NIL;
4884     }
4885     break;
4886   }
4887   return ret;
4888 }
4889 \f
4890 /* IMAP parse string
4891  * Accepts: MAIL stream
4892  *          current text pointer
4893  *          parsed reply
4894  *          mailgets data
4895  *          returned string length
4896  *          filter newline flag
4897  * Returns: string
4898  *
4899  * Updates text pointer
4900  */
4901
4902 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
4903                                   IMAPPARSEDREPLY *reply,GETS_DATA *md,
4904                                   unsigned long *len,long flags)
4905 {
4906   char *st;
4907   char *string = NIL;
4908   unsigned long i,j,k;
4909   int bogon = NIL;
4910   unsigned char c = **txtptr;   /* sniff at first character */
4911   mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
4912   readprogress_t rp =
4913     (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
4914                                 /* ignore leading spaces */
4915   while (c == ' ') c = *++*txtptr;
4916   st = ++*txtptr;               /* remember start of string */
4917   switch (c) {
4918   case '"':                     /* if quoted string */
4919     i = 0;                      /* initial byte count */
4920                                 /* search for end of string */
4921     for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) {
4922                                 /* backslash quotes next character */
4923       if (c == '\\') c = *++*txtptr;
4924                                 /* CHAR8 not permitted in quoted string */
4925       if (!bogon && (bogon = (c & 0x80))) {
4926         sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x",
4927                  (unsigned int) c);
4928         mm_notify (stream,LOCAL->tmp,WARN);
4929         stream->unhealthy = T;
4930       }
4931       else if (!c) {            /* NUL not permitted either */
4932         mm_notify (stream,"Unterminated quoted string",WARN);
4933         stream->unhealthy = T;
4934         if (len) *len = 0;      /* punt, since may be at end of string */
4935         return NIL;
4936       }
4937     }
4938     ++*txtptr;                  /* bump past delimiter */
4939     string = (char *) fs_get ((size_t) i + 1);
4940     for (j = 0; j < i; j++) {   /* copy the string */
4941       if (*st == '\\') ++st;    /* quoted character */
4942       string[j] = *st++;
4943     }
4944     string[j] = '\0';           /* tie off string */
4945     if (len) *len = i;          /* set return value too */
4946     if (md && mg) {             /* have special routine to slurp string? */
4947       STRING bs;
4948       if (md->first) {          /* partial fetch? */
4949         md->first--;            /* restore origin octet */
4950         md->last = i;           /* number of octets that we got */
4951       }
4952       INIT (&bs,mail_string,string,i);
4953       (*mg) (mail_read,&bs,i,md);
4954     }
4955     break;
4956 \f
4957   case 'N':                     /* if NIL */
4958   case 'n':
4959     ++*txtptr;                  /* bump past "I" */
4960     ++*txtptr;                  /* bump past "L" */
4961     if (len) *len = 0;
4962     break;
4963   case '{':                     /* if literal string */
4964                                 /* get size of string */ 
4965     if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) {
4966       sprintf (LOCAL->tmp,"Absurd server literal length %lu",i);
4967       mm_notify (stream,LOCAL->tmp,WARN);
4968       stream->unhealthy = T;    /* read and discard */
4969       do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
4970                         LOCAL->tmp);
4971       while (i -= j);
4972     }
4973     if (len) *len = i;          /* set return value */
4974     if (md && mg) {             /* have special routine to slurp string? */
4975       if (md->first) {          /* partial fetch? */
4976         md->first--;            /* restore origin octet */
4977         md->last = i;           /* number of octets that we got */
4978       }
4979       else md->flags |= MG_COPY;/* otherwise flag need to copy */
4980       string = (*mg) (net_getbuffer,LOCAL->netstream,i,md);
4981     }
4982     else {                      /* must slurp into free storage */
4983       string = (char *) fs_get ((size_t) i + 1);
4984       *string = '\0';           /* init in case getbuffer fails */
4985                                 /* get the literal */
4986       if (rp) for (k = 0; j = min ((long) MAILTMPLEN,(long) i); i -= j) {
4987         net_getbuffer (LOCAL->netstream,j,string + k);
4988         (*rp) (md,k += j);
4989       }
4990       else net_getbuffer (LOCAL->netstream,i,string);
4991     }
4992     fs_give ((void **) &reply->line);
4993     if (flags && string)        /* need to filter newlines? */
4994       for (st = string; st = strpbrk (st,"\015\012\011"); *st++ = ' ');
4995                                 /* get new reply text line */
4996     if (!(reply->line = net_getline (LOCAL->netstream)))
4997       reply->line = cpystr ("");
4998     if (stream->debug) mm_dlog (reply->line);
4999     *txtptr = reply->line;      /* set text pointer to point at it */
5000     break;
5001   default:
5002     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,(char *) *txtptr);
5003     mm_notify (stream,LOCAL->tmp,WARN);
5004     stream->unhealthy = T;
5005     if (len) *len = 0;
5006     break;
5007   }
5008   return (unsigned char *) string;
5009 }
5010 \f
5011 /* Register text in IMAP cache
5012  * Accepts: MAIL stream
5013  *          message number
5014  *          IMAP segment specifier
5015  *          header string list (if a HEADER section specifier)
5016  *          sized text to register
5017  * Returns: non-zero if cache non-empty
5018  */
5019
5020 long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg,
5021                  STRINGLIST *stl,SIZEDTEXT *text)
5022 {
5023   char *t,tmp[MAILTMPLEN];
5024   unsigned long i;
5025   BODY *b;
5026   SIZEDTEXT *ret;
5027   STRINGLIST *stc;
5028   MESSAGECACHE *elt = mail_elt (stream,msgno);
5029                                 /* top-level header never does mailgets */
5030   if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") ||
5031       !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) {
5032     ret = &elt->private.msg.header.text;
5033     if (text) {                 /* don't do this if no text */
5034       if (ret->data) fs_give ((void **) &ret->data);
5035       mail_free_stringlist (&elt->private.msg.lines);
5036       elt->private.msg.lines = stl;
5037                                 /* prevent cache reuse of .NOT */
5038       if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.'))
5039         for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5040       if (stream->scache) {     /* short caching puts it in the stream */
5041         if (stream->msgno != msgno) {
5042                                 /* flush old stuff */
5043           mail_free_envelope (&stream->env);
5044           mail_free_body (&stream->body);
5045           stream->msgno = msgno;
5046         }
5047         imap_parse_header (stream,&stream->env,text,stl);
5048       }
5049                                 /* regular caching */
5050       else imap_parse_header (stream,&elt->private.msg.env,text,stl);
5051     }
5052   }
5053                                 /* top level text */
5054   else if (!strcmp (seg,"TEXT")) {
5055     ret = &elt->private.msg.text.text;
5056     if (text && ret->data) fs_give ((void **) &ret->data);
5057   }
5058   else if (!*seg) {             /* full message */
5059     ret = &elt->private.msg.full.text;
5060     if (text && ret->data) fs_give ((void **) &ret->data);
5061   }
5062 \f
5063   else {                        /* nested, find non-contents specifier */
5064     for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++);
5065     if (*t) *t++ = '\0';        /* tie off section from data specifier */
5066     if (!(b = mail_body (stream,msgno,seg))) {
5067       sprintf (tmp,"Unknown section number: %.80s",seg);
5068       mm_notify (stream,tmp,WARN);
5069       stream->unhealthy = T;
5070       return NIL;
5071     }
5072     if (*t) {                   /* if a non-numberic subpart */
5073       if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) &&
5074           (!strcmp (t,"HEADER") || !strcmp (t,"0") ||
5075            !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) {
5076         ret = &b->nested.msg->header.text;
5077         if (text) {
5078           if (ret->data) fs_give ((void **) &ret->data);
5079           mail_free_stringlist (&b->nested.msg->lines);
5080           b->nested.msg->lines = stl;
5081                                 /* prevent cache reuse of .NOT */
5082           if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.'))
5083             for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5084           imap_parse_header (stream,&b->nested.msg->env,text,stl);
5085         }
5086       }
5087       else if (i && !strcmp (t,"TEXT")) {
5088         ret = &b->nested.msg->text.text;
5089         if (text && ret->data) fs_give ((void **) &ret->data);
5090       }
5091                                 /* otherwise it must be MIME */
5092       else if (!strcmp (t,"MIME")) {
5093         ret = &b->mime.text;
5094         if (text && ret->data) fs_give ((void **) &ret->data);
5095       }
5096       else {
5097         sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t);
5098         mm_notify (stream,tmp,WARN);
5099         stream->unhealthy = T;
5100         return NIL;
5101       }
5102     }
5103     else {                      /* ordinary contents */
5104       ret = &b->contents.text;
5105       if (text && ret->data) fs_give ((void **) &ret->data);
5106     }
5107   }
5108   if (text) {                   /* update cache if requested */
5109     ret->data = text->data;
5110     ret->size = text->size;
5111   }
5112   return ret->data ? LONGT : NIL;
5113 }
5114 \f
5115 /* IMAP parse body structure
5116  * Accepts: MAIL stream
5117  *          body structure to write into
5118  *          current text pointer
5119  *          parsed reply
5120  *
5121  * Updates text pointer
5122  */
5123
5124 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
5125                                 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5126 {
5127   int i;
5128   char *s;
5129   PART *part = NIL;
5130   char c = *((*txtptr)++);      /* grab first character */
5131                                 /* ignore leading spaces */
5132   while (c == ' ') c = *((*txtptr)++);
5133   switch (c) {                  /* dispatch on first character */
5134   case '(':                     /* body structure list */
5135     if (**txtptr == '(') {      /* multipart body? */
5136       body->type= TYPEMULTIPART;/* yes, set its type */
5137       do {                      /* instantiate new body part */
5138         if (part) part = part->next = mail_newbody_part ();
5139         else body->nested.part = part = mail_newbody_part ();
5140                                 /* parse it */
5141         imap_parse_body_structure (stream,&part->body,txtptr,reply);
5142       } while (**txtptr == '(');/* for each body part */
5143       if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT))
5144         ucase (body->subtype);
5145       else {
5146         mm_notify (stream,"Missing multipart subtype",WARN);
5147         stream->unhealthy = T;
5148         body->subtype = cpystr (rfc822_default_subtype (body->type));
5149       }
5150       if (**txtptr == ' ')      /* multipart parameters */
5151         body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5152       if (**txtptr == ' ') {    /* disposition */
5153         imap_parse_disposition (stream,body,txtptr,reply);
5154         if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5155       }
5156       if (**txtptr == ' ') {    /* language */
5157         body->language = imap_parse_language (stream,txtptr,reply);
5158         if (LOCAL->cap.extlevel < BODYEXTLANG)
5159           LOCAL->cap.extlevel = BODYEXTLANG;
5160       }
5161       if (**txtptr == ' ') {    /* location */
5162         body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5163         if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5164       }
5165       while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5166       if (**txtptr != ')') {    /* validate ending */
5167         sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",
5168                  (char *) *txtptr);
5169         mm_notify (stream,LOCAL->tmp,WARN);
5170         stream->unhealthy = T;
5171       }
5172       else ++*txtptr;           /* skip past delimiter */
5173     }
5174 \f
5175     else {                      /* not multipart, parse type name */
5176       if (**txtptr == ')') {    /* empty body? */
5177         ++*txtptr;              /* bump past it */
5178         break;                  /* and punt */
5179       }
5180       body->type = TYPEOTHER;   /* assume unknown type */
5181       body->encoding = ENCOTHER;/* and unknown encoding */
5182                                 /* parse type */
5183       if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) {
5184         ucase (s);              /* application always gets uppercase form */
5185         for (i = 0;             /* look in existing table */
5186              (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++);
5187         if (i <= TYPEMAX) {     /* only if found a slot */
5188           body->type = i;       /* set body type */
5189           if (body_types[i]) fs_give ((void **) &s);
5190           else body_types[i]=s; /* assign empty slot */
5191         }
5192       }
5193       if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT))
5194         ucase (body->subtype);  /* parse subtype */
5195       else {
5196         mm_notify (stream,"Missing body subtype",WARN);
5197         stream->unhealthy = T;
5198         body->subtype = cpystr (rfc822_default_subtype (body->type));
5199       }
5200       body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5201       body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5202       body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5203                                              LONGT);
5204       if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) {
5205         ucase (s);              /* application always gets uppercase form */
5206         for (i = 0;             /* search for body encoding */
5207              (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]);
5208              i++);
5209         if (i > ENCMAX) body->encoding = ENCOTHER;
5210         else {                  /* only if found a slot */
5211           body->encoding = i;   /* set body encoding */
5212           if (body_encodings[i]) fs_give ((void **) &s);
5213                                 /* assign empty slot */
5214           else body_encodings[i] = s;
5215         }
5216       }
5217 \f                               /* parse size of contents in bytes */
5218       body->size.bytes = strtoul (*txtptr,(char **) txtptr,10);
5219       switch (body->type) {     /* possible extra stuff */
5220       case TYPEMESSAGE:         /* message envelope and body */
5221                                 /* non MESSAGE/RFC822 is basic type */
5222         if (strcmp (body->subtype,"RFC822")) break;
5223         {                       /* make certain server sends an envelope */
5224           ENVELOPE *env = NIL;
5225           imap_parse_envelope (stream,&env,txtptr,reply);
5226           if (!env) {
5227             mm_notify (stream,"Missing body message envelope",WARN);
5228             stream->unhealthy = T;
5229             body->subtype = cpystr ("RFC822_MISSING_ENVELOPE");
5230             break;
5231           }
5232           (body->nested.msg = mail_newmsg ())->env = env;
5233         }
5234         body->nested.msg->body = mail_newbody ();
5235         imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply);
5236                                 /* drop into text case */
5237       case TYPETEXT:            /* size in lines */
5238         body->size.lines = strtoul (*txtptr,(char **) txtptr,10);
5239         break;
5240       default:                  /* otherwise nothing special */
5241         break;
5242       }
5243 \f
5244       if (**txtptr == ' ') {    /* extension data - md5 */
5245         body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5246         if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5;
5247       }
5248       if (**txtptr == ' ') {    /* disposition */
5249         imap_parse_disposition (stream,body,txtptr,reply);
5250         if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5251       }
5252       if (**txtptr == ' ') {    /* language */
5253         body->language = imap_parse_language (stream,txtptr,reply);
5254         if (LOCAL->cap.extlevel < BODYEXTLANG)
5255           LOCAL->cap.extlevel = BODYEXTLANG;
5256       }
5257       if (**txtptr == ' ') {    /* location */
5258         body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5259         if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5260       }
5261       while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5262       if (**txtptr != ')') {    /* validate ending */
5263         sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",
5264                  (char *) *txtptr);
5265         mm_notify (stream,LOCAL->tmp,WARN);
5266         stream->unhealthy = T;
5267       }
5268       else ++*txtptr;           /* skip past delimiter */
5269     }
5270     break;
5271   case 'N':                     /* if NIL */
5272   case 'n':
5273     ++*txtptr;                  /* bump past "I" */
5274     ++*txtptr;                  /* bump past "L" */
5275     break;
5276   default:                      /* otherwise quite bogus */
5277     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",(char *) *txtptr);
5278     mm_notify (stream,LOCAL->tmp,WARN);
5279     stream->unhealthy = T;
5280     break;
5281   }
5282 }
5283 \f
5284 /* IMAP parse body parameter
5285  * Accepts: MAIL stream
5286  *          current text pointer
5287  *          parsed reply
5288  * Returns: body parameter
5289  * Updates text pointer
5290  */
5291
5292 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
5293                                       unsigned char **txtptr,
5294                                       IMAPPARSEDREPLY *reply)
5295 {
5296   PARAMETER *ret = NIL;
5297   PARAMETER *par = NIL;
5298   char c,*s;
5299                                 /* ignore leading spaces */
5300   while ((c = *(*txtptr)++) == ' ');
5301                                 /* parse parameter list */
5302   if (c == '(') while (c != ')') {
5303                                 /* append new parameter to tail */
5304     if (ret) par = par->next = mail_newbody_parameter ();
5305     else ret = par = mail_newbody_parameter ();
5306     if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL,
5307                                            LONGT))) {
5308       mm_notify (stream,"Missing parameter attribute",WARN);
5309       stream->unhealthy = T;
5310       par->attribute = cpystr ("UNKNOWN");
5311     }
5312     if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){
5313       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
5314       mm_notify (stream,LOCAL->tmp,WARN);
5315       stream->unhealthy = T;
5316       par->value = cpystr ("UNKNOWN");
5317     }
5318     switch (c = **txtptr) {     /* see what comes after */
5319     case ' ':                   /* flush whitespace */
5320       while ((c = *++*txtptr) == ' ');
5321       break;
5322     case ')':                   /* end of attribute/value pairs */
5323       ++*txtptr;                /* skip past closing paren */
5324       break;
5325     default:
5326       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
5327       mm_notify (stream,LOCAL->tmp,WARN);
5328       stream->unhealthy = T;
5329       break;
5330     }
5331   }
5332                                 /* empty parameter, must be NIL */
5333   else if (((c == 'N') || (c == 'n')) &&
5334            ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
5335            ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
5336   else {
5337     sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,
5338              (char *) (*txtptr) - 1);
5339     mm_notify (stream,LOCAL->tmp,WARN);
5340     stream->unhealthy = T;
5341   }
5342   return ret;
5343 }
5344 \f
5345 /* IMAP parse body disposition
5346  * Accepts: MAIL stream
5347  *          body structure to write into
5348  *          current text pointer
5349  *          parsed reply
5350  */
5351
5352 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
5353                              unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5354 {
5355   switch (*++*txtptr) {
5356   case '(':
5357     ++*txtptr;                  /* skip open paren */
5358     body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5359                                                 LONGT);
5360     body->disposition.parameter =
5361       imap_parse_body_parameter (stream,txtptr,reply);
5362     if (**txtptr != ')') {      /* validate ending */
5363       sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",
5364                (char *) *txtptr);
5365       mm_notify (stream,LOCAL->tmp,WARN);
5366       stream->unhealthy = T;
5367     }
5368     else ++*txtptr;             /* skip past delimiter */
5369     break;
5370   case 'N':                     /* if NIL */
5371   case 'n':
5372     ++*txtptr;                  /* bump past "N" */
5373     ++*txtptr;                  /* bump past "I" */
5374     ++*txtptr;                  /* bump past "L" */
5375     break;
5376   default:
5377     sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",(char *) *txtptr);
5378     mm_notify (stream,LOCAL->tmp,WARN);
5379     stream->unhealthy = T;
5380                                 /* try to skip to next space */
5381     while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
5382     break;
5383   }
5384 }
5385 \f
5386 /* IMAP parse body language
5387  * Accepts: MAIL stream
5388  *          current text pointer
5389  *          parsed reply
5390  * Returns: string list or NIL if empty or error
5391  */
5392
5393 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
5394                                  IMAPPARSEDREPLY *reply)
5395 {
5396   unsigned long i;
5397   char *s;
5398   STRINGLIST *ret = NIL;
5399                                 /* language is a list */
5400   if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
5401   else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i,LONGT)) {
5402     (ret = mail_newstringlist ())->text.data = (unsigned char *) s;
5403     ret->text.size = i;
5404   }
5405   return ret;
5406 }
5407 \f
5408 /* IMAP parse string list
5409  * Accepts: MAIL stream
5410  *          current text pointer
5411  *          parsed reply
5412  * Returns: string list or NIL if empty or error
5413  */
5414
5415 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
5416                                    IMAPPARSEDREPLY *reply)
5417 {
5418   STRINGLIST *stl = NIL;
5419   STRINGLIST *stc = NIL;
5420   unsigned char *t = *txtptr;
5421                                 /* parse the list */
5422   if (*t++ == '(') while (*t != ')') {
5423     if (stl) stc = stc->next = mail_newstringlist ();
5424     else stc = stl = mail_newstringlist ();
5425                                 /* parse astring */
5426     if (!(stc->text.data =
5427           imap_parse_astring (stream,&t,reply,&stc->text.size))) {
5428       sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t);
5429       mm_notify (stream,LOCAL->tmp,WARN);
5430       stream->unhealthy = T;
5431       mail_free_stringlist (&stl);
5432       break;
5433     }
5434     else if (*t == ' ') ++t;    /* another token follows */
5435   }
5436   if (stl) *txtptr = ++t;       /* update return string */
5437   return stl;
5438 }
5439 \f
5440 /* IMAP parse unknown body extension data
5441  * Accepts: MAIL stream
5442  *          current text pointer
5443  *          parsed reply
5444  *
5445  * Updates text pointer
5446  */
5447
5448 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
5449                            IMAPPARSEDREPLY *reply)
5450 {
5451   unsigned long i,j;
5452   switch (*++*txtptr) {         /* action depends upon first character */
5453   case '(':
5454     while (**txtptr != ')') imap_parse_extension (stream,txtptr,reply);
5455     ++*txtptr;                  /* bump past closing parenthesis */
5456     break;
5457   case '"':                     /* if quoted string */
5458     while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr;
5459     ++*txtptr;                  /* bump past closing quote */
5460     break;
5461   case 'N':                     /* if NIL */
5462   case 'n':
5463     ++*txtptr;                  /* bump past "N" */
5464     ++*txtptr;                  /* bump past "I" */
5465     ++*txtptr;                  /* bump past "L" */
5466     break;
5467   case '{':                     /* get size of literal */
5468     ++*txtptr;                  /* bump past open squiggle */
5469     if (i = strtoul (*txtptr,(char **) txtptr,10)) do
5470       net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
5471                      LOCAL->tmp);
5472     while (i -= j);
5473                                 /* get new reply text line */
5474     if (!(reply->line = net_getline (LOCAL->netstream)))
5475       reply->line = cpystr ("");
5476     if (stream->debug) mm_dlog (reply->line);
5477     *txtptr = reply->line;      /* set text pointer to point at it */
5478     break;
5479   case '0': case '1': case '2': case '3': case '4':
5480   case '5': case '6': case '7': case '8': case '9':
5481     strtoul (*txtptr,(char **) txtptr,10);
5482     break;
5483   default:
5484     sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr);
5485     mm_notify (stream,LOCAL->tmp,WARN);
5486     stream->unhealthy = T;
5487                                 /* try to skip to next space */
5488     while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
5489     break;
5490   }
5491 }
5492 \f
5493 /* IMAP parse capabilities
5494  * Accepts: MAIL stream
5495  *          capability list
5496  */
5497
5498 void imap_parse_capabilities (MAILSTREAM *stream,char *t)
5499 {
5500   char *s,*r;
5501   unsigned long i;
5502   THREADER *thr,*th;
5503   if (!LOCAL->gotcapability) {  /* need to save previous capabilities? */
5504                                 /* no, flush threaders */
5505     if (thr = LOCAL->cap.threader) while (th = thr) {
5506       fs_give ((void **) &th->name);
5507       thr = th->next;
5508       fs_give ((void **) &th);
5509     }
5510                                 /* zap capabilities */
5511     memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
5512     LOCAL->gotcapability = T;   /* flag that capabilities arrived */
5513   }
5514   for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) {
5515     if (!compare_cstring (t,"IMAP4"))
5516       LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5517     else if (!compare_cstring (t,"IMAP4rev1"))
5518       LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5519     else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T;
5520     else if (!compare_cstring (t,"IMAP2bis"))
5521       LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5522     else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T;
5523     else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T;
5524     else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T;
5525     else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T;
5526     else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T;
5527     else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T;
5528     else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T;
5529     else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T;
5530     else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T;
5531     else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T;
5532     else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T;
5533     else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T;
5534     else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T;
5535     else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T;
5536     else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T;
5537     else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T;
5538     else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T;
5539     else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T;
5540     else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T;
5541     else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T;
5542     else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T;
5543     else if (((t[0] == 'S') || (t[0] == 's')) &&
5544              ((t[1] == 'O') || (t[1] == 'o')) &&
5545              ((t[2] == 'R') || (t[2] == 'r')) &&
5546              ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T;
5547                                 /* capability with value? */
5548     else if (s = strchr (t,'=')) {
5549       *s++ = '\0';              /* separate token from value */
5550       if (!compare_cstring (t,"THREAD") && !LOCAL->loser) {
5551         THREADER *thread = (THREADER *) fs_get (sizeof (THREADER));
5552         thread->name = cpystr (s);
5553         thread->dispatch = NIL;
5554         thread->next = LOCAL->cap.threader;
5555         LOCAL->cap.threader = thread;
5556       }
5557       else if (!compare_cstring (t,"AUTH")) {
5558         if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) &&
5559             (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i);
5560         else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T;
5561       }
5562     }
5563                                 /* ignore other capabilities */
5564   }
5565                                 /* disable LOGIN if PLAIN also advertised */
5566   if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) &&
5567       (LOCAL->cap.auth & (1 << i)) &&
5568       (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS))
5569     LOCAL->cap.auth &= ~(1 << i);
5570 }
5571 \f
5572 /* IMAP load cache
5573  * Accepts: MAIL stream
5574  *          sequence
5575  *          flags
5576  * Returns: parsed reply from fetch
5577  */
5578
5579 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags)
5580 {
5581   int i = 2;
5582   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ?
5583     "UID FETCH" : "FETCH";
5584   IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl;
5585   if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
5586                                                      flags & FT_UID);
5587   args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence;
5588   args[1] = &aarg; aarg.type = ATOM;
5589 #ifdef __HEADER_OPTIMIZATION__  
5590   aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
5591   /* g.shyamakshi@samsung.com - Check FT_SELECTEDHDRS flag to fetch only selected header fields */
5592   ahhr.type = ATOM;
5593   if( flags & FT_SELECTEDHDRS ) 
5594           ahhr.text = (void *) "BODY.PEEK[HEADER.FIELDS (Date subject from to cc bcc message-id Return-path X-Priority x-msmail-priority Disposition-Notification-To)]";
5595   else
5596           ahhr.text = (void *) "BODY.PEEK[HEADER]";
5597   axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
5598   ahtr.type = ATOM; ahtr.text = (void *) "INTERNALDATE";
5599   /* Commented out - Only fetch header - no bodystructure */
5600   abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
5601   atrl.type = ATOM; atrl.text = (void *) "RFC822.SIZE FLAGS)";
5602 #else
5603   aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
5604   ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel];
5605   axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
5606   ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer;
5607   abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
5608   atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
5609 #endif
5610   if (LEVELIMAP4 (stream)) {    /* include UID if IMAP4 or IMAP4rev1 */
5611     aarg.text = (void *) "(UID";
5612     if (flags & FT_NEEDENV) {   /* if need envelopes */
5613       args[i++] = &aenv;        /* include envelope */
5614                                 /* extra header poop if IMAP4rev1 */
5615       if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
5616         args[i++] = &ahhr;      /* header header */
5617         if (axtr.text) args[i++] = &axtr;
5618         args[i++] = &ahtr;      /* header trailer */
5619       }
5620                                 /* fetch body if requested */
5621       if (flags & FT_NEEDBODY) args[i++] = &abdy;
5622     }
5623     args[i++] = &atrl;          /* fetch trailer */
5624   }
5625                                 /* easy if IMAP2 */
5626   else aarg.text = (void *) (flags & FT_NEEDENV) ?
5627     ((flags & FT_NEEDBODY) ?
5628      "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
5629      "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
5630   args[i] = NIL;                /* tie off command */
5631   return imap_send (stream,cmd,args);
5632 }
5633 \f
5634 /* Reform sequence for losing server that doesn't handle ranges right
5635  * Accepts: MAIL stream
5636  *          sequence
5637  *          non-zero if UID
5638  * Returns: sequence
5639  */
5640
5641 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags)
5642 {
5643   unsigned long i,j,star;
5644   char *s,*t,*tl,*rs;
5645                                 /* can't win if empty */
5646   if (!stream->nmsgs) return sequence;
5647                                 /* get highest possible range value */
5648   star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs;
5649                                 /* flush old reformed sequence */
5650   if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
5651   rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence));
5652   for (s = sequence; t = strpbrk (s,",:"); ) switch (*t++) {
5653   case ',':                     /* single message */
5654     strncpy (rs,s,i = t - s);   /* copy string up to that point */
5655     rs += i;                    /* advance destination pointer */
5656     s += i;                     /* and source */
5657     break;
5658   case ':':                     /* message range */
5659     i = (*s == '*') ? star : strtoul (s,NIL,10);
5660     if (*t == '*') {            /* range ends with star */
5661       j = star;
5662       tl = t+1;
5663     }
5664     else {                      /* numeric range end */
5665       j = strtoul (t,(char **) &tl,10);
5666       if (!tl) tl = t + strlen (t);
5667     }
5668     if (i <= j) {               /* if first less than second */
5669       if (*tl) tl++;            /* skip past end of range if present */
5670       strncpy (rs,s,i = tl - s);/* copy string up to that point */
5671       rs += i;                  /* advance destination and source pointers */
5672       s += i;
5673     }
5674     else {                      /* here's the workaround for losing servers */
5675       strncpy (rs,t,i = tl - t);/* swap the order */
5676       rs[i] = ':';              /* delimit */
5677       strncpy (rs+i+1,s,j = (t-1) - s);
5678       rs += i + 1 + j;          /* advance destination pointer */
5679       if (*tl) *rs++ = *tl++;   /* write trailing delimiter if present */
5680       s = tl;                   /* advance source pointer */
5681     }
5682   }
5683   if (*s) strcpy (rs,s);        /* write remainder of sequence */
5684   else *rs = '\0';              /* tie off string */
5685   return LOCAL->reform;
5686 }
5687 \f
5688 /* IMAP return host name
5689  * Accepts: MAIL stream
5690  * Returns: host name
5691  */
5692
5693 char *imap_host (MAILSTREAM *stream)
5694 {
5695   if (stream->dtb != &imapdriver)
5696     fatal ("imap_host called on non-IMAP stream!");
5697                                 /* return host name on stream if open */
5698   return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) :
5699     ".NO-IMAP-CONNECTION.";
5700 }
5701
5702
5703 /* IMAP return IMAP capability structure
5704  * Accepts: MAIL stream
5705  * Returns: IMAP capability structure
5706  */
5707
5708 IMAPCAP *imap_cap (MAILSTREAM *stream)
5709 {
5710   if (stream->dtb != &imapdriver)
5711     fatal ("imap_cap called on non-IMAP stream!");
5712   return &LOCAL->cap;           /* return capability structure */
5713 }