imapext-2007

view src/c-client/imap4r1.c @ 0:ada5e610ab86

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

UW-IMAP'd extensions by yuuji