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