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