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