* Security Bug: Alpine can be configured to start a secure connection using /tls
[alpine.git] / imap / src / c-client / imap4r1.c
blob4991f85e02907ac3958c56ec0a4a1274d74a024a
1 /*
2 * Copyright 2016-2020 Eduardo Chappa
4 * Last Edited: Jun 18, 2020 Eduardo Chappa <alpine.chappa@yandex.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 tls1_3 : 1; /* using TLSv1_3 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 *base,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 long imap_search_x_gm_ext1 (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
195 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
196 SORTPGM *pgm,long flags);
197 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
198 SEARCHPGM *spg,long flags);
199 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
200 SEARCHPGM *spg,long flags);
201 long imap_ping (MAILSTREAM *stream);
202 void imap_check (MAILSTREAM *stream);
203 long imap_expunge (MAILSTREAM *stream,char *sequence,long options);
204 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
205 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
206 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
207 char *flags,char *date,STRING *message,
208 APPENDDATA *map,long options);
209 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
210 char *flags,char *date,STRING *message);
212 void imap_gc (MAILSTREAM *stream,long gcflags);
213 void imap_gc_body (BODY *body);
214 void imap_capability (MAILSTREAM *stream);
215 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]);
217 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]);
218 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s);
219 long imap_soutr (MAILSTREAM *stream,char *string);
220 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
221 SIZEDTEXT *as,long wildok,char *limit);
222 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
223 STRING *st);
224 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
225 char **s,SEARCHPGM *pgm,char *limit);
226 char *imap_send_spgm_trim (char *base,char *s,char *text);
227 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
228 char **s,SEARCHSET *set,char *prefix,
229 char *limit);
230 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
231 char **s,char *name,STRINGLIST *list,
232 char *limit);
233 void imap_send_sdate (char **s,char *name,unsigned short date);
234 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag);
235 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text);
236 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text);
237 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
238 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
239 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy);
240 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
241 IMAPPARSEDREPLY *reply);
242 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr);
243 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
244 STRINGLIST *stl);
245 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
246 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
247 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
248 IMAPPARSEDREPLY *reply);
249 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
250 IMAPPARSEDREPLY *reply);
251 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
252 unsigned char **txtptr);
253 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag);
254 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
255 IMAPPARSEDREPLY *reply,unsigned long *len);
256 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
257 IMAPPARSEDREPLY *reply,GETS_DATA *md,
258 unsigned long *len,long flags);
259 void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr,
260 IMAPPARSEDREPLY *reply);
261 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
262 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
263 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
264 unsigned char **txtptr,
265 IMAPPARSEDREPLY *reply);
266 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
267 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
268 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
269 IMAPPARSEDREPLY *reply);
270 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
271 IMAPPARSEDREPLY *reply);
272 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
273 IMAPPARSEDREPLY *reply);
274 void imap_parse_capabilities (MAILSTREAM *stream,char *t);
275 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags);
276 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags);
277 long imap_setid(MAILSTREAM *stream, IDLIST *idlist);
278 IDLIST *imap_parse_idlist(char *text);
280 /* Driver dispatch used by MAIL */
282 DRIVER imapdriver = {
283 "imap", /* driver name */
284 /* driver flags */
285 DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN,
286 (DRIVER *) NIL, /* next driver */
287 imap_valid, /* mailbox is valid for us */
288 imap_parameters, /* manipulate parameters */
289 imap_scan, /* scan mailboxes */
290 imap_list, /* find mailboxes */
291 imap_lsub, /* find subscribed mailboxes */
292 imap_subscribe, /* subscribe to mailbox */
293 imap_unsubscribe, /* unsubscribe from mailbox */
294 imap_create, /* create mailbox */
295 imap_delete, /* delete mailbox */
296 imap_rename, /* rename mailbox */
297 imap_status, /* status of mailbox */
298 imap_open, /* open mailbox */
299 imap_close, /* close mailbox */
300 imap_fast, /* fetch message "fast" attributes */
301 imap_flags, /* fetch message flags */
302 imap_overview, /* fetch overview */
303 imap_structure, /* fetch message envelopes */
304 NIL, /* fetch message header */
305 NIL, /* fetch message body */
306 imap_msgdata, /* fetch partial message */
307 imap_uid, /* unique identifier */
308 imap_msgno, /* message number */
309 imap_flag, /* modify flags */
310 NIL, /* per-message modify flags */
311 imap_search, /* search for message based on criteria */
312 imap_sort, /* sort messages */
313 imap_thread, /* thread messages */
314 imap_ping, /* ping mailbox to see if still alive */
315 imap_check, /* check for new messages */
316 imap_expunge, /* expunge deleted messages */
317 imap_copy, /* copy messages to another mailbox */
318 imap_append, /* append string message to mailbox */
319 imap_gc /* garbage collect stream */
322 /* prototype stream */
323 MAILSTREAM imapproto = {&imapdriver};
325 /* driver parameters */
326 static unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
327 static long imap_lookahead = IMAPLOOKAHEAD;
328 static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
329 static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD;
330 static long imap_defaultport = 0;
331 static long imap_sslport = 0;
332 static long imap_tryssl = NIL;
333 static long imap_prefetch = IMAPLOOKAHEAD;
334 static long imap_closeonerror = NIL;
335 static imapenvelope_t imap_envelope = NIL;
336 static imapreferral_t imap_referral = NIL;
337 static char *imap_extrahdrs = NIL;
339 /* constants */
340 static char *hdrheader[] = {
341 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
342 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
343 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
344 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
345 "BODY.PEEK[HEADER.FIELDS (Newsgroups"
347 static char *hdrtrailer ="Followup-To References)]";
349 /* IMAP validate mailbox
350 * Accepts: mailbox name
351 * Returns: our driver if name is valid, NIL otherwise
354 DRIVER *imap_valid (char *name)
356 return mail_valid_net (name,&imapdriver,NIL,NIL);
360 /* IMAP manipulate driver parameters
361 * Accepts: function code
362 * function-dependent value
363 * Returns: function-dependent return value
366 void *imap_parameters (long function,void *value)
368 switch ((int) function) {
369 case GET_NAMESPACE:
370 if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace &&
371 !((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace)
372 imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL);
373 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace;
374 break;
375 case GET_THREADERS:
376 value = (void *)
377 ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader;
378 break;
379 case SET_FETCHLOOKAHEAD: /* must use pointer from GET_FETCHLOOKAHEAD */
380 fatal ("SET_FETCHLOOKAHEAD not permitted");
381 case GET_FETCHLOOKAHEAD:
382 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead;
383 break;
384 case SET_MAXLOGINTRIALS:
385 imap_maxlogintrials = (long) value;
386 break;
387 case GET_MAXLOGINTRIALS:
388 value = (void *) imap_maxlogintrials;
389 break;
390 case SET_LOOKAHEAD:
391 imap_lookahead = (long) value;
392 break;
393 case GET_LOOKAHEAD:
394 value = (void *) imap_lookahead;
395 break;
396 case SET_UIDLOOKAHEAD:
397 imap_uidlookahead = (long) value;
398 break;
399 case GET_UIDLOOKAHEAD:
400 value = (void *) imap_uidlookahead;
401 break;
403 case SET_IMAPPORT:
404 imap_defaultport = (long) value;
405 break;
406 case GET_IMAPPORT:
407 value = (void *) imap_defaultport;
408 break;
409 case SET_SSLIMAPPORT:
410 imap_sslport = (long) value;
411 break;
412 case GET_SSLIMAPPORT:
413 value = (void *) imap_sslport;
414 break;
415 case SET_PREFETCH:
416 imap_prefetch = (long) value;
417 break;
418 case GET_PREFETCH:
419 value = (void *) imap_prefetch;
420 break;
421 case SET_CLOSEONERROR:
422 imap_closeonerror = (long) value;
423 break;
424 case GET_CLOSEONERROR:
425 value = (void *) imap_closeonerror;
426 break;
427 case SET_IMAPENVELOPE:
428 imap_envelope = (imapenvelope_t) value;
429 break;
430 case GET_IMAPENVELOPE:
431 value = (void *) imap_envelope;
432 break;
433 case SET_IMAPREFERRAL:
434 imap_referral = (imapreferral_t) value;
435 break;
436 case GET_IMAPREFERRAL:
437 value = (void *) imap_referral;
438 break;
439 case SET_IMAPEXTRAHEADERS:
440 imap_extrahdrs = (char *) value;
441 break;
442 case GET_IMAPEXTRAHEADERS:
443 value = (void *) imap_extrahdrs;
444 break;
445 case SET_IMAPTRYSSL:
446 imap_tryssl = (long) value;
447 break;
448 case GET_IMAPTRYSSL:
449 value = (void *) imap_tryssl;
450 break;
451 case SET_FETCHLOOKAHEADLIMIT:
452 imap_fetchlookaheadlimit = (long) value;
453 break;
454 case GET_FETCHLOOKAHEADLIMIT:
455 value = (void *) imap_fetchlookaheadlimit;
456 break;
458 case SET_IDLETIMEOUT:
459 fatal ("SET_IDLETIMEOUT not permitted");
460 case GET_IDLETIMEOUT:
461 value = (void *) IDLETIMEOUT;
462 break;
463 case SET_IDSTREAM: /* set IMAP server ID */
464 fatal ("SET_IDSTREAM not permitted");
465 case GET_IDSTREAM: /* get IMAP server ID */
466 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->id;
467 break;
468 default:
469 value = NIL; /* error case */
470 break;
472 return value;
475 /* IMAP scan mailboxes
476 * Accepts: mail stream
477 * reference
478 * pattern to search
479 * string to scan
482 void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
484 imap_list_work (stream,"SCAN",ref,pat,contents);
488 /* IMAP list mailboxes
489 * Accepts: mail stream
490 * reference
491 * pattern to search
494 void imap_list (MAILSTREAM *stream,char *ref,char *pat)
496 imap_list_work (stream,"LIST",ref,pat,NIL);
500 /* IMAP list subscribed mailboxes
501 * Accepts: mail stream
502 * reference
503 * pattern to search
506 void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
508 void *sdb = NIL;
509 char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
510 /* do it on the server */
511 imap_list_work (stream,"LSUB",ref,pat,NIL);
512 if (*pat == '{') { /* if remote pattern, must be IMAP */
513 if (!imap_valid (pat)) return;
514 ref = NIL; /* good IMAP pattern, punt reference */
516 /* if remote reference, must be valid IMAP */
517 if (ref && (*ref == '{') && !imap_valid (ref)) return;
518 /* kludgy application of reference */
519 if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
520 else strcpy (mbx,pat);
522 if ((s = sm_read (tmp,&sdb)) != NULL) do if (imap_valid (s) && pmatch (s,mbx))
523 mm_lsub (stream,NIL,s,NIL);
524 /* until no more subscriptions */
525 while ((s = sm_read (tmp,&sdb)) != NULL);
528 /* IMAP find list of mailboxes
529 * Accepts: mail stream
530 * list command
531 * reference
532 * pattern to search
533 * string to scan
536 void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
537 char *contents)
539 MAILSTREAM *st = stream;
540 int pl;
541 char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN];
542 IMAPARG *args[4],aref,apat,acont;
543 if (ref && *ref) { /* have a reference? */
544 if (!(imap_valid (ref) && /* make sure valid IMAP name and open stream */
545 ((stream && LOCAL && LOCAL->netstream) ||
546 (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return;
547 /* calculate prefix length */
548 pl = strchr (ref,'}') + 1 - ref;
549 strncpy (prefix,ref,pl); /* build prefix */
550 prefix[pl] = '\0'; /* tie off prefix */
551 ref += pl; /* update reference */
553 else {
554 if (!(imap_valid (pat) && /* make sure valid IMAP name and open stream */
555 ((stream && LOCAL && LOCAL->netstream) ||
556 (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return;
557 /* calculate prefix length */
558 pl = strchr (pat,'}') + 1 - pat;
559 strncpy (prefix,pat,pl); /* build prefix */
560 prefix[pl] = '\0'; /* tie off prefix */
561 pat += pl; /* update reference */
563 LOCAL->prefix = prefix; /* note prefix */
564 if (contents) { /* want to do a scan? */
565 if (LEVELSCAN (stream)) { /* make sure permitted */
566 args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
567 aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
568 apat.type = LISTMAILBOX; apat.text = (void *) pat;
569 acont.type = ASTRING; acont.text = (void *) contents;
570 imap_send (stream,cmd,args);
572 else mm_log ("Scan not valid on this IMAP server",ERROR);
575 else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */
576 args[0] = &aref; args[1] = &apat; args[2] = NIL;
577 aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
578 apat.type = LISTMAILBOX; apat.text = (void *) pat;
579 /* referrals armed? */
580 if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) {
581 /* yes, convert LIST -> RLIST */
582 if (!compare_cstring (cmd,"LIST")) cmd = "RLIST";
583 /* and convert LSUB -> RLSUB */
584 else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB";
586 imap_send (stream,cmd,args);
588 else if (LEVEL1176 (stream)) {/* convert to IMAP2 format wildcard */
589 /* kludgy application of reference */
590 if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
591 else strcpy (mbx,pat);
592 for (s = mbx; *s; s++) if (*s == '%') *s = '*';
593 args[0] = &apat; args[1] = NIL;
594 apat.type = LISTMAILBOX; apat.text = (void *) mbx;
595 if (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
596 strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) &&
597 !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
598 LOCAL->cap.rfc1176 = NIL; /* must be RFC-1064 */
600 LOCAL->prefix = NIL; /* no more prefix */
601 /* close temporary stream if we made one */
602 if (stream != st) mail_close (stream);
605 /* IMAP subscribe to mailbox
606 * Accepts: mail stream
607 * mailbox to add to subscription list
608 * Returns: T on success, NIL on failure
611 long imap_subscribe (MAILSTREAM *stream,char *mailbox)
613 MAILSTREAM *st = stream;
614 long ret = ((stream && LOCAL && LOCAL->netstream) ||
615 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
616 imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
617 "Subscribe" : "Subscribe Mailbox",NIL) : NIL;
618 /* toss out temporary stream */
619 if (st != stream) mail_close (stream);
620 return ret;
624 /* IMAP unsubscribe to mailbox
625 * Accepts: mail stream
626 * mailbox to delete from manage list
627 * Returns: T on success, NIL on failure
630 long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
632 MAILSTREAM *st = stream;
633 long ret = ((stream && LOCAL && LOCAL->netstream) ||
634 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
635 imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
636 "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL;
637 /* toss out temporary stream */
638 if (st != stream) mail_close (stream);
639 return ret;
642 /* IMAP create mailbox
643 * Accepts: mail stream
644 * mailbox name to create
645 * Returns: T on success, NIL on failure
648 long imap_create (MAILSTREAM *stream,char *mailbox)
650 return imap_manage (stream,mailbox,"Create",NIL);
654 /* IMAP delete mailbox
655 * Accepts: mail stream
656 * mailbox name to delete
657 * Returns: T on success, NIL on failure
660 long imap_delete (MAILSTREAM *stream,char *mailbox)
662 return imap_manage (stream,mailbox,"Delete",NIL);
666 /* IMAP rename mailbox
667 * Accepts: mail stream
668 * old mailbox name
669 * new mailbox name
670 * Returns: T on success, NIL on failure
673 long imap_rename (MAILSTREAM *stream,char *old,char *newname)
675 return imap_manage (stream,old,"Rename",newname);
678 /* IMAP manage a mailbox
679 * Accepts: mail stream
680 * mailbox to manipulate
681 * command to execute
682 * optional second argument
683 * Returns: T on success, NIL on failure
686 long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
688 MAILSTREAM *st = stream;
689 IMAPPARSEDREPLY *reply;
690 long ret = NIL;
691 char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
692 IMAPARG *args[3],ambx,amb2;
693 imapreferral_t ir =
694 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
695 ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
696 amb2.text = (void *) mbx2;
697 args[0] = &ambx; args[1] = args[2] = NIL;
698 /* require valid names and open stream */
699 if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) &&
700 (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) &&
701 ((stream && LOCAL && LOCAL->netstream) ||
702 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
703 if (arg2) args[1] = &amb2; /* second arg present? */
704 if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) &&
705 ir && LOCAL->referral) {
706 long code = -1;
707 switch (*command) { /* which command was it? */
708 case 'S': code = REFSUBSCRIBE; break;
709 case 'U': code = REFUNSUBSCRIBE; break;
710 case 'C': code = REFCREATE; break;
711 case 'D': code = REFDELETE; break;
712 case 'R': code = REFRENAME; break;
713 default:
714 fatal ("impossible referral command");
716 if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code)))
717 ret = imap_manage (NIL,mailbox,command,(*command == 'R') ?
718 (mailbox + strlen (mailbox) + 1) : NIL);
720 mm_log (reply->text,ret ? NIL : ERROR);
721 /* toss out temporary stream */
722 if (st != stream) mail_close (stream);
724 return ret;
727 /* IMAP status
728 * Accepts: mail stream
729 * mailbox name
730 * status flags
731 * Returns: T on success, NIL on failure
734 long imap_status (MAILSTREAM *stream,char *mbx,long flags)
736 IMAPARG *args[3],ambx,aflg;
737 char tmp[MAILTMPLEN];
738 NETMBX mb;
739 unsigned long i;
740 long ret = NIL;
741 MAILSTREAM *tstream = NIL;
742 /* use given stream if (rev1 or halfopen) and
743 right host */
744 if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) &&
745 mail_usable_network_stream (stream,mbx)) ||
746 (stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))))
747 return NIL;
748 /* parse mailbox name */
749 mail_valid_net_parse (mbx,&mb);
750 args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
751 ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
752 if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */
753 imapreferral_t ir;
754 aflg.type = FLAGS; aflg.text = (void *) tmp;
755 args[1] = &aflg; args[2] = NIL;
756 tmp[0] = tmp[1] = '\0'; /* build flag list */
757 if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
758 if (flags & SA_RECENT) strcat (tmp," RECENT");
759 if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
760 if (flags & SA_UIDNEXT) strcat (tmp," UIDNEXT");
761 if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY");
762 tmp[0] = '(';
763 strcat (tmp,")");
764 /* send "STATUS mailbox flag" */
765 if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
766 else if ((ir = (imapreferral_t)
767 mail_parameters (stream,GET_IMAPREFERRAL,NIL)) &&
768 LOCAL->referral &&
769 (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS)))
770 ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL));
773 /* IMAP2 way */
774 else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) {
775 MAILSTATUS status;
776 status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY);
777 status.messages = stream->nmsgs;
778 status.recent = stream->recent;
779 status.unseen = 0;
780 if (flags & SA_UNSEEN) { /* must search to get unseen messages */
781 /* clear search vector */
782 for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
783 if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL)))
784 for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
785 if (mail_elt (stream,i)->searched) status.unseen++;
787 strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox);
788 /* pass status to main program */
789 mm_status (stream,tmp,&status);
790 ret = T; /* note success */
792 if (tstream) mail_close (tstream);
793 return ret; /* success */
796 /* IMAP open
797 * Accepts: stream to open
798 * Returns: stream to use on success, NIL on failure
801 MAILSTREAM *imap_open (MAILSTREAM *stream)
803 unsigned long i,j;
804 char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN];
805 NETMBX mb;
806 IMAPPARSEDREPLY *reply = NIL;
807 imapreferral_t ir =
808 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
809 /* return prototype for OP_PROTOTYPE call */
810 if (!stream) return &imapproto;
811 mail_valid_net_parse (stream->mailbox,&mb);
812 usr[0] = '\0'; /* initially no user name */
813 if (LOCAL) { /* if stream opened earlier by us */
814 /* recycle if still alive */
815 if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) {
816 i = stream->silent; /* temporarily mark silent */
817 stream->silent = T; /* don't give mm_exists() events */
818 j = imap_ping (stream); /* learn if stream still alive */
819 stream->silent = i; /* restore prior state */
820 if (j) { /* was stream still alive? */
821 sprintf (tmp,"Reusing connection to %s",net_host (LOCAL->netstream));
822 if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
823 LOCAL->user);
824 if (!stream->silent) mm_log (tmp,(long) NIL);
825 /* unselect if now want halfopen */
826 if (stream->halfopen) imap_send (stream,"UNSELECT",NIL);
828 else imap_close (stream,NIL);
830 else imap_close (stream,NIL);
832 /* copy flags from name */
833 if (mb.dbgflag) stream->debug = T;
834 if (mb.readonlyflag) stream->rdonly = T;
835 if (mb.anoflag) stream->anonymous = T;
836 if (mb.secflag) stream->secure = T;
837 if (mb.trysslflag || imap_tryssl) stream->tryssl = T;
839 if (!LOCAL) { /* open new connection if no recycle */
840 NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
841 unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
842 unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT;
843 stream->local = /* instantiate localdata */
844 (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL));
845 /* assume IMAP2bis server */
846 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
847 /* in case server is a loser */
848 if (mb.loser) LOCAL->loser = T;
849 /* desirable authenticators */
850 LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) |
851 (mb.authuser[0] ? AU_AUTHUSER : NIL);
852 /* IMAP connection open logic is more complex than net_open() normally
853 * deals with, because of the simap and rimap hacks.
854 * If the session is anonymous, a specific port is given, or if /ssl or
855 * /starttls is set, do net_open() since those conditions override everything
856 * else.
858 if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag)
859 reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps",
860 sslport)) ?
861 imap_reply (stream,NIL) : NIL;
863 * No overriding conditions, so get the best connection that we can. In
864 * order, attempt to open via simap, tryssl, rimap, and finally TCP.
866 /* try simap */
867 else if ((reply = imap_rimap (stream,"*imap",&mb,usr,tmp)) != NULL);
868 else if (ssld && /* try tryssl if enabled */
869 (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) &&
870 (LOCAL->netstream =
871 net_open_work (ssld,mb.host,"*imaps",sslport,mb.port,
872 (mb.novalidate ? NET_NOVALIDATECERT : 0) |
873 NET_SILENT | NET_TRYSSL))) {
874 if (net_sout (LOCAL->netstream,"",0)) {
875 mb.sslflag = T;
876 reply = imap_reply (stream,NIL);
878 else { /* flush fake SSL stream */
879 net_close (LOCAL->netstream);
880 LOCAL->netstream = NIL;
883 /* try rimap first, then TCP */
884 else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) &&
885 (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL)))
886 reply = imap_reply (stream,NIL);
887 /* make sure greeting is good */
888 if (!reply || strcmp (reply->tag,"*") ||
889 (strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) {
890 if (reply) mm_log (reply->text,ERROR);
891 return NIL; /* lost during greeting */
894 /* STARTTLS is not allowed in PREAUTH state */
895 if (LOCAL->netstream && !strcmp (reply->key,"PREAUTH")){
896 sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
897 if (!LOCAL->gotcapability) imap_capability (stream);
898 if (LOCAL->netstream
899 && stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag && mb.tlsflag){
900 mm_log("STARTTLS not allowed on PREAUTH state. Closing Connection", ERROR);
901 return NIL;
904 /* if connected and not preauthenticated */
905 if (LOCAL->netstream && strcmp (reply->key,"PREAUTH")) {
906 sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
907 /* get server capabilities */
908 if (!LOCAL->gotcapability) imap_capability (stream);
909 if (LOCAL->netstream && /* does server support STARTTLS? */
910 stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag &&
911 imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) {
912 mb.tlsflag = T; /* TLS OK, get into TLS at this end */
913 LOCAL->netstream->dtb = ssld;
914 if (!(LOCAL->netstream->stream =
915 (*stls) (LOCAL->netstream->stream,mb.host,
916 SSL_MTHD(mb) | (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) {
917 /* drat, drop this connection */
918 if (LOCAL->netstream) net_close (LOCAL->netstream);
919 LOCAL->netstream = NIL;
921 /* get capabilities now that TLS in effect */
922 if (LOCAL->netstream) imap_capability (stream);
924 else if (mb.tlsflag) { /* user specified /starttls but can't do it */
925 mm_log ("Unable to negotiate TLS with this server",ERROR);
926 return NIL;
928 if (LOCAL->netstream) { /* still in the land of the living? */
929 if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
930 /* remote name for authentication */
931 strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)?
932 net_remotehost (LOCAL->netstream) :
933 net_host (LOCAL->netstream),NETMAXHOST-1);
934 mb.host[NETMAXHOST-1] = '\0';
936 /* need new capabilities after login */
937 LOCAL->gotcapability = NIL;
938 if (!(stream->anonymous ? imap_anon (stream,tmp) :
939 (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) :
940 imap_login (stream,&mb,tmp,usr)))) {
941 /* failed, is there a referral? */
942 if (mb.tlsflag) LOCAL->tlsflag = T;
943 if (ir && LOCAL->referral &&
944 (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) {
945 imap_close (stream,NIL);
946 fs_give ((void **) &stream->mailbox);
947 /* set as new mailbox name to open */
948 stream->mailbox = s;
949 return imap_open (stream);
951 return NIL; /* authentication failed */
953 else if (ir && LOCAL->referral &&
954 (s = (*ir) (stream,LOCAL->referral,REFAUTH))) {
955 imap_close (stream,NIL);
956 fs_give ((void **) &stream->mailbox);
957 stream->mailbox = s; /* set as new mailbox name to open */
958 /* recurse to log in on real site */
959 return imap_open (stream);
963 /* get server capabilities again */
964 if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream);
965 /* save state for future recycling */
966 if (mb.tlsflag) LOCAL->tlsflag = T;
967 if (mb.tls1) LOCAL->tls1 = T;
968 if (mb.tls1_1) LOCAL->tls1_1 = T;
969 if (mb.tls1_2) LOCAL->tls1_2 = T;
970 if (mb.tls1_3) LOCAL->tls1_3 = T;
971 if (mb.tlssslv23) LOCAL->tlssslv23 = T;
972 if (mb.notlsflag) LOCAL->notlsflag = T;
973 if (mb.sslflag) LOCAL->sslflag = T;
974 if (mb.novalidate) LOCAL->novalidate = T;
975 if (mb.loser) LOCAL->loser = T;
978 if (LOCAL->netstream) { /* still have a connection? */
979 stream->perm_seen = stream->perm_deleted = stream->perm_answered =
980 stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T;
981 stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff;
982 stream->sequence++; /* bump sequence number */
983 sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
984 net_host (LOCAL->netstream) : mb.host);
985 if (!((i = net_port (LOCAL->netstream)) & 0xffff0000))
986 sprintf (tmp + strlen (tmp),":%lu",i);
987 strcat (tmp,"/imap");
988 if (LOCAL->tlsflag) strcat (tmp,"/starttls");
989 if (LOCAL->tls1) strcat (tmp,"/tls1");
990 if (LOCAL->tls1_1) strcat (tmp,"/tls1_1");
991 if (LOCAL->tls1_2) strcat (tmp,"/tls1_2");
992 if (LOCAL->tls1_3) strcat (tmp,"/tls1_3");
993 if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
994 if (LOCAL->notlsflag) strcat (tmp,"/nostarttls");
995 if (LOCAL->sslflag) strcat (tmp,"/ssl");
996 if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
997 if (LOCAL->loser) strcat (tmp,"/loser");
998 if (stream->secure) strcat (tmp,"/secure");
999 if (stream->rdonly) strcat (tmp,"/readonly");
1000 if (stream->anonymous) strcat (tmp,"/anonymous");
1001 else { /* record user name */
1002 if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr);
1003 if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
1004 LOCAL->user);
1006 strcat (tmp,"}");
1008 if(LEVELID(stream)){ /* Set ID of app */
1009 IDLIST *idapp = (IDLIST *) mail_parameters(NIL, GET_IDPARAMS, NIL);
1010 if(idapp && !LOCAL->setid){
1011 imap_setid(stream, idapp);
1012 LOCAL->setid++;
1015 if (!stream->halfopen) { /* wants to open a mailbox? */
1016 IMAPARG *args[2];
1017 IMAPARG ambx;
1018 ambx.type = ASTRING;
1019 ambx.text = (void *) mb.mailbox;
1020 args[0] = &ambx; args[1] = NIL;
1021 stream->nmsgs = 0;
1022 if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
1023 "EXAMINE": "SELECT",args))) {
1024 strcat (tmp,mb.mailbox);/* mailbox name */
1025 if (!stream->nmsgs && !stream->silent)
1026 mm_log ("Mailbox is empty",(long) NIL);
1027 /* note if an INBOX or not */
1028 stream->inbox = !compare_cstring (mb.mailbox,"INBOX");
1030 else if (ir && LOCAL->referral &&
1031 (s = (*ir) (stream,LOCAL->referral,REFSELECT))) {
1032 imap_close (stream,NIL);
1033 fs_give ((void **) &stream->mailbox);
1034 stream->mailbox = s; /* set as new mailbox name to open */
1035 return imap_open (stream);
1037 else {
1038 mm_log (reply->text,ERROR);
1039 if (imap_closeonerror) return NIL;
1040 stream->halfopen = T; /* let him keep it half-open */
1043 if (stream->halfopen) { /* half-open connection? */
1044 strcat (tmp,"<no_mailbox>");
1045 /* make sure dummy message counts */
1046 mail_exists (stream,(long) 0);
1047 mail_recent (stream,(long) 0);
1049 fs_give ((void **) &stream->mailbox);
1050 stream->mailbox = cpystr (tmp);
1052 /* success if stream open */
1053 return LOCAL->netstream ? stream : NIL;
1056 /* IMAP rimap connect
1057 * Accepts: MAIL stream
1058 * NETMBX specification
1059 * service to use
1060 * user name
1061 * scratch buffer
1062 * Returns: parsed reply if success, else NIL
1065 IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
1066 char *usr,char *tmp)
1068 unsigned long i;
1069 char c[2];
1070 NETSTREAM *tstream;
1071 IMAPPARSEDREPLY *reply = NIL;
1072 /* try rimap open */
1073 if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) {
1074 /* if success, see if reasonable banner */
1075 if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
1076 i = 0; /* copy to buffer */
1077 do tmp[i++] = *c;
1078 while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
1079 (*c != '\012') && (i < (MAILTMPLEN-1)));
1080 tmp[i] = '\0'; /* tie off */
1081 /* snarfed a valid greeting? */
1082 if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) &&
1083 (*c == '\012') &&
1084 !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){
1085 /* parse line as IMAP */
1086 imap_parse_unsolicited (stream,reply);
1087 /* make sure greeting is good */
1088 if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
1089 LOCAL->netstream = tstream;
1090 return reply; /* return success */
1094 net_close (tstream); /* failed, punt the temporary netstream */
1096 return NIL;
1099 /* IMAP log in as anonymous
1100 * Accepts: stream to authenticate
1101 * scratch buffer
1102 * Returns: T on success, NIL on failure
1105 long imap_anon (MAILSTREAM *stream,char *tmp)
1107 IMAPPARSEDREPLY *reply;
1108 char *s = net_localhost (LOCAL->netstream);
1109 if (LOCAL->cap.authanon) {
1110 char tag[16];
1111 unsigned long i;
1112 char *broken = "[CLOSED] IMAP connection broken (anonymous auth)";
1113 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1114 /* build command */
1115 sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag);
1116 if (!imap_soutr (stream,tmp)) {
1117 mm_log (broken,ERROR);
1118 return NIL;
1120 if (imap_challenge (stream,&i)) imap_response (stream,NIL,s,strlen (s));
1121 /* get response */
1122 if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken);
1123 /* what we wanted? */
1124 if (compare_cstring (reply->tag,tag)) {
1125 /* abort if don't have tagged response */
1126 while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1127 imap_soutr (stream,"*");
1130 else {
1131 IMAPARG *args[2];
1132 IMAPARG ausr;
1133 ausr.type = ASTRING;
1134 ausr.text = (void *) s;
1135 args[0] = &ausr; args[1] = NIL;
1136 /* send "LOGIN anonymous <host>" */
1137 reply = imap_send (stream,"LOGIN ANONYMOUS",args);
1139 /* success if reply OK */
1140 if (imap_OK (stream,reply)) return T;
1141 mm_log (reply->text,ERROR);
1142 return NIL;
1145 /* IMAP authenticate
1146 * Accepts: stream to authenticate
1147 * parsed network mailbox structure
1148 * scratch buffer
1149 * place to return user name
1150 * Returns: T on success, NIL on failure
1153 long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
1155 unsigned long trial,ua,uasaved;
1156 int ok;
1157 char tag[16];
1158 char *lsterr = NIL, *base;
1159 AUTHENTICATOR *at, *atsaved;
1160 IMAPPARSEDREPLY *reply;
1161 for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua &&
1162 (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
1163 if(mb && *mb->auth){
1164 if(!compare_cstring(at->name, mb->auth))
1165 atsaved = at;
1166 else{
1167 uasaved = ua;
1168 continue;
1171 if (lsterr) { /* previous authenticator failed? */
1172 sprintf (tmp,"Retrying using %s authentication after %.80s",
1173 at->name,lsterr);
1174 mm_log (tmp,NIL);
1175 fs_give ((void **) &lsterr);
1177 trial = 0; /* initial trial count */
1178 tmp[0] = '\0'; /* no error */
1179 do { /* gensym a new tag */
1180 if (lsterr) { /* previous attempt with this one failed? */
1181 sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
1182 mm_log (tmp,WARN);
1183 fs_give ((void **) &lsterr);
1185 LOCAL->saslcancel = NIL;
1186 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1187 /* build command */
1188 sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name);
1189 base = (at->flags & AU_SINGLE) && LOCAL->cap.sasl_ir
1190 ? (char *) tmp : NIL;
1191 if (base || imap_soutr (stream,tmp)) {
1192 /* report we tried this authenticator */
1193 if(base && stream && stream->debug) mm_dlog (base);
1194 /* hide client authentication responses */
1195 if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T;
1196 ok = (*at->client) (imap_challenge,imap_response,base,"imap",mb,stream,
1197 net_port(LOCAL->netstream),&trial,usr);
1198 LOCAL->sensitive = NIL; /* unhide */
1200 if(base && ok && !trial){ /* return now or see below for the same code */
1201 mm_log ("IMAP Authentication cancelled",ERROR);
1202 return NIL;
1204 /* make sure have a response */
1205 if (!(reply = &LOCAL->reply)->tag)
1206 reply = imap_fake (stream,tag,
1207 "[CLOSED] IMAP connection broken (authenticate)");
1208 else if (compare_cstring (reply->tag,tag))
1209 while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1210 imap_soutr (stream,"*");
1211 /* good if SASL ok and success response */
1212 if (ok && imap_OK (stream,reply)) return T;
1213 if (!trial) { /* if main program requested cancellation */
1214 mm_log ("IMAP Authentication cancelled",ERROR);
1215 return NIL;
1217 /* no error if protocol-initiated cancel */
1218 lsterr = cpystr (reply->text);
1221 while (LOCAL->netstream && !LOCAL->byeseen && trial &&
1222 (trial < imap_maxlogintrials));
1224 if (lsterr) { /* previous authenticator failed? */
1225 if (!LOCAL->saslcancel) { /* don't do this if a cancel */
1226 sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr);
1227 mm_log (tmp,ERROR);
1229 fs_give ((void **) &lsterr);
1231 if(mb && *mb->auth){
1232 if(!uasaved) sprintf (tmp,"Client does not support AUTH=%.80s authenticator",mb->auth);
1233 else if (!atsaved) sprintf (tmp,"IMAP server does not support AUTH=%.80s authenticator",mb->auth);
1234 if (!uasaved || !atsaved) mm_log (tmp,ERROR);
1236 return NIL; /* ran out of authenticators */
1239 /* IMAP login
1240 * Accepts: stream to login
1241 * parsed network mailbox structure
1242 * scratch buffer of length MAILTMPLEN
1243 * place to return user name
1244 * Returns: T on success, NIL on failure
1247 long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
1249 unsigned long trial = 0;
1250 IMAPPARSEDREPLY *reply;
1251 IMAPARG *args[3];
1252 IMAPARG ausr,apwd;
1253 long ret = NIL;
1254 char *app_pwd = NIL;
1255 if (stream->secure) /* never do LOGIN if want security */
1256 mm_log ("Can't do secure authentication with this server",ERROR);
1257 /* never do LOGIN if server disabled it */
1258 else if (LOCAL->cap.logindisabled)
1259 mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR);
1260 else if (mb->authuser[0]) /* never do LOGIN with /authuser */
1261 mm_log ("Can't do /authuser with this server",ERROR);
1262 else { /* OK to try login */
1263 ausr.type = apwd.type = ASTRING;
1264 ausr.text = (void *) usr;
1265 apwd.text = (void *) pwd;
1266 args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
1267 do {
1268 if(app_pwd) fs_give((void **) &app_pwd);
1269 pwd[0] = '\0';
1270 mm_login (mb,usr, &app_pwd,trial++);
1271 if(app_pwd){
1272 strncpy(pwd, app_pwd, MAILTMPLEN);
1273 pwd[MAILTMPLEN-1] = '\0';
1275 if (pwd[0]) { /* send login command if have password */
1276 LOCAL->sensitive = T; /* hide this command */
1277 /* send "LOGIN usr pwd" */
1278 if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args)))
1279 ret = LONGT; /* success */
1280 else {
1281 mm_log (reply->text,WARN);
1282 if (!LOCAL->referral && (trial == imap_maxlogintrials))
1283 mm_log ("Too many login failures",ERROR);
1285 LOCAL->sensitive = NIL; /* unhide */
1287 /* user refused to give password */
1288 else mm_log ("Login aborted",ERROR);
1289 } while (!ret && pwd[0] && (trial < imap_maxlogintrials) &&
1290 LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral);
1292 if(app_pwd) fs_give((void **) &app_pwd);
1293 memset((void *) pwd, 0, MAILTMPLEN);
1294 return ret;
1297 /* Get challenge to authenticator in binary
1298 * Accepts: stream
1299 * pointer to returned size
1300 * Returns: challenge or NIL if not challenge
1303 void *imap_challenge (void *s,unsigned long *len)
1305 char tmp[MAILTMPLEN];
1306 void *ret = NIL;
1307 MAILSTREAM *stream = (MAILSTREAM *) s;
1308 IMAPPARSEDREPLY *reply = NIL;
1309 /* get tagged response or challenge */
1310 while (stream && LOCAL->netstream &&
1311 (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) &&
1312 !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
1313 /* parse challenge if have one */
1314 if (stream && LOCAL->netstream && reply && reply->tag &&
1315 (*reply->tag == '+') && !reply->tag[1] && reply->text &&
1316 !(ret = rfc822_base64 ((unsigned char *) reply->text,
1317 strlen (reply->text),len))) {
1318 sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s",
1319 (char *) reply->text);
1320 mm_log (tmp,ERROR);
1322 return ret;
1326 /* Send authenticator response in BASE64
1327 * Accepts: MAIL stream
1328 * string to send
1329 * length of string
1330 * Returns: T if successful, else NIL
1333 long imap_response (void *s,char *base,char *response,unsigned long size)
1335 MAILSTREAM *stream = (MAILSTREAM *) s;
1336 unsigned long i,j,ret;
1337 char *t,*u;
1338 if (response) { /* make CRLFless BASE64 string */
1339 if (size) {
1340 if(base){
1341 char *s, *v;
1343 v = (char *) rfc822_binary ((void *) response,size,&i);
1344 t = fs_get((strlen(base) + strlen(v) + 1 + 2)*sizeof(char));
1345 for(s = base, u = t; *s; s++) *u++ = *s;
1346 *u++ = ' ';
1347 for (s = v,j = 0; j < i; j++) if (s[j] > ' ') *u++ = s[j];
1348 fs_give((void **) &v);
1349 } else {
1350 for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
1351 j < i; j++) if (t[j] > ' ') *u++ = t[j];
1353 *u = '\0'; /* tie off string for mm_dlog() */
1354 if (stream->debug) mail_dlog (t,LOCAL->sensitive);
1356 /* append CRLF */
1357 *u++ = '\015'; *u++ = '\012';
1358 ret = net_sout (LOCAL->netstream,t,u - t);
1359 fs_give ((void **) &t);
1361 else ret = imap_soutr (stream,"");
1363 else { /* abort requested */
1364 ret = base ? NIL : imap_soutr (stream,"*");
1365 LOCAL->saslcancel = T; /* mark protocol-requested SASL cancel */
1367 return ret;
1370 /* IMAP close
1371 * Accepts: MAIL stream
1372 * option flags
1375 void imap_close (MAILSTREAM *stream,long options)
1377 THREADER *thr,*t;
1378 IMAPPARSEDREPLY *reply;
1379 if (stream && LOCAL) { /* send "LOGOUT" */
1380 if (!LOCAL->byeseen) { /* don't even think of doing it if saw a BYE */
1381 /* expunge silently if requested */
1382 if (options & CL_EXPUNGE)
1383 imap_send (stream,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL);
1384 if (LOCAL->netstream &&
1385 !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
1386 mm_log (reply->text,WARN);
1388 /* close NET connection if still open */
1389 if (LOCAL->netstream) net_close (LOCAL->netstream);
1390 LOCAL->netstream = NIL;
1391 /* free up memory */
1392 if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
1393 if (LOCAL->namespace) {
1394 mail_free_namespace (&LOCAL->namespace[0]);
1395 mail_free_namespace (&LOCAL->namespace[1]);
1396 mail_free_namespace (&LOCAL->namespace[2]);
1397 fs_give ((void **) &LOCAL->namespace);
1399 if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
1400 /* flush threaders */
1401 if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
1402 fs_give ((void **) &t->name);
1403 thr = t->next;
1404 fs_give ((void **) &t);
1406 if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
1407 if (LOCAL->user) fs_give ((void **) &LOCAL->user);
1408 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
1409 if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
1410 if (LOCAL->id) mail_free_idlist(&LOCAL->id);
1411 /* nuke the local data */
1412 fs_give ((void **) &stream->local);
1416 /* IMAP fetch fast information
1417 * Accepts: MAIL stream
1418 * sequence
1419 * option flags
1421 * Generally, imap_structure is preferred
1424 void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
1426 IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID);
1427 if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
1431 /* IMAP fetch flags
1432 * Accepts: MAIL stream
1433 * sequence
1434 * option flags
1437 void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
1438 { /* send "FETCH sequence FLAGS" */
1439 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1440 IMAPPARSEDREPLY *reply;
1441 IMAPARG *args[3],aseq,aatt;
1442 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1443 flags & FT_UID);
1444 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1445 aatt.type = ATOM; aatt.text = (void *) "FLAGS";
1446 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1447 if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1448 mm_log (reply->text,ERROR);
1451 /* IMAP fetch overview
1452 * Accepts: MAIL stream, sequence bits set
1453 * pointer to overview return function
1454 * Returns: T if successful, NIL otherwise
1457 long imap_overview (MAILSTREAM *stream,overview_t ofn)
1459 MESSAGECACHE *elt;
1460 ENVELOPE *env;
1461 OVERVIEW ov;
1462 char *s,*t;
1463 unsigned long i,start,last,len,slen;
1464 if (!LOCAL->netstream) return NIL;
1465 /* build overview sequence */
1466 for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
1467 if ((elt = mail_elt (stream,i))->sequence) {
1468 if (!elt->private.msg.env) {
1469 if (s) { /* continuing a sequence */
1470 if (i == last + 1) last = i;
1471 else { /* end of range */
1472 if (last != start) sprintf (t,":%lu,%lu",last,i);
1473 else sprintf (t,",%lu",i);
1474 if ((len - (slen = (t += strlen (t)) - s)) < 20) {
1475 fs_resize ((void **) &s,len += MAILTMPLEN);
1476 t = s + slen; /* relocate current pointer */
1478 start = last = i; /* begin a new range */
1481 else { /* first time, start new buffer */
1482 s = (char *) fs_get (len = MAILTMPLEN);
1483 sprintf (s,"%lu",start = last = i);
1484 t = s + strlen (s); /* end of buffer */
1488 /* last sequence */
1489 if (last != start) sprintf (t,":%lu",last);
1490 if (s) { /* prefetch as needed */
1491 imap_fetch (stream,s,FT_NEEDENV);
1492 fs_give ((void **) &s);
1494 ov.optional.lines = 0; /* now overview each message */
1495 ov.optional.xref = NIL;
1496 if (ofn) for (i = 1; i <= stream->nmsgs; i++)
1497 if (((elt = mail_elt (stream,i))->sequence) &&
1498 (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
1499 ov.subject = env->subject;
1500 ov.from = env->from;
1501 ov.date = env->date;
1502 ov.message_id = env->message_id;
1503 ov.references = env->references;
1504 ov.optional.octets = elt->rfc822_size;
1505 (*ofn) (stream,mail_uid (stream,i),&ov,i);
1507 return LONGT;
1510 /* IMAP fetch structure
1511 * Accepts: MAIL stream
1512 * message # to fetch
1513 * pointer to return body
1514 * option flags
1515 * Returns: envelope of this message, body returned in body value
1517 * Fetches the "fast" information as well
1520 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
1521 long flags)
1523 unsigned long i,j,k,x;
1524 char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN];
1525 MESSAGECACHE *elt;
1526 ENVELOPE **env;
1527 BODY **b;
1528 IMAPPARSEDREPLY *reply = NIL;
1529 IMAPARG *args[3],aseq,aatt;
1530 SEARCHSET *set = LOCAL->lookahead;
1531 LOCAL->lookahead = NIL;
1532 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1533 aseq.type = SEQUENCE; aseq.text = (void *) seq;
1534 aatt.type = ATOM; aatt.text = NIL;
1535 if (flags & FT_UID) /* see if can find msgno from UID */
1536 for (i = 1; i <= stream->nmsgs; i++)
1537 if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1538 msgno = i; /* found msgno, use it from now on */
1539 flags &= ~FT_UID; /* no longer a UID fetch */
1541 sprintf (s = seq,"%lu",msgno);/* initial sequence */
1542 if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
1543 /* UID fetching is requested and we can't map the UID to a message sequence
1544 * number. Assume that the message isn't cached at all.
1546 if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV +
1547 (body ? FT_NEEDBODY : NIL) +
1548 (flags & (FT_UID + FT_NOHDRS)))))
1549 mm_log (reply->text,ERROR);
1550 /* now hunt for this UID */
1551 for (i = 1; i <= stream->nmsgs; i++)
1552 if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1553 if (body) *body = elt->private.msg.body;
1554 return elt->private.msg.env;
1556 if (body) *body = NIL; /* can't find the UID */
1557 return NIL;
1559 elt = mail_elt (stream,msgno);/* get cache pointer */
1560 if (stream->scache) { /* short caching? */
1561 env = &stream->env; /* use temporaries on the stream */
1562 b = &stream->body;
1563 if (msgno != stream->msgno){/* flush old poop if a different message */
1564 mail_free_envelope (env);
1565 mail_free_body (b);
1566 stream->msgno = msgno; /* this is now the current short cache msg */
1570 else { /* normal cache */
1571 env = &elt->private.msg.env;/* get envelope and body pointers */
1572 b = &elt->private.msg.body;
1573 /* prefetch if don't have envelope */
1574 if (!(flags & FT_NOLOOKAHEAD) &&
1575 ((!*env || (*env)->incomplete) ||
1576 (body && !*b && LEVELIMAP2bis (stream)))) {
1577 if (set) { /* have a lookahead list? */
1578 MESSAGE *msg;
1579 for (k = imap_fetchlookaheadlimit;
1580 k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30));
1581 set = set->next) {
1582 i = (set->first == 0xffffffff) ? stream->nmsgs :
1583 min (set->first,stream->nmsgs);
1584 if ((j = (set->last == 0xffffffff) ? stream->nmsgs :
1585 min (set->last,stream->nmsgs)) != 0L) {
1586 if (i > j) { /* swap the range if backwards */
1587 x = i; i = j; j = x;
1589 /* find first message not msgno or in cache */
1590 while (((i == msgno) ||
1591 ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1592 (!body || msg->body))) && (i++ < j));
1593 /* until range or lookahead finished */
1594 while (k && (i <= j)) {
1595 /* find first cached message in range */
1596 for (x = i + 1; (x <= j) &&
1597 !((msg = &(mail_elt (stream,x)->private.msg))->env &&
1598 (!body || msg->body)); x++);
1599 if (i == --x) { /* only one message? */
1600 sprintf (s += strlen (s),",%lu",i++);
1601 k--; /* prefetching one message */
1603 else { /* a range to prefetch */
1604 sprintf (s += strlen (s),",%lu:%lu",i,x);
1605 i = 1 + x - i; /* number of messages in this range */
1606 /* still can look ahead some more? */
1607 if ((k = (k > i) ? k - i : 0) != 0)
1608 /* yes, scan further in this range */
1609 for (i = x + 2; (i <= j) &&
1610 ((i == msgno) ||
1611 ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1612 (!body || msg->body)));
1613 i++);
1617 else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) {
1618 sprintf (s += strlen (s),",%lu",i);
1619 k--; /* prefetching one message */
1623 /* build message number list */
1624 else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++)
1625 if (!mail_elt (stream,i)->private.msg.env) {
1626 s += strlen (s); /* find string end, see if nearing end */
1627 if ((s - seq) > (MAILTMPLEN - 20)) break;
1628 sprintf (s,",%lu",i); /* append message */
1629 for (j = i + 1, k--; /* hunt for last message without an envelope */
1630 k && (j <= stream->nmsgs) &&
1631 !mail_elt (stream,j)->private.msg.env; j++, k--);
1632 /* if different, make a range */
1633 if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1638 if (!stream->lock) { /* no-op if stream locked */
1639 /* Build the fetch attributes. Unlike imap_fetch(), this tries not to
1640 * fetch data that is already cached. However, since it is based on the
1641 * message requested and not on any of the prefetched messages, it can
1642 * goof, either by fetching data already cached or not prefetching data
1643 * that isn't cached (but was cached in the message requested).
1644 * Fortunately, no great harm is done. If it doesn't prefetch the data,
1645 * it will get it when the affected message(s) are requested.
1647 if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID");
1648 else tmp[0] = '\0'; /* initialize command */
1649 /* need envelope? */
1650 if (!*env || (*env)->incomplete) {
1651 strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */
1652 if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
1653 if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
1654 hdrheader[LOCAL->cap.extlevel],
1655 imap_extrahdrs,hdrtrailer);
1656 else sprintf (tmp + strlen (tmp)," %s %s",
1657 hdrheader[LOCAL->cap.extlevel],hdrtrailer);
1660 /* need body? */
1661 if (body && !*b && LEVELIMAP2bis (stream))
1662 strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY");
1663 if (!elt->day) strcat (tmp," INTERNALDATE");
1664 if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
1665 if (tmp[0]) { /* anything to do? */
1666 tmp[0] = '('; /* make into a list */
1667 strcat (tmp," FLAGS)"); /* always get current flags */
1668 aatt.text = (void *) tmp; /* do the built command */
1669 if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
1670 /* failed, probably RFC-1176 server */
1671 if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
1672 aatt.text = (void *) "ALL";
1673 if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1674 /* doesn't have body capabilities */
1675 LOCAL->cap.imap2bis = NIL;
1676 else mm_log (reply->text,ERROR);
1678 else mm_log (reply->text,ERROR);
1682 if (body) { /* wants to return body */
1683 if (!*b && !LEVELIMAP2bis (stream)) {
1684 /* simulate body structure fetch for IMAP2 */
1685 *b = mail_initbody (mail_newbody ());
1686 (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type));
1687 ((*b)->parameter = mail_newbody_parameter ())->attribute =
1688 cpystr ("CHARSET");
1689 (*b)->parameter->value = cpystr ("US-ASCII");
1690 s = mail_fetch_text (stream,msgno,NIL,&i,flags);
1691 (*b)->size.bytes = i;
1692 while (i--) if (*s++ == '\n') (*b)->size.lines++;
1694 *body = *b; /* return the body */
1696 return *env; /* return the envelope */
1699 /* IMAP fetch message data
1700 * Accepts: MAIL stream
1701 * message number
1702 * section specifier
1703 * offset of first designated byte or 0 to start at beginning
1704 * maximum number of bytes or 0 for all bytes
1705 * lines to fetch if header
1706 * flags
1707 * Returns: T on success, NIL on failure
1710 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
1711 unsigned long first,unsigned long last,STRINGLIST *lines,
1712 long flags)
1714 int i;
1715 char *t,tmp[MAILTMPLEN],partial[40],seq[40];
1716 char *noextend,*nopartial,*nolines,*nopeek,*nononpeek;
1717 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1718 IMAPPARSEDREPLY *reply;
1719 IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg;
1720 noextend = nopartial = nolines = nopeek = nononpeek = NIL;
1721 /* does searching desire a lookahead? */
1722 if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) &&
1723 !stream->scache) {
1724 sprintf (seq,"%lu:%lu",msgno,
1725 (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs));
1726 aseq.type = SEQUENCE;
1727 aseq.text = (void *) seq;
1729 else { /* no, do it the easy way */
1730 aseq.type = NUMBER;
1731 aseq.text = (void *) msgno;
1733 aatt.type = ATOM; /* assume atomic attribute */
1734 alns.type = LIST; alns.text = (void *) lines;
1735 acls.type = BODYCLOSE; acls.text = (void *) partial;
1736 aflg.type = ATOM; aflg.text = (void *) "FLAGS";
1737 args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
1738 auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL;
1739 partial[0] = '\0'; /* initially no partial specifier */
1740 if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */
1741 /* HEADER fetching with special handling? */
1742 if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) {
1743 if (lines) { /* want specific header lines? */
1744 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1745 aatt.text = (void *) ((flags & FT_NOT) ?
1746 "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1747 args[2] = &alns; args[3] = &acls;
1749 /* must be prefetching */
1750 else aatt.text = (void *) ((flags & FT_PEEK) ?
1751 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1752 "(BODY[HEADER] BODY[TEXT])");
1754 else { /* simple case */
1755 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1756 aatt.text = (void *) section;
1757 args[2] = &acls;
1759 if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1);
1762 /* IMAP4 did not have:
1763 * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1764 * . TEXT body part (can simulate top-level with RFC822.TEXT or
1765 * RFC822.TEXT.PEEK)
1766 * . MIME body part
1767 * . (usable) partial fetching
1768 * . (usable) selective header line fetching
1770 else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */
1771 /* BODY[HEADER] becomes BODY.PEEK[0] */
1772 if (!strcmp (section,"HEADER"))
1773 aatt.text = (void *)
1774 ((flags & FT_PREFETCHTEXT) ?
1775 ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1776 "(BODY[0] RFC822.TEXT)") :
1777 ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]"));
1778 /* BODY[TEXT] becomes RFC822.TEXT */
1779 else if (!strcmp (section,"TEXT"))
1780 aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" :
1781 "RFC822.TEXT");
1782 else if (!section[0]) /* BODY[] becomes RFC822 */
1783 aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
1784 /* nested header */
1785 else if ((t = strstr (section,".HEADER")) != NULL) {
1786 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1787 args[2] = &acls; /* will need to close section */
1788 aatt.text = (void *) tmp; /* convert .HEADER to .0 */
1789 strncpy (tmp,section,t-section);
1790 strcpy (tmp+(t-section),".0");
1792 else { /* IMAP4 body part */
1793 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1794 args[2] = &acls; /* will need to close section */
1795 aatt.text = (void *) section;
1797 if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4";
1798 if (first || last) nopartial = "4";
1799 if (lines) nolines = "4";
1802 /* IMAP2bis did not have:
1803 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1804 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1805 * . MIME body part
1806 * . partial fetching
1807 * . selective header line fetching
1808 * . non-peeking header fetching
1809 * . peeking body fetching
1811 /* IMAP2bis compatibility */
1812 else if (LEVELIMAP2bis (stream)) {
1813 /* BODY[HEADER] becomes RFC822.HEADER */
1814 if (!strcmp (section,"HEADER")) {
1815 aatt.text = (void *)
1816 ((flags & FT_PREFETCHTEXT) ?
1817 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1818 if (flags & FT_PEEK) flags &= ~FT_PEEK;
1819 else nononpeek = "2bis";
1821 /* BODY[TEXT] becomes RFC822.TEXT */
1822 else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1823 /* BODY[] becomes RFC822 */
1824 else if (!section[0]) aatt.text = (void *) "RFC822";
1825 else { /* IMAP2bis body part */
1826 aatt.type = BODYTEXT;
1827 args[2] = &acls; /* will need to close section */
1828 aatt.text = (void *) section;
1830 if (strstr (section,".HEADER") || strstr (section,".MIME") ||
1831 strstr (section,".TEXT")) noextend = "2bis";
1832 if (first || last) nopartial = "2bis";
1833 if (lines) nolines = "2bis";
1834 if (flags & FT_PEEK) nopeek = "2bis";
1837 /* IMAP2 did not have:
1838 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1839 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1840 * . MIME body part
1841 * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1842 * . partial fetching
1843 * . selective header line fetching
1844 * . non-peeking header fetching
1845 * . peeking body fetching
1847 else { /* IMAP2 (RFC 1176/1064) compatibility */
1848 /* BODY[HEADER] */
1849 if (!strcmp (section,"HEADER")) {
1850 aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
1851 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1852 if (flags & FT_PEEK) flags &= ~FT_PEEK;
1853 nononpeek = "2";
1855 /* BODY[TEXT] becomes RFC822.TEXT */
1856 else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1857 /* BODY[1] treated like RFC822.TEXT */
1858 else if (!strcmp (section,"1")) {
1859 SIZEDTEXT text;
1860 MESSAGECACHE *elt = mail_elt (stream,msgno);
1861 /* have a cached RFC822.TEXT? */
1862 if (elt->private.msg.text.text.data) {
1863 text.size = elt->private.msg.text.text.size;
1864 /* should move instead of copy */
1865 text.data = memcpy (fs_get (text.size+1),
1866 elt->private.msg.text.text.data,text.size);
1867 (t = (char *) text.data)[text.size] = '\0';
1868 imap_cache (stream,msgno,"1",NIL,&text);
1869 return LONGT; /* don't have to do any fetches */
1871 /* otherwise do RFC822.TEXT */
1872 aatt.text = (void *) "RFC822.TEXT";
1874 /* BODY[] becomes RFC822 */
1875 else if (!section[0]) aatt.text = (void *) "RFC822";
1876 else noextend = "2"; /* how did we get here? */
1877 if (flags & FT_PEEK) nopeek = "2";
1878 if (first || last) nopartial = "2";
1879 if (lines) nolines = "2";
1882 /* Report unavailable functionalities. The application can use the helpful
1883 * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1884 * imap4r1.h to avoid triggering these errors. There aren't any workarounds
1885 * for these restrictions.
1887 if (noextend) {
1888 sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1889 noextend);
1890 mm_log (tmp,ERROR);
1891 return NIL; /* can't do anything close either */
1893 if (nopartial) {
1894 sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1895 nopartial);
1896 mm_notify (stream,tmp,WARN);
1898 if (nolines) {
1899 sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1900 nolines);
1901 mm_notify (stream,tmp,WARN);
1904 /* trying to do unsupported peek behavior? */
1905 if ((t = nopeek) || (t = nononpeek)) {
1906 /* get most recent \Seen setting */
1907 if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs)))
1908 mm_log (reply->text,WARN);
1909 /* note current setting of \Seen flag */
1910 if (!(i = mail_elt (stream,msgno)->seen)) {
1911 sprintf (tmp,nopeek ? /* only babble if \Seen not set */
1912 "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1913 "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t);
1914 mm_notify (stream,tmp,NIL);
1916 /* send the fetch command */
1917 if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1918 mm_log (reply->text,ERROR);
1919 return NIL; /* failure */
1921 /* send command if need to reset \Seen */
1922 if (((nopeek && !i && mail_elt (stream,msgno)->seen &&
1923 (aflg.text = "-FLAGS \\Seen")) ||
1924 ((nononpeek && !mail_elt (stream,msgno)->seen) &&
1925 (aflg.text = "+FLAGS \\Seen"))) &&
1926 !imap_OK (stream,reply = imap_send (stream,"STORE",auxargs)))
1927 mm_log (reply->text,WARN);
1929 /* simple case if traditional behavior */
1930 else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1931 mm_log (reply->text,ERROR);
1932 return NIL; /* failure */
1934 /* simulate BODY[1] return for RFC 1064/1176 */
1935 if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) {
1936 SIZEDTEXT text;
1937 MESSAGECACHE *elt = mail_elt (stream,msgno);
1938 text.size = elt->private.msg.text.text.size;
1939 /* should move instead of copy */
1940 text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data,
1941 text.size);
1942 (t = (char *) text.data)[text.size] = '\0';
1943 imap_cache (stream,msgno,"1",NIL,&text);
1945 return LONGT;
1948 /* IMAP fetch UID
1949 * Accepts: MAIL stream
1950 * message number
1951 * Returns: UID
1954 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
1956 MESSAGECACHE *elt;
1957 IMAPPARSEDREPLY *reply;
1958 IMAPARG *args[3],aseq,aatt;
1959 char *s,seq[MAILTMPLEN];
1960 unsigned long i,j,k;
1961 /* IMAP2 didn't have UIDs */
1962 if (!LEVELIMAP4 (stream)) return msgno;
1963 /* do we know its UID yet? */
1964 if (!(elt = mail_elt (stream,msgno))->private.uid) {
1965 aseq.type = SEQUENCE; aseq.text = (void *) seq;
1966 aatt.type = ATOM; aatt.text = (void *) "UID";
1967 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1968 sprintf (seq,"%lu",msgno);
1969 if ((k = imap_uidlookahead) != 0L) {/* build UID list */
1970 for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
1971 if (!mail_elt (stream,i)->private.uid) {
1972 s += strlen (s); /* find string end, see if nearing end */
1973 if ((s - seq) > (MAILTMPLEN - 20)) break;
1974 sprintf (s,",%lu",i); /* append message */
1975 for (j = i + 1, k--; /* hunt for last message without a UID */
1976 k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
1977 j++, k--);
1978 /* if different, make a range */
1979 if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1982 /* send "FETCH msgno UID" */
1983 if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1984 mm_log (reply->text,ERROR);
1986 return elt->private.uid; /* return our UID now */
1989 /* IMAP fetch message number from UID
1990 * Accepts: MAIL stream
1991 * UID
1992 * Returns: message number
1995 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
1997 IMAPPARSEDREPLY *reply;
1998 IMAPARG *args[3],aseq,aatt;
1999 char seq[MAILTMPLEN];
2000 int holes = 0;
2001 unsigned long i,msgno;
2002 /* IMAP2 didn't have UIDs */
2003 if (!LEVELIMAP4 (stream)) return uid;
2004 /* This really should be a binary search, but since there are likely to be
2005 * holes in the msgno->UID map it's hard to do.
2007 for (msgno = 1; msgno <= stream->nmsgs; msgno++) {
2008 if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T;
2009 else if (i == uid) return msgno;
2011 if (holes) { /* have holes in cache? */
2012 /* yes, have server hunt for UID */
2013 LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0;
2014 aseq.type = SEQUENCE; aseq.text = (void *) seq;
2015 aatt.type = ATOM; aatt.text = (void *) "UID";
2016 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
2017 sprintf (seq,"%lu",uid);
2018 /* send "UID FETCH uid UID" */
2019 if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
2020 mm_log (reply->text,ERROR);
2021 if (LOCAL->lastuid.uid) { /* got any results from FETCH? */
2022 if ((LOCAL->lastuid.uid == uid) &&
2023 /* what, me paranoid? */
2024 (LOCAL->lastuid.msgno <= stream->nmsgs) &&
2025 (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid))
2026 /* got it the easy way */
2027 return LOCAL->lastuid.msgno;
2028 /* sigh, do another linear search... */
2029 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2030 if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
2033 return 0; /* didn't find the UID anywhere */
2036 /* IMAP modify flags
2037 * Accepts: MAIL stream
2038 * sequence
2039 * flag(s)
2040 * option flags
2043 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
2045 char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
2046 IMAPPARSEDREPLY *reply;
2047 IMAPARG *args[4],aseq,ascm,aflg;
2048 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2049 flags & ST_UID);
2050 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2051 ascm.type = ATOM; ascm.text = (void *)
2052 ((flags & ST_SET) ?
2053 ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
2054 "+Flags.silent" : "+Flags") :
2055 ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
2056 "-Flags.silent" : "-Flags"));
2057 aflg.type = FLAGS; aflg.text = (void *) flag;
2058 args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
2059 /* send "STORE sequence +Flags flag" */
2060 if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
2061 mm_log (reply->text,ERROR);
2064 /* IMAP search for messages
2065 * Accepts: MAIL stream
2066 * character set
2067 * search program
2068 * option flags
2069 * Returns: T on success, NIL on failure
2072 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
2074 unsigned long i,j,k;
2075 char *s;
2076 IMAPPARSEDREPLY *reply;
2077 MESSAGECACHE *elt;
2079 if(LOCAL->cap.x_gm_ext1 && pgm && pgm->x_gm_ext1)
2080 return imap_search_x_gm_ext1(stream, charset, pgm, flags);
2082 if ((flags & SE_NOSERVER) || /* if want to do local search */
2083 LOCAL->loser || /* or loser */
2084 (!LEVELIMAP4 (stream) && /* or old server but new functions... */
2085 (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or ||
2086 pgm->not || pgm->header || pgm->larger || pgm->smaller ||
2087 pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
2088 pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
2089 pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
2090 pgm->followup_to || pgm->references)) ||
2091 (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) {
2092 if ((flags & SE_NOLOCAL) ||
2093 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2094 return NIL;
2096 /* do silly ALL or seq-only search locally */
2097 else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) &&
2098 !(pgm->uid || pgm->or || pgm->not ||
2099 pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc ||
2100 pgm->subject || pgm->body || pgm->text ||
2101 pgm->larger || pgm->smaller ||
2102 pgm->sentbefore || pgm->senton || pgm->sentsince ||
2103 pgm->before || pgm->on || pgm->since ||
2104 pgm->answered || pgm->unanswered ||
2105 pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft ||
2106 pgm->flagged || pgm->unflagged || pgm->recent || pgm->old ||
2107 pgm->seen || pgm->unseen ||
2108 pgm->keyword || pgm->unkeyword ||
2109 pgm->return_path || pgm->sender ||
2110 pgm->reply_to || pgm->in_reply_to || pgm->message_id ||
2111 pgm->newsgroups || pgm->followup_to || pgm->references)) {
2112 if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER))
2113 fatal ("impossible mail_search_default() failure");
2116 else { /* do server-based SEARCH */
2117 char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH";
2118 IMAPARG *args[4],apgm,aatt,achs;
2119 SEARCHSET *ss,*set;
2120 args[1] = args[2] = args[3] = NIL;
2121 apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
2122 if (charset) { /* optional charset argument requested */
2123 args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
2124 aatt.type = ATOM; aatt.text = (void *) "CHARSET";
2125 achs.type = ASTRING; achs.text = (void *) charset;
2127 else args[0] = &apgm; /* no charset argument */
2128 /* tell receiver that these will be UIDs */
2129 LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
2130 reply = imap_send (stream,cmd,args);
2131 /* did server barf with that searchpgm? */
2132 if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) &&
2133 !strcmp (reply->key,"BAD")) {
2134 LOCAL->filter = T; /* retry, filtering SEARCH results */
2135 for (i = 1; i <= stream->nmsgs; i++)
2136 mail_elt (stream,i)->private.filter = NIL;
2137 for (set = ss; set; set = set->next) if ((i = set->first) != 0L) {
2138 /* single message becomes one-message range */
2139 if (!(j = set->last)) j = i;
2140 else if (j < i) { /* swap reversed range */
2141 i = set->last; j = set->first;
2143 while (i <= j) mail_elt (stream,i++)->private.filter = T;
2145 pgm->msgno = NIL; /* and without the searchset */
2146 reply = imap_send (stream,cmd,args);
2147 pgm->msgno = ss; /* restore searchset */
2148 LOCAL->filter = NIL; /* turn off filtering */
2150 LOCAL->uidsearch = NIL;
2151 /* do locally if server won't grok */
2152 if (!strcmp (reply->key,"BAD")) {
2153 if ((flags & SE_NOLOCAL) ||
2154 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2155 return NIL;
2157 else if (!imap_OK (stream,reply)) {
2158 mm_log (reply->text,ERROR);
2159 return NIL;
2163 /* can never pre-fetch with a short cache */
2164 if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
2165 !stream->scache) { /* only if prefetching permitted */
2166 s = LOCAL->tmp; /* build sequence in temporary buffer */
2167 *s = '\0'; /* initially nothing */
2168 /* search through mailbox */
2169 for (i = 1; k && (i <= stream->nmsgs); ++i)
2170 /* for searched messages with no envelope */
2171 if ((elt = mail_elt (stream,i)) && elt->searched &&
2172 !mail_elt (stream,i)->private.msg.env) {
2173 /* prepend with comma if not first time */
2174 if (LOCAL->tmp[0]) *s++ = ',';
2175 sprintf (s,"%lu",j = i);/* output message number */
2176 s += strlen (s); /* point at end of string */
2177 k--; /* count one up */
2178 /* search for possible end of range */
2179 while (k && (i < stream->nmsgs) &&
2180 (elt = mail_elt (stream,i+1))->searched &&
2181 !elt->private.msg.env) i++,k--;
2182 if (i != j) { /* if a range */
2183 sprintf (s,":%lu",i); /* output delimiter and end of range */
2184 s += strlen (s); /* point at end of string */
2186 if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
2188 if (LOCAL->tmp[0]) { /* anything to pre-fetch? */
2189 /* pre-fetch envelopes for the first imap_prefetch number of messages */
2190 if (!imap_OK (stream,reply =
2191 imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV +
2192 ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
2193 ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
2194 mm_log (reply->text,ERROR);
2195 fs_give ((void **) &s); /* flush copy of sequence */
2198 return LONGT;
2201 /* IMAP sort messages
2202 * Accepts: mail stream
2203 * character set
2204 * search program
2205 * sort program
2206 * option flags
2207 * Returns: vector of sorted message sequences or NIL if error
2210 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
2211 SORTPGM *pgm,long flags)
2213 unsigned long i,j,start,last;
2214 unsigned long *ret = NIL;
2215 pgm->nmsgs = 0; /* start off with no messages */
2216 /* can use server-based sort? */
2217 if (LEVELSORT (stream) && !(flags & SE_NOSERVER) &&
2218 (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) {
2219 char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
2220 IMAPARG *args[4],apgm,achs,aspg;
2221 IMAPPARSEDREPLY *reply;
2222 SEARCHSET *ss = NIL;
2223 SEARCHPGM *tsp = NIL;
2224 apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
2225 achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
2226 aspg.type = SEARCHPROGRAM;
2227 /* did he provide a searchpgm? */
2228 if (!(aspg.text = (void *) spg)) {
2229 for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2230 if (mail_elt (stream,i)->searched) {
2231 if (ss) { /* continuing a sequence */
2232 if (i == last + 1) last = i;
2233 else { /* end of range */
2234 if (last != start) ss->last = last;
2235 (ss = ss->next = mail_newsearchset ())->first = i;
2236 start = last = i; /* begin a new range */
2239 else { /* first time, start new searchpgm */
2240 (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2241 ss->first = start = last = i;
2244 /* nothing to sort if no messages */
2245 if (!(aspg.text = (void *) tsp)) return NIL;
2246 /* else install last sequence */
2247 if (last != start) ss->last = last;
2250 args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2251 /* ask server to do it */
2252 reply = imap_send (stream,cmd,args);
2253 if (tsp) { /* was there a temporary searchpgm? */
2254 aspg.text = NIL; /* yes, flush it */
2255 mail_free_searchpgm (&tsp);
2256 /* did server barf with that searchpgm? */
2257 if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2258 LOCAL->filter = T; /* retry, filtering SORT/THREAD results */
2259 reply = imap_send (stream,cmd,args);
2260 LOCAL->filter = NIL; /* turn off filtering */
2263 /* do locally if server barfs */
2264 if (!strcmp (reply->key,"BAD"))
2265 return (flags & SE_NOLOCAL) ? NIL :
2266 imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER);
2267 /* server sorted OK? */
2268 else if (imap_OK (stream,reply)) {
2269 pgm->nmsgs = LOCAL->sortsize;
2270 ret = LOCAL->sortdata;
2271 LOCAL->sortdata = NIL; /* mail program is responsible for flushing */
2273 else mm_log (reply->text,ERROR);
2276 /* not much can do if short caching */
2277 else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
2278 else { /* try to be a bit more clever */
2279 char *s,*t;
2280 unsigned long len;
2281 MESSAGECACHE *elt;
2282 SORTCACHE **sc;
2283 SORTPGM *sp;
2284 long ftflags = 0;
2285 /* see if need envelopes */
2286 for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) {
2287 case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
2288 ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL);
2290 if (spg) { /* only if a search needs to be done */
2291 int silent = stream->silent;
2292 stream->silent = T; /* don't pass up mm_searched() events */
2293 /* search for messages */
2294 mail_search_full (stream,charset,spg,flags & SE_NOSERVER);
2295 stream->silent = silent; /* restore silence state */
2297 /* initialize progress counters */
2298 pgm->nmsgs = pgm->progress.cached = 0;
2299 /* pass 1: count messages to sort */
2300 for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
2301 if ((elt = mail_elt (stream,i))->searched) {
2302 pgm->nmsgs++;
2303 if (ftflags ? !elt->private.msg.env : !elt->day) {
2304 if (s) { /* continuing a sequence */
2305 if (i == last + 1) last = i;
2306 else { /* end of range */
2307 if (last != start) sprintf (t,":%lu,%lu",last,i);
2308 else sprintf (t,",%lu",i);
2309 start = last = i; /* begin a new range */
2310 if ((len - (j = ((t += strlen (t)) - s)) < 20)) {
2311 fs_resize ((void **) &s,len += MAILTMPLEN);
2312 t = s + j; /* relocate current pointer */
2316 else { /* first time, start new buffer */
2317 s = (char *) fs_get (len = MAILTMPLEN);
2318 sprintf (s,"%lu",start = last = i);
2319 t = s + strlen (s); /* end of buffer */
2323 /* last sequence */
2324 if (last != start) sprintf (t,":%lu",last);
2325 if (s) { /* load cache for all messages being sorted */
2326 imap_fetch (stream,s,ftflags);
2327 fs_give ((void **) &s);
2329 if (pgm->nmsgs) { /* pass 2: sort cache */
2330 sortresults_t sr = (sortresults_t)
2331 mail_parameters (NIL,GET_SORTRESULTS,NIL);
2332 sc = mail_sort_loadcache (stream,pgm);
2333 /* pass 3: sort messages */
2334 if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
2335 fs_give ((void **) &sc); /* don't need sort vector any more */
2336 /* also return via callback if requested */
2337 if (sr) (*sr) (stream,ret,pgm->nmsgs);
2340 return ret;
2343 /* IMAP thread messages
2344 * Accepts: mail stream
2345 * thread type
2346 * character set
2347 * search program
2348 * option flags
2349 * Returns: thread node tree or NIL if error
2352 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
2353 SEARCHPGM *spg,long flags)
2355 THREADER *thr;
2356 if (!(flags & SE_NOSERVER) &&
2357 (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger))))
2358 /* does server have this threader type? */
2359 for (thr = LOCAL->cap.threader; thr; thr = thr->next)
2360 if (!compare_cstring (thr->name,type))
2361 return imap_thread_work (stream,type,charset,spg,flags);
2362 /* server doesn't support it, do locally */
2363 return (flags & SE_NOLOCAL) ? NIL:
2364 mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2367 /* IMAP thread messages worker routine
2368 * Accepts: mail stream
2369 * thread type
2370 * character set
2371 * search program
2372 * option flags
2373 * Returns: thread node tree
2376 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
2377 SEARCHPGM *spg,long flags)
2379 unsigned long i,start,last;
2380 char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
2381 IMAPARG *args[4],apgm,achs,aspg;
2382 IMAPPARSEDREPLY *reply;
2383 THREADNODE *ret = NIL;
2384 SEARCHSET *ss = NIL;
2385 SEARCHPGM *tsp = NIL;
2386 apgm.type = ATOM; apgm.text = (void *) type;
2387 achs.type = ASTRING;
2388 achs.text = (void *) (charset ? charset : "US-ASCII");
2389 aspg.type = SEARCHPROGRAM;
2390 /* did he provide a searchpgm? */
2391 if (!(aspg.text = (void *) spg)) {
2392 for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2393 if (mail_elt (stream,i)->searched) {
2394 if (ss) { /* continuing a sequence */
2395 if (i == last + 1) last = i;
2396 else { /* end of range */
2397 if (last != start) ss->last = last;
2398 (ss = ss->next = mail_newsearchset ())->first = i;
2399 start = last =i; /* begin a new range */
2402 else { /* first time, start new searchpgm */
2403 (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2404 ss->first = start = last = i;
2407 /* nothing to sort if no messages */
2408 if (!(aspg.text = (void *) tsp)) return NIL;
2409 /* else install last sequence */
2410 if (last != start) ss->last = last;
2413 args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2414 /* ask server to do it */
2415 reply = imap_send (stream,cmd,args);
2416 if (tsp) { /* was there a temporary searchpgm? */
2417 aspg.text = NIL; /* yes, flush it */
2418 mail_free_searchpgm (&tsp);
2419 /* did server barf with that searchpgm? */
2420 if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2421 LOCAL->filter = T; /* retry, filtering SORT/THREAD results */
2422 reply = imap_send (stream,cmd,args);
2423 LOCAL->filter = NIL; /* turn off filtering */
2426 /* do locally if server barfs */
2427 if (!strcmp (reply->key,"BAD"))
2428 ret = (flags & SE_NOLOCAL) ? NIL:
2429 mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2430 /* server threaded OK? */
2431 else if (imap_OK (stream,reply)) {
2432 ret = LOCAL->threaddata;
2433 LOCAL->threaddata = NIL; /* mail program is responsible for flushing */
2435 else mm_log (reply->text,ERROR);
2436 return ret;
2439 /* IMAP ping mailbox
2440 * Accepts: MAIL stream
2441 * Returns: T if stream still alive, else NIL
2444 long imap_ping (MAILSTREAM *stream)
2446 return (LOCAL->netstream && /* send "NOOP" */
2447 imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
2451 /* IMAP check mailbox
2452 * Accepts: MAIL stream
2455 void imap_check (MAILSTREAM *stream)
2457 /* send "CHECK" */
2458 IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
2459 mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
2462 /* IMAP expunge mailbox
2463 * Accepts: MAIL stream
2464 * sequence to expunge if non-NIL
2465 * expunge options
2466 * Returns: T if success, NIL if failure
2469 long imap_expunge (MAILSTREAM *stream,char *sequence,long options)
2471 long ret = NIL;
2472 IMAPPARSEDREPLY *reply = NIL;
2473 if (sequence) { /* wants selective expunging? */
2474 if (options & EX_UID) { /* UID EXPUNGE form? */
2475 if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */
2476 IMAPARG *args[2],aseq;
2477 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2478 args[0] = &aseq; args[1] = NIL;
2479 ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args));
2481 else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR);
2483 /* otherwise try to make into UID EXPUNGE */
2484 else if (mail_sequence (stream,sequence)) {
2485 unsigned long i,j;
2486 char *t = (char *) fs_get (IMAPTMPLEN);
2487 char *s = t;
2488 /* search through mailbox */
2489 for (*s = '\0', i = 1; i <= stream->nmsgs; ++i)
2490 if (mail_elt (stream,i)->sequence) {
2491 if (t[0]) *s++ = ','; /* prepend with comma if not first time */
2492 sprintf (s,"%lu",mail_uid (stream,j = i));
2493 s += strlen (s); /* point at end of string */
2494 /* search for possible end of range */
2495 while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++;
2496 if (i != j) { /* output end of range */
2497 sprintf (s,":%lu",mail_uid (stream,i));
2498 s += strlen (s); /* point at end of string */
2500 if ((s - t) > (IMAPTMPLEN - 50)) {
2501 mm_log ("Excessively complex sequence",ERROR);
2502 return NIL;
2505 /* now do as UID EXPUNGE */
2506 ret = imap_expunge (stream,t,EX_UID);
2507 fs_give ((void **) &t);
2510 /* ordinary EXPUNGE */
2511 else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL));
2512 if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR);
2513 return ret;
2516 /* IMAP copy message(s)
2517 * Accepts: MAIL stream
2518 * sequence
2519 * destination mailbox
2520 * option flags
2521 * Returns: T if successful else NIL
2524 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
2526 char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
2527 char *s;
2528 long ret = NIL;
2529 IMAPPARSEDREPLY *reply;
2530 IMAPARG *args[3],aseq,ambx;
2531 imapreferral_t ir =
2532 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2533 mailproxycopy_t pc =
2534 (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
2535 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2536 flags & CP_UID);
2537 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2538 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2539 args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
2540 /* note mailbox in case APPENDUID */
2541 LOCAL->appendmailbox = mailbox;
2542 /* send "COPY sequence mailbox" */
2543 ret = imap_OK (stream,reply = imap_send (stream,cmd,args));
2544 LOCAL->appendmailbox = NIL; /* no longer appending */
2545 if (ret) { /* success, delete messages if move */
2546 if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
2547 ST_SET + ((flags&CP_UID) ? ST_UID : NIL));
2549 /* failed, do referral action if any */
2550 else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
2551 (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
2552 ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL));
2553 /* otherwise issue error message */
2554 else mm_log (reply->text,ERROR);
2555 return ret;
2558 /* IMAP mail append message from stringstruct
2559 * Accepts: MAIL stream
2560 * destination mailbox
2561 * append callback
2562 * data for callback
2563 * Returns: T if append successful, else NIL
2566 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
2568 MAILSTREAM *st = stream;
2569 IMAPARG *args[3],ambx,amap;
2570 IMAPPARSEDREPLY *reply = NIL;
2571 APPENDDATA map;
2572 char tmp[MAILTMPLEN];
2573 long debug = stream ? stream->debug : NIL;
2574 long ret = NIL;
2575 imapreferral_t ir =
2576 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2577 /* mailbox must be good */
2578 if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2579 /* create a stream if given one no good */
2580 if ((stream && LOCAL && LOCAL->netstream) ||
2581 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2582 (debug ? OP_DEBUG : NIL)))) {
2583 /* note mailbox in case APPENDUID */
2584 LOCAL->appendmailbox = mailbox;
2585 /* use multi-append? */
2586 if (LEVELMULTIAPPEND (stream)) {
2587 ambx.type = ASTRING; ambx.text = (void *) tmp;
2588 amap.type = MULTIAPPEND; amap.text = (void *) &map;
2589 map.af = af; map.data = data;
2590 args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2591 /* success if OK */
2592 ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args));
2593 LOCAL->appendmailbox = NIL;
2595 /* do succession of single appends */
2596 else while ((*af) (stream,data,&map.flags,&map.date,&map.message) &&
2597 map.message &&
2598 (ret = imap_OK (stream,reply =
2599 imap_append_single (stream,tmp,map.flags,
2600 map.date,map.message))));
2601 LOCAL->appendmailbox = NIL;
2602 /* don't do referrals if success or no reply */
2603 if (ret || !reply) mailbox = NIL;
2604 /* otherwise generate referral */
2605 else if (!(mailbox = (ir && LOCAL->referral) ?
2606 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2607 mm_log (reply->text,ERROR);
2608 /* close temporary stream */
2609 if (st != stream) stream = mail_close (stream);
2610 if (mailbox) /* chase referral if any */
2611 ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date,
2612 map.message,&map,debug);
2614 else mm_log ("Can't access server for append",ERROR);
2616 return ret; /* return */
2619 /* IMAP mail append message referral retry
2620 * Accepts: destination mailbox
2621 * temporary buffer
2622 * append callback
2623 * data for callback
2624 * flags from previous attempt
2625 * date from previous attempt
2626 * message stringstruct from previous attempt
2627 * options (currently non-zero to set OP_DEBUG)
2628 * Returns: T if append successful, else NIL
2631 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
2632 char *flags,char *date,STRING *message,
2633 APPENDDATA *map,long options)
2635 MAILSTREAM *stream;
2636 IMAPARG *args[3],ambx,amap;
2637 IMAPPARSEDREPLY *reply;
2638 imapreferral_t ir =
2639 (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL);
2640 /* barf if bad mailbox */
2641 while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2642 /* create a stream if given one no good */
2643 if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2644 (options ? OP_DEBUG : NIL)))) {
2645 sprintf (tmp,"Can't access referral server: %.80s",mailbox);
2646 mm_log (tmp,ERROR);
2647 return NIL;
2649 /* got referral server, use multi-append? */
2650 if (LEVELMULTIAPPEND (stream)) {
2651 ambx.type = ASTRING; ambx.text = (void *) tmp;
2652 amap.type = MULTIAPPENDREDO; amap.text = (void *) map;
2653 args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2654 /* do multiappend on referral site */
2655 if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) {
2656 mail_close (stream); /* multiappend OK, close stream */
2657 return LONGT; /* all done */
2660 /* do multiple single appends */
2661 else while (imap_OK (stream,reply =
2662 imap_append_single (stream,tmp,flags,date,message)))
2663 if (!((*af) (stream,data,&flags,&date,&message) && message)) {
2664 mail_close (stream); /* last message, close stream */
2665 return LONGT; /* all done */
2667 /* generate error if no nested referral */
2668 if (!(mailbox = (ir && LOCAL->referral) ?
2669 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2670 mm_log (reply->text,ERROR);
2671 mail_close (stream); /* close previous referral stream */
2673 return NIL; /* bogus mailbox */
2676 /* IMAP append single message
2677 * Accepts: mail stream
2678 * destination mailbox
2679 * initial flags
2680 * internal date
2681 * stringstruct of message to append
2682 * Returns: reply from append
2685 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
2686 char *flags,char *date,STRING *message)
2688 MESSAGECACHE elt;
2689 IMAPARG *args[5],ambx,aflg,adat,amsg;
2690 IMAPPARSEDREPLY *reply;
2691 char tmp[MAILTMPLEN];
2692 int i;
2693 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2694 args[i = 0] = &ambx;
2695 if (flags) {
2696 aflg.type = FLAGS; aflg.text = (void *) flags;
2697 args[++i] = &aflg;
2699 if (date) { /* ensure date in INTERNALDATE format */
2700 if (!mail_parse_date (&elt,date)) {
2701 /* flush previous reply */
2702 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
2703 /* build new fake reply */
2704 LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*");
2705 LOCAL->reply.key = "BAD";
2706 LOCAL->reply.text = "Bad date in append";
2707 return &LOCAL->reply;
2709 adat.type = ASTRING;
2710 adat.text = (void *) (date = mail_date (tmp,&elt));
2711 args[++i] = &adat;
2713 amsg.type = LITERAL; amsg.text = (void *) message;
2714 args[++i] = &amsg;
2715 args[++i] = NIL;
2716 /* easy if IMAP4[rev1] */
2717 if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args);
2718 else { /* try the IMAP2bis way */
2719 args[1] = &amsg; args[2] = NIL;
2720 reply = imap_send (stream,"APPEND",args);
2722 return reply;
2725 /* IMAP garbage collect stream
2726 * Accepts: Mail stream
2727 * garbage collection flags
2730 void imap_gc (MAILSTREAM *stream,long gcflags)
2732 unsigned long i;
2733 MESSAGECACHE *elt;
2734 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2735 /* make sure the cache is large enough */
2736 (*mc) (stream,stream->nmsgs,CH_SIZE);
2737 if (gcflags & GC_TEXTS) { /* garbage collect texts? */
2738 if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
2739 if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) != NULL)
2740 imap_gc_body (elt->private.msg.body);
2741 imap_gc_body (stream->body);
2743 /* gc cache if requested and unlocked */
2744 if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
2745 if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
2746 (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
2749 /* IMAP garbage collect body texts
2750 * Accepts: body to GC
2753 void imap_gc_body (BODY *body)
2755 PART *part;
2756 if (body) { /* have a body? */
2757 if (body->mime.text.data) /* flush MIME data */
2758 fs_give ((void **) &body->mime.text.data);
2759 /* flush text contents */
2760 if (body->contents.text.data)
2761 fs_give ((void **) &body->contents.text.data);
2762 body->mime.text.size = body->contents.text.size = 0;
2763 /* multipart? */
2764 if (body->type == TYPEMULTIPART)
2765 for (part = body->nested.part; part; part = part->next)
2766 imap_gc_body (&part->body);
2767 /* MESSAGE/RFC822? */
2768 else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
2769 imap_gc_body (body->nested.msg->body);
2770 if (body->nested.msg->full.text.data)
2771 fs_give ((void **) &body->nested.msg->full.text.data);
2772 if (body->nested.msg->header.text.data)
2773 fs_give ((void **) &body->nested.msg->header.text.data);
2774 if (body->nested.msg->text.text.data)
2775 fs_give ((void **) &body->nested.msg->text.text.data);
2776 body->nested.msg->full.text.size = body->nested.msg->header.text.size =
2777 body->nested.msg->text.text.size = 0;
2782 /* IMAP get capabilities
2783 * Accepts: mail stream
2786 void imap_capability (MAILSTREAM *stream)
2788 THREADER *thr,*t;
2789 LOCAL->gotcapability = NIL; /* flush any previous capabilities */
2790 /* request new capabilities */
2791 imap_send (stream,"CAPABILITY",NIL);
2792 if (!LOCAL->gotcapability) { /* did server get any? */
2793 /* no, flush threaders just in case */
2794 if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
2795 fs_give ((void **) &t->name);
2796 thr = t->next;
2797 fs_give ((void **) &t);
2799 /* zap most capabilities */
2800 memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
2801 /* assume IMAP2bis server if failure */
2802 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
2806 /* IMAP set ACL
2807 * Accepts: mail stream
2808 * mailbox name
2809 * authentication identifier
2810 * new access rights
2811 * Returns: T on success, NIL on failure
2814 long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights)
2816 IMAPARG *args[4],ambx,aid,art;
2817 ambx.type = aid.type = art.type = ASTRING;
2818 ambx.text = (void *) mailbox; aid.text = (void *) id;
2819 art.text = (void *) rights;
2820 args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL;
2821 return imap_acl_work (stream,"SETACL",args);
2825 /* IMAP delete ACL
2826 * Accepts: mail stream
2827 * mailbox name
2828 * authentication identifier
2829 * Returns: T on success, NIL on failure
2832 long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id)
2834 IMAPARG *args[3],ambx,aid;
2835 ambx.type = aid.type = ASTRING;
2836 ambx.text = (void *) mailbox; aid.text = (void *) id;
2837 args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2838 return imap_acl_work (stream,"DELETEACL",args);
2842 /* IMAP get ACL
2843 * Accepts: mail stream
2844 * mailbox name
2845 * Returns: T on success with data returned via callback, NIL on failure
2848 long imap_getacl (MAILSTREAM *stream,char *mailbox)
2850 IMAPARG *args[2],ambx;
2851 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2852 args[0] = &ambx; args[1] = NIL;
2853 return imap_acl_work (stream,"GETACL",args);
2856 /* IMAP list rights
2857 * Accepts: mail stream
2858 * mailbox name
2859 * authentication identifier
2860 * Returns: T on success with data returned via callback, NIL on failure
2863 long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id)
2865 IMAPARG *args[3],ambx,aid;
2866 ambx.type = aid.type = ASTRING;
2867 ambx.text = (void *) mailbox; aid.text = (void *) id;
2868 args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2869 return imap_acl_work (stream,"LISTRIGHTS",args);
2873 /* IMAP my rights
2874 * Accepts: mail stream
2875 * mailbox name
2876 * Returns: T on success with data returned via callback, NIL on failure
2879 long imap_myrights (MAILSTREAM *stream,char *mailbox)
2881 IMAPARG *args[2],ambx;
2882 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2883 args[0] = &ambx; args[1] = NIL;
2884 return imap_acl_work (stream,"MYRIGHTS",args);
2888 /* IMAP ACL worker routine
2889 * Accepts: mail stream
2890 * command
2891 * command arguments
2892 * Returns: T on success, NIL on failure
2895 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[])
2897 long ret = NIL;
2898 if (LEVELACL (stream)) { /* send command */
2899 IMAPPARSEDREPLY *reply;
2900 if (imap_OK (stream,reply = imap_send (stream,command,args)))
2901 ret = LONGT;
2902 else mm_log (reply->text,ERROR);
2904 else mm_log ("ACL not available on this IMAP server",ERROR);
2905 return ret;
2908 /* IMAP set quota
2909 * Accepts: mail stream
2910 * quota root name
2911 * resource limit list as a stringlist
2912 * Returns: T on success with data returned via callback, NIL on failure
2915 long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits)
2917 long ret = NIL;
2918 if (LEVELQUOTA (stream)) { /* send "SETQUOTA" */
2919 IMAPPARSEDREPLY *reply;
2920 IMAPARG *args[3],aqrt,alim;
2921 aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2922 alim.type = SNLIST; alim.text = (void *) limits;
2923 args[0] = &aqrt; args[1] = &alim; args[2] = NIL;
2924 if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args)))
2925 ret = LONGT;
2926 else mm_log (reply->text,ERROR);
2928 else mm_log ("Quota not available on this IMAP server",ERROR);
2929 return ret;
2932 IDLIST *imap_parse_idlist (char *text)
2934 IDLIST *ret = NULL;
2935 char *s;
2936 char tmp[MAILTMPLEN];
2938 if(text == NULL) return NULL;
2939 for(s = text; *s == ' '; s++); /* move past spaces */
2940 if(*s == '(') s++;
2941 if(*s++ == '"'){
2942 char *t;
2943 for(t = s; *t && *t != '"'; t++);
2944 if(*t == '"'){
2945 ret = fs_get(sizeof(IDLIST));
2946 *t = '\0';
2947 ret->name = cpystr(s);
2948 *t = '"';
2949 for(s = t+1; *s == ' '; s++); /* move past spaces */
2950 if(*s++ == '"'){
2951 for(t = s; *t && *t != '"'; t++);
2952 if(*t == '"'){
2953 *t = '\0';
2954 ret->value = cpystr(s);
2955 *t++ = '"';
2956 ret->next = imap_parse_idlist(t);
2958 else {
2959 sprintf(tmp,"ID value not found for name %.80s, at %.80s", ret->name, s);
2960 fs_give((void **)&ret->name);
2961 fs_give((void **)&ret);
2962 mm_log (tmp, NIL); /* this is an technically an error */
2965 else { /* failed!, quit */
2966 sprintf(tmp,"ID name \"%.80s\" has no value", ret->name);
2967 fs_give((void **)&ret->name);
2968 fs_give((void **)&ret);
2969 mm_log (tmp, NIL); /* this is an technically an error */
2973 return ret;
2976 long imap_setid (MAILSTREAM *stream, IDLIST *idlist)
2978 long ret = NIL;
2979 if (LEVELID (stream)) { /* send "ID (params)" */
2980 IMAPPARSEDREPLY *reply;
2981 IMAPARG *args[2],aqrt;
2982 IDLIST *list;
2983 char *qroot, *p;
2984 long len = 0L;
2986 if(idlist == NULL) return ret;
2987 for (list = idlist; list != NULL; list = list->next)
2988 len += strlen(list->name) + strlen(list->value) + 6;
2989 if(len > 0){
2990 len += 1L; /* in case there is only one field */
2991 qroot = fs_get(len+1);
2992 memset((void *)&qroot[0], 0, len+1);
2993 p = qroot;
2994 for (list = idlist; list != NULL; list = list->next){
2995 sprintf(p, " \"%s\" \"%s\"", list->name, list->value);
2996 p += strlen(p);
2998 *p = ')';
2999 qroot[0] = '(';
3000 aqrt.type = ATOM; aqrt.text = (void *) qroot;
3001 args[0] = &aqrt; args[1] = NIL;
3002 if (imap_OK (stream,reply = imap_send (stream,"ID",args)))
3003 ret = LONGT;
3004 else mm_log (reply->text,ERROR);
3005 if(qroot) fs_give((void **) &qroot);
3006 } else mm_log("Empty or malformed ID list", ERROR);
3008 else mm_log ("ID capability not available on this IMAP server",ERROR);
3009 return ret;
3012 /* IMAP get quota
3013 * Accepts: mail stream
3014 * quota root name
3015 * Returns: T on success with data returned via callback, NIL on failure
3018 long imap_getquota (MAILSTREAM *stream,char *qroot)
3020 long ret = NIL;
3021 if (LEVELQUOTA (stream)) { /* send "GETQUOTA" */
3022 IMAPPARSEDREPLY *reply;
3023 IMAPARG *args[2],aqrt;
3024 aqrt.type = ASTRING; aqrt.text = (void *) qroot;
3025 args[0] = &aqrt; args[1] = NIL;
3026 if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args)))
3027 ret = LONGT;
3028 else mm_log (reply->text,ERROR);
3030 else mm_log ("Quota not available on this IMAP server",ERROR);
3031 return ret;
3035 /* IMAP get quota root
3036 * Accepts: mail stream
3037 * mailbox name
3038 * Returns: T on success with data returned via callback, NIL on failure
3041 long imap_getquotaroot (MAILSTREAM *stream,char *mailbox)
3043 long ret = NIL;
3044 if (LEVELQUOTA (stream)) { /* send "GETQUOTAROOT" */
3045 IMAPPARSEDREPLY *reply;
3046 IMAPARG *args[2],ambx;
3047 ambx.type = ASTRING; ambx.text = (void *) mailbox;
3048 args[0] = &ambx; args[1] = NIL;
3049 if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args)))
3050 ret = LONGT;
3051 else mm_log (reply->text,ERROR);
3053 else mm_log ("Quota not available on this IMAP server",ERROR);
3054 return ret;
3057 /* Internal routines */
3060 /* IMAP send command
3061 * Accepts: MAIL stream
3062 * command
3063 * argument list
3064 * Returns: parsed reply
3067 #define CMDBASE LOCAL->tmp /* command base */
3069 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
3071 IMAPPARSEDREPLY *reply;
3072 IMAPARG *arg,**arglst;
3073 SORTPGM *spg;
3074 STRINGLIST *list;
3075 SIZEDTEXT st;
3076 APPENDDATA *map;
3077 sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL);
3078 size_t i;
3079 void *a;
3080 char c,*s,*t,tag[10];
3081 stream->unhealthy = NIL; /* make stream healthy again */
3082 /* gensym a new tag */
3083 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
3084 if (!LOCAL->netstream) /* make sure have a session */
3085 return imap_fake (stream,tag,"[CLOSED] IMAP connection lost");
3086 mail_lock (stream); /* lock up the stream */
3087 if (sc) /* tell client sending a command */
3088 (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") &&
3089 compare_cstring (cmd,"STORE") &&
3090 compare_cstring (cmd,"SEARCH")) ?
3091 NIL : SC_EXPUNGEDEFERRED));
3092 /* ignore referral from previous command */
3093 if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
3094 sprintf (CMDBASE,"%s %s",tag,cmd);
3095 s = CMDBASE + strlen (CMDBASE);
3096 if ((arglst = args) != NULL) while ((arg = *arglst++) != NULL) {
3097 *s++ = ' '; /* delimit argument with space */
3098 switch (arg->type) {
3099 case ATOM: /* atom */
3100 for (t = (char *) arg->text; *t; *s++ = *t++);
3101 break;
3102 case NUMBER: /* number */
3103 sprintf (s,"%lu",(unsigned long) arg->text);
3104 s += strlen (s);
3105 break;
3106 case FLAGS: /* flag list as a single string */
3107 if (*(t = (char *) arg->text) != '(') {
3108 *s++ = '('; /* wrap parens around string */
3109 while (*t) *s++ = *t++;
3110 *s++ = ')'; /* wrap parens around string */
3112 else while (*t) *s++ = *t++;
3113 break;
3114 case ASTRING: /* atom or string, must be literal? */
3115 st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3116 if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) != NULL)
3117 return reply;
3118 break;
3119 case LITERAL: /* literal, as a stringstruct */
3120 if ((reply = imap_send_literal (stream,tag,&s,arg->text)) != NULL) return reply;
3121 break;
3123 case LIST: /* list of strings */
3124 list = (STRINGLIST *) arg->text;
3125 c = '('; /* open paren */
3126 do { /* for each list item */
3127 *s++ = c; /* write prefix character */
3128 if ((reply = imap_send_astring (stream,tag,&s,&list->text,NIL,
3129 CMDBASE+MAXCOMMAND)) != NULL) return reply;
3130 c = ' '; /* prefix character for subsequent strings */
3132 while ((list = list->next) != NULL);
3133 *s++ = ')'; /* close list */
3134 break;
3135 case SEARCHPROGRAM: /* search program */
3136 if ((reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
3137 CMDBASE+MAXCOMMAND)) != NULL)
3138 return reply;
3139 break;
3140 case SORTPROGRAM: /* search program */
3141 c = '('; /* open paren */
3142 for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) {
3143 *s++ = c; /* write prefix */
3144 if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++);
3145 switch (spg->function) {
3146 case SORTDATE:
3147 for (t = "DATE"; *t; *s++ = *t++);
3148 break;
3149 case SORTARRIVAL:
3150 for (t = "ARRIVAL"; *t; *s++ = *t++);
3151 break;
3152 case SORTFROM:
3153 for (t = "FROM"; *t; *s++ = *t++);
3154 break;
3155 case SORTSUBJECT:
3156 for (t = "SUBJECT"; *t; *s++ = *t++);
3157 break;
3158 case SORTTO:
3159 for (t = "TO"; *t; *s++ = *t++);
3160 break;
3161 case SORTCC:
3162 for (t = "CC"; *t; *s++ = *t++);
3163 break;
3164 case SORTSIZE:
3165 for (t = "SIZE"; *t; *s++ = *t++);
3166 break;
3167 default:
3168 fatal ("Unknown sort program function in imap_send()!");
3170 c = ' '; /* prefix character for subsequent items */
3172 *s++ = ')'; /* close list */
3173 break;
3175 case BODYTEXT: /* body section */
3176 for (t = "BODY["; *t; *s++ = *t++);
3177 for (t = (char *) arg->text; *t; *s++ = *t++);
3178 break;
3179 case BODYPEEK: /* body section */
3180 for (t = "BODY.PEEK["; *t; *s++ = *t++);
3181 for (t = (char *) arg->text; *t; *s++ = *t++);
3182 break;
3183 case BODYCLOSE: /* close bracket and possible length */
3184 s[-1] = ']'; /* no leading space */
3185 for (t = (char *) arg->text; *t; *s++ = *t++);
3186 break;
3187 case SEQUENCE: /* sequence */
3188 if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND)
3189 while (*t) *s++ = *t++; /* easy case */
3190 else {
3191 mail_unlock (stream); /* unlock stream */
3192 a = arg->text; /* save original sequence pointer */
3193 arg->type = ATOM; /* make recursive call be faster */
3194 do { /* break up into multiple commands */
3195 if (i <= MAXCOMMAND) {/* final part? */
3196 reply = imap_send (stream,cmd,args);
3197 i = 0; /* and mark as done */
3199 else { /* still needs to be split further */
3200 if (!(t = strchr (t + MAXCOMMAND - 30,',')) ||
3201 ((t - (char *) arg->text) > MAXCOMMAND))
3202 fatal ("impossible over-long sequence");
3203 *t = '\0'; /* tie off sequence at point of split*/
3204 /* recurse to do this part */
3205 reply = imap_send (stream,cmd,args);
3206 *t++ = ','; /* restore the comma in case something cares */
3207 /* punt if error */
3208 if (!imap_OK (stream,reply)) break;
3209 /* calculate size of remaining sequence */
3210 i -= (t - (char *) arg->text);
3211 /* point to new remaining sequence */
3212 arg->text = (void *) t;
3214 } while (i);
3215 arg->type = SEQUENCE; /* restore in case something cares */
3216 arg->text = a;
3217 return reply; /* return result */
3219 break;
3220 case LISTMAILBOX: /* astring with wildcards */
3221 st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3222 if ((reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND)) != NULL)
3223 return reply;
3224 break;
3226 case MULTIAPPEND: /* append multiple messages */
3227 /* get package pointer */
3228 map = (APPENDDATA *) arg->text;
3229 if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)||
3230 !map->message) {
3231 STRING es;
3232 INIT (&es,mail_string,"",0);
3233 return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3234 reply : imap_fake (stream,tag,"Server zero-length literal error");
3236 case MULTIAPPENDREDO: /* redo multiappend */
3237 /* get package pointer */
3238 map = (APPENDDATA *) arg->text;
3239 do { /* make sure date valid if given */
3240 char datetmp[MAILTMPLEN];
3241 MESSAGECACHE elt;
3242 STRING es;
3243 if (!map->date || mail_parse_date (&elt,map->date)) {
3244 if ((t = map->flags) != NULL) { /* flags given? */
3245 if (*t != '(') {
3246 *s++ = '('; /* wrap parens around string */
3247 while (*t) *s++ = *t++;
3248 *s++ = ')'; /* wrap parens around string */
3250 else while (*t) *s++ = *t++;
3251 *s++ = ' '; /* delimit with space */
3253 if (map->date) { /* date given? */
3254 st.size = strlen ((char *) (st.data = (unsigned char *)
3255 mail_date (datetmp,&elt)));
3256 if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,
3257 CMDBASE+MAXCOMMAND)) != NULL) return reply;
3258 *s++ = ' '; /* delimit with space */
3260 if ((reply = imap_send_literal (stream,tag,&s,map->message)) != NULL)
3261 return reply;
3262 /* get next message */
3263 if ((*map->af) (stream,map->data,&map->flags,&map->date,
3264 &map->message)) {
3265 /* have a message, delete next in command */
3266 if (map->message) *s++ = ' ';
3267 continue; /* loop back for next message */
3270 /* bad date or need to abort */
3271 INIT (&es,mail_string,"",0);
3272 return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3273 reply : imap_fake (stream,tag,"Server zero-length literal error");
3274 break; /* exit the loop */
3275 } while (map->message);
3276 break;
3278 case SNLIST: /* list of string/number pairs */
3279 list = (STRINGLIST *) arg->text;
3280 c = '('; /* open paren */
3281 do { /* for each list item */
3282 *s++ = c; /* write prefix character */
3283 if (list) { /* sigh, QUOTA has bizarre syntax! */
3284 for (t = (char *) list->text.data; *t; *s++ = *t++);
3285 sprintf (s," %lu",list->text.size);
3286 s += strlen (s);
3287 c = ' '; /* prefix character for subsequent strings */
3290 while ((list = list->next) != NULL);
3291 *s++ = ')'; /* close list */
3292 break;
3293 default:
3294 fatal ("Unknown argument type in imap_send()!");
3297 /* send the command */
3298 reply = imap_sout (stream,tag,CMDBASE,&s);
3299 mail_unlock (stream); /* unlock stream */
3300 return reply;
3303 /* IMAP send atom-string
3304 * Accepts: MAIL stream
3305 * reply tag
3306 * pointer to current position pointer of output bigbuf
3307 * atom-string to output
3308 * flag if list_wildcards allowed
3309 * maximum to write as atom or qstring
3310 * Returns: error reply or NIL if success
3313 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
3314 SIZEDTEXT *as,long wildok,char *limit)
3316 unsigned long j;
3317 char c;
3318 STRING st;
3319 /* default to atom unless empty or loser */
3320 int qflag = (as->size && !LOCAL->loser) ? NIL : T;
3321 /* in case needed */
3322 INIT (&st,mail_string,(void *) as->data,as->size);
3323 /* always write literal if no space */
3324 if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st);
3325 for (j = 0; j < as->size; j++) switch (c = as->data[j]) {
3326 default: /* all other characters */
3327 if (!(c & 0x80)) { /* must not be 8bit */
3328 if (c <= ' ') qflag = T; /* must quote if a CTL */
3329 break;
3331 case '\0': /* not a CHAR */
3332 case '\012': case '\015': /* not a TEXT-CHAR */
3333 case '"': case '\\': /* quoted-specials (IMAP2 required this) */
3334 return imap_send_literal (stream,tag,s,&st);
3335 case '*': case '%': /* list_wildcards */
3336 if (wildok) break; /* allowed if doing the wild thing */
3337 /* atom_specials */
3338 case '(': case ')': case '{': case ' ': case 0x7f:
3339 #if 0
3340 case '"': case '\\': /* quoted-specials (could work in IMAP4) */
3341 #endif
3342 qflag = T; /* must use quoted string format */
3343 break;
3345 if (qflag) *(*s)++ = '"'; /* write open quote */
3346 for (j = 0; j < as->size; j++) *(*s)++ = as->data[j];
3347 if (qflag) *(*s)++ = '"'; /* write close quote */
3348 return NIL;
3351 /* IMAP send literal
3352 * Accepts: MAIL stream
3353 * reply tag
3354 * pointer to current position pointer of output bigbuf
3355 * literal to output as stringstruct
3356 * Returns: error reply or NIL if success
3359 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
3360 STRING *st)
3362 IMAPPARSEDREPLY *reply;
3363 unsigned long i = SIZE (st);
3364 unsigned long j;
3365 sprintf (*s,"{%lu}",i); /* write literal count */
3366 *s += strlen (*s); /* size of literal count */
3367 /* send the command */
3368 reply = imap_sout (stream,tag,CMDBASE,s);
3369 if (strcmp (reply->tag,"+")) {/* prompt for more data? */
3370 mail_unlock (stream); /* no, give up */
3371 return reply;
3373 while (i) { /* dump the text */
3374 if (st->cursize) { /* if text to do in this chunk */
3375 /* RFC 3501 technically forbids NULs in literals. Normally, the
3376 * delivering MTA would take care of MIME converting the message text
3377 * so that it is NUL-free. If it doesn't, then we have the choice of
3378 * either violating IMAP by sending NULs, corrupting the data, or going
3379 * to lots of work to do MIME conversion in the IMAP server.
3381 * No current stringstruct driver objects to having its buffer patched.
3382 * If this ever changes, it will be necessary to change this kludge.
3384 /* patch NULs to C1 control */
3385 for (j = 0; j < st->cursize; ++j)
3386 if (!st->curpos[j]) st->curpos[j] = 0x80;
3387 if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) {
3388 mail_unlock (stream);
3389 return imap_fake (stream,tag,"[CLOSED] IMAP connection broken (data)");
3391 i -= st->cursize; /* note that we wrote out this much */
3392 st->curpos += (st->cursize - 1);
3393 st->cursize = 0;
3395 (*st->dtb->next) (st); /* advance to next buffer's worth */
3397 return NIL; /* success */
3400 #define ADD_STRING(X, Y) { \
3401 if(remain > 0){ \
3402 sprintf (u, (X), (Y)); \
3403 len = strlen(u); \
3404 if(len < remain){ \
3405 strncpy(t, u, remain); \
3406 t[remain-1] = '\0'; \
3407 remain -= len; \
3408 t += strlen (t); \
3414 long imap_search_x_gm_ext1 (MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long flags)
3416 char *cmd = (flags & SE_UID) ? "UID SEARCH X-GM-RAW" : "SEARCH X-GM-RAW";
3417 char *t, s[MAILTMPLEN+1], u[MAILTMPLEN];
3418 IMAPARG *args[4],apgm;
3419 IMAPPARSEDREPLY *reply;
3420 unsigned long i,j,k;
3421 MESSAGECACHE *elt;
3422 size_t remain = sizeof(s), len;
3424 u[0] = s[0] = '\0';
3425 t = s;
3426 args[1] = args[2] = args[3] = NIL;
3428 if(pgm->x_gm_ext1)
3429 ADD_STRING(" %s", pgm->x_gm_ext1->text.data);
3431 #if 0 /* maybe later */
3432 if (pgm->larger)
3433 ADD_STRING(" larger:%lu", pgm->larger);
3435 if (pgm->smaller)
3436 ADD_STRING(" smaller:%lu", pgm->smaller);
3438 if (pgm->deleted)
3439 ADD_STRING(" %s", "in:trash");
3441 if (pgm->undeleted)
3442 ADD_STRING(" %s", "-in:trash");
3444 if (pgm->draft)
3445 ADD_STRING(" %s", "in:drafts");
3447 if (pgm->undraft)
3448 ADD_STRING(" %s", "-in:drafts");
3450 if (pgm->flagged)
3451 ADD_STRING(" %s", "is:starred");
3453 if (pgm->unflagged)
3454 ADD_STRING(" %s", "-is:starred");
3456 if (pgm->seen)
3457 ADD_STRING(" %s", "-is:unread");
3459 if (pgm->unseen)
3460 ADD_STRING(" %s", "is:unread");
3462 if (pgm->keyword){
3463 STRINGLIST *sl;
3464 for(sl = pgm->keyword; remain > 0 && sl; sl = sl->next)
3465 ADD_STRING(" label:%s", sl->text.data);
3468 if (pgm->unkeyword){
3469 STRINGLIST *sl;
3470 for(sl = pgm->unkeyword; remain > 0 && sl; sl = sl->next)
3471 ADD_STRING(" -label:%s", sl->text.data);
3474 if (pgm->sentbefore){
3475 unsigned short date = pgm->sentbefore;
3476 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3477 (date >> 5) & 0xf, date & 0x1f);
3478 ADD_STRING(" before:%s", v);
3481 if (pgm->sentsince){
3482 unsigned short date = pgm->sentsince;
3483 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3484 (date >> 5) & 0xf, date & 0x1f);
3485 ADD_STRING(" older:%s", v);
3488 if (pgm->before){
3489 unsigned short date = pgm->before;
3490 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3491 (date >> 5) & 0xf, date & 0x1f);
3492 ADD_STRING(" before:%s", v);
3495 if (pgm->since){
3496 unsigned short date = pgm->since;
3497 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3498 (date >> 5) & 0xf, date & 0x1f);
3499 ADD_STRING(" before:%s", v);
3502 if (pgm->older){
3503 sprintf(v, "%dd", pgm->older/86400);
3504 ADD_STRING(" older_than:%s", v);
3507 if (pgm->younger){
3508 sprintf(v, "%dd", pgm->younger/86400);
3509 ADD_STRING(" newer_than:%s", v);
3512 if(pgm->bcc){
3513 STRINGLIST *sl;
3514 ADD_STRING("%s", pgm->bcc->next ? " {" : " ");
3515 for(sl = pgm->bcc; remain > 0 && sl; sl = sl->next){
3516 ADD_STRING("bcc:%s", sl->text.data);
3517 ADD_STRING("%s", sl->next ? " " : "}");
3521 if(pgm->cc){
3522 STRINGLIST *sl;
3523 ADD_STRING("%s", pgm->cc->next ? " {" : " ");
3524 for(sl = pgm->cc; remain > 0 && sl; sl = sl->next){
3525 ADD_STRING("cc:%s", sl->text.data);
3526 ADD_STRING("%s", sl->next ? " " : "}");
3530 if(pgm->from){
3531 STRINGLIST *sl;
3532 ADD_STRING("%s", pgm->from->next ? " {" : " ");
3533 for(sl = pgm->from; remain > 0 && sl; sl = sl->next){
3534 ADD_STRING("from:%s", sl->text.data);
3535 ADD_STRING("%s", sl->next ? " " : (pgm->from->next ? "}" : ""));
3539 if(pgm->to){
3540 STRINGLIST *sl;
3541 ADD_STRING("%s", pgm->to->next ? " {" : " ");
3542 for(sl = pgm->to; remain > 0 && sl; sl = sl->next){
3543 ADD_STRING("to:%s", sl->text.data);
3544 ADD_STRING("%s", sl->next ? " " : (pgm->to->next ? "}" : ""));
3548 if(pgm->subject){
3549 STRINGLIST *sl;
3550 ADD_STRING("%s", pgm->subject->next ? " {" : " ");
3551 for(sl = pgm->subject; remain > 0 && sl; sl = sl->next){
3552 ADD_STRING("subject:(%s)", sl->text.data);
3553 ADD_STRING("%s", sl->next ? " " : (pgm->subject->next ? "}" : ""));
3557 if(pgm->body){
3558 STRINGLIST *sl;
3559 ADD_STRING("%s", pgm->body->next ? " {" : " ");
3560 for(sl = pgm->body; remain > 0 && sl; sl = sl->next){
3561 ADD_STRING(" %s", sl->text.data);
3562 ADD_STRING("%s", sl->next ? " " : (pgm->body->next ? "}" : ""));
3566 if (mail_valid_net_parse (stream->mailbox,&mb)){
3567 p = strchr(mb.mailbox, '/');
3568 ADD_STRING(" in:%s", p ? p+1 : "inbox");
3571 #endif /* maybe later */
3573 s[0] = '\"';
3574 strcat(t, "\"");
3576 apgm.type = ATOM; apgm.text = (void *) s;
3577 args[0] = &apgm;
3578 args[1] = NIL;
3579 LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
3580 reply = imap_send (stream,cmd,args);
3581 LOCAL->uidsearch = NIL;
3582 /* do locally if server won't grok */
3583 if (!strcmp (reply->key,"BAD")) {
3584 if ((flags & SE_NOLOCAL) ||
3585 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
3586 return NIL;
3588 else if (!imap_OK (stream,reply)) {
3589 mm_log (reply->text,ERROR);
3590 return NIL;
3593 /* can never pre-fetch with a short cache */
3594 if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
3595 !stream->scache) { /* only if prefetching permitted */
3596 t = LOCAL->tmp; /* build sequence in temporary buffer */
3597 *t = '\0'; /* initially nothing */
3598 /* search through mailbox */
3599 for (i = 1; k && (i <= stream->nmsgs); ++i)
3600 /* for searched messages with no envelope */
3601 if ((elt = mail_elt (stream,i)) && elt->searched &&
3602 !mail_elt (stream,i)->private.msg.env) {
3603 /* prepend with comma if not first time */
3604 if (LOCAL->tmp[0]) *t++ = ',';
3605 sprintf (t,"%lu",j = i);/* output message number */
3606 t += strlen (t); /* point at end of string */
3607 k--; /* count one up */
3608 /* search for possible end of range */
3609 while (k && (i < stream->nmsgs) &&
3610 (elt = mail_elt (stream,i+1))->searched &&
3611 !elt->private.msg.env) i++,k--;
3612 if (i != j) { /* if a range */
3613 sprintf (t,":%lu",i); /* output delimiter and end of range */
3614 t += strlen (t); /* point at end of string */
3616 if ((t - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
3618 if (LOCAL->tmp[0]) { /* anything to pre-fetch? */
3619 /* pre-fetch envelopes for the first imap_prefetch number of messages */
3620 if (!imap_OK (stream,reply =
3621 imap_fetch (stream,t = cpystr (LOCAL->tmp),FT_NEEDENV +
3622 ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
3623 ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
3624 mm_log (reply->text,ERROR);
3625 fs_give ((void **) &t); /* flush copy of sequence */
3628 return LONGT;
3631 /* IMAP send search program
3632 * Accepts: MAIL stream
3633 * reply tag
3634 * base pointer if trimming needed
3635 * pointer to current position pointer of output bigbuf
3636 * search program to output
3637 * pointer to limit guideline
3638 * Returns: error reply or NIL if success
3642 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
3643 char **s,SEARCHPGM *pgm,char *limit)
3645 IMAPPARSEDREPLY *reply;
3646 SEARCHHEADER *hdr;
3647 SEARCHOR *pgo;
3648 SEARCHPGMLIST *pgl;
3649 char *t;
3650 /* trim if called recursively */
3651 if (base) *s = imap_send_spgm_trim (base,*s,NIL);
3652 base = *s; /* this is the new base */
3653 /* default searchpgm */
3654 for (t = "ALL"; *t; *(*s)++ = *t++);
3655 if (!pgm) return NIL; /* done if NIL searchpgm */
3656 if ((pgm->msgno && /* message sequences */
3657 (pgm->msgno->next || /* trim away first:last */
3658 (pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) &&
3659 (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) ||
3660 (pgm->uid &&
3661 (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit))))
3662 return reply;
3663 /* message sizes */
3664 if (pgm->larger) {
3665 sprintf (*s," LARGER %lu",pgm->larger);
3666 *s += strlen (*s);
3668 if (pgm->smaller) {
3669 sprintf (*s," SMALLER %lu",pgm->smaller);
3670 *s += strlen (*s);
3673 /* message flags */
3674 if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++);
3675 if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++);
3676 if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++);
3677 if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++);
3678 if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++);
3679 if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++);
3680 if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++);
3681 if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++);
3682 if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++);
3683 if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++);
3684 if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++);
3685 if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++);
3686 if ((pgm->keyword && /* keywords */
3687 (reply = imap_send_slist (stream,tag,base,s," KEYWORD ",pgm->keyword,
3688 limit))) ||
3689 (pgm->unkeyword &&
3690 (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ",
3691 pgm->unkeyword,limit))))
3692 return reply;
3693 /* sent date ranges */
3694 if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
3695 if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
3696 if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
3697 /* internal date ranges */
3698 if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
3699 if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
3700 if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
3701 if (pgm->older) {
3702 sprintf (*s," OLDER %lu",pgm->older);
3703 *s += strlen (*s);
3705 if (pgm->younger) {
3706 sprintf (*s," YOUNGER %lu",pgm->younger);
3707 *s += strlen (*s);
3709 /* search texts */
3710 if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ",
3711 pgm->bcc,limit))) ||
3712 (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc,
3713 limit))) ||
3714 (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ",
3715 pgm->from,limit))) ||
3716 (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to,
3717 limit))))
3718 return reply;
3719 if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ",
3720 pgm->subject,limit))) ||
3721 (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ",
3722 pgm->body,limit))) ||
3723 (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ",
3724 pgm->text,limit))))
3725 return reply;
3727 /* Note that these criteria are not supported by IMAP and have to be
3728 emulated */
3729 if ((pgm->return_path &&
3730 (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ",
3731 pgm->return_path,limit))) ||
3732 (pgm->sender &&
3733 (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ",
3734 pgm->sender,limit))) ||
3735 (pgm->reply_to &&
3736 (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ",
3737 pgm->reply_to,limit))) ||
3738 (pgm->in_reply_to &&
3739 (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ",
3740 pgm->in_reply_to,limit))) ||
3741 (pgm->message_id &&
3742 (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ",
3743 pgm->message_id,limit))) ||
3744 (pgm->newsgroups &&
3745 (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ",
3746 pgm->newsgroups,limit))) ||
3747 (pgm->followup_to &&
3748 (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ",
3749 pgm->followup_to,limit))) ||
3750 (pgm->references &&
3751 (reply = imap_send_slist (stream,tag,base,s," HEADER References ",
3752 pgm->references,limit)))) return reply;
3754 /* all other headers */
3755 if ((hdr = pgm->header) != NULL) do {
3756 *s = imap_send_spgm_trim (base,*s," HEADER ");
3757 if ((reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) != NULL)
3758 return reply;
3759 *(*s)++ = ' ';
3760 if ((reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) != NULL)
3761 return reply;
3762 } while ((hdr = hdr->next) != NULL);
3763 for (pgo = pgm->or; pgo; pgo = pgo->next) {
3764 *s = imap_send_spgm_trim (base,*s," OR (");
3765 if ((reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit)) != NULL)
3766 return reply;
3767 for (t = ") ("; *t; *(*s)++ = *t++);
3768 if ((reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) != NULL)
3769 return reply;
3770 *(*s)++ = ')';
3772 for (pgl = pgm->not; pgl; pgl = pgl->next) {
3773 *s = imap_send_spgm_trim (base,*s," NOT (");
3774 if ((reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit)) != NULL)
3775 return reply;
3776 *(*s)++ = ')';
3778 /* trim if needed */
3779 *s = imap_send_spgm_trim (base,*s,NIL);
3780 return NIL; /* search program written OK */
3784 /* Write new text and trim extraneous "ALL" from searchpgm
3785 * Accepts: pointer to start of searchpgm or NIL
3786 * current end pointer
3787 * new text to write or NIL
3788 * Returns: new end pointer, trimmed if needed
3791 char *imap_send_spgm_trim (char *base,char *s,char *text)
3793 char *t;
3794 /* write new text */
3795 if (text) for (t = text; *t; *s++ = *t++);
3796 /* need to trim? */
3797 if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') &&
3798 (base[2] == 'L') && (base[3] == ' ')) {
3799 memmove (base,t,s - t); /* yes, blat down remaining text */
3800 s -= 4; /* and reduce current pointer */
3802 return s; /* return new end pointer */
3805 /* IMAP send search set
3806 * Accepts: MAIL stream
3807 * current command tag
3808 * base pointer if trimming needed
3809 * pointer to current position pointer of output bigbuf
3810 * search set to output
3811 * message prefix
3812 * maximum output pointer
3813 * Returns: NIL if success, error reply if error
3816 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
3817 char **s,SEARCHSET *set,char *prefix,
3818 char *limit)
3820 IMAPPARSEDREPLY *reply;
3821 STRING st;
3822 char c,*t;
3823 char *start = *s;
3824 /* trim and write prefix */
3825 *s = imap_send_spgm_trim (base,*s,prefix);
3826 /* run down search list */
3827 for (c = NIL; set && (*s < limit); set = set->next, c = ',') {
3828 if (c) *(*s)++ = c; /* write delimiter and first value */
3829 if (set->first == 0xffffffff) *(*s)++ = '*';
3830 else {
3831 sprintf (*s,"%lu",set->first);
3832 *s += strlen (*s);
3834 /* have a second value? */
3835 if (set->last && (set->first != set->last)) {
3836 *(*s)++ = ':'; /* write delimiter and second value */
3837 if (set->last == 0xffffffff) *(*s)++ = '*';
3838 else {
3839 sprintf (*s,"%lu",set->last);
3840 *s += strlen (*s);
3844 if (set) { /* insert "OR" in front of incomplete set */
3845 memmove (start + 3,start,*s - start);
3846 memcpy (start," OR",3);
3847 *s += 3; /* point to end of buffer */
3848 /* write glue that is equivalent to ALL */
3849 for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++);
3850 /* but broken by a literal */
3851 INIT (&st,mail_string,(void *) "FOO",3);
3852 if ((reply = imap_send_literal (stream,tag,s,&st)) != NULL) return reply;
3853 *(*s)++ = ')'; /* close glue */
3854 if ((reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) != NULL)
3855 return reply;
3856 *(*s)++ = ')'; /* close second OR argument */
3858 return NIL;
3861 /* IMAP send search list
3862 * Accepts: MAIL stream
3863 * reply tag
3864 * base pointer if trimming needed
3865 * pointer to current position pointer of output bigbuf
3866 * name of search list
3867 * search list to output
3868 * maximum output pointer
3869 * Returns: NIL if success, error reply if error
3872 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
3873 char **s,char *name,STRINGLIST *list,
3874 char *limit)
3876 IMAPPARSEDREPLY *reply;
3877 do {
3878 *s = imap_send_spgm_trim (base,*s,name);
3879 base = NIL; /* no longer need trimming */
3880 reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit);
3882 while (!reply && (list = list->next));
3883 return reply;
3887 /* IMAP send search date
3888 * Accepts: pointer to current position pointer of output bigbuf
3889 * field name
3890 * search date to output
3893 void imap_send_sdate (char **s,char *name,unsigned short date)
3895 sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
3896 months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
3897 *s += strlen (*s);
3900 /* IMAP send buffered command to sender
3901 * Accepts: MAIL stream
3902 * reply tag
3903 * string
3904 * pointer to string tail pointer
3905 * Returns: reply
3908 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s)
3910 IMAPPARSEDREPLY *reply;
3911 if (stream->debug) { /* output debugging telemetry */
3912 **s = '\0';
3913 mail_dlog (base,LOCAL->sensitive);
3915 *(*s)++ = '\015'; /* append CRLF */
3916 *(*s)++ = '\012';
3917 **s = '\0';
3918 reply = net_sout (LOCAL->netstream,base,*s - base) ?
3919 imap_reply (stream,tag) :
3920 imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)");
3921 *s = base; /* restart buffer */
3922 return reply;
3926 /* IMAP send null-terminated string to sender
3927 * Accepts: MAIL stream
3928 * string
3929 * Returns: T if success, else NIL
3932 long imap_soutr (MAILSTREAM *stream,char *string)
3934 long ret;
3935 unsigned long i;
3936 char *s;
3937 if (stream->debug) mm_dlog (string);
3938 sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1),
3939 "%s\015\012",string);
3940 ret = net_sout (LOCAL->netstream,s,i);
3941 fs_give ((void **) &s);
3942 return ret;
3945 /* IMAP get reply
3946 * Accepts: MAIL stream
3947 * tag to search or NIL if want a greeting
3948 * Returns: parsed reply, never NIL
3951 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
3953 IMAPPARSEDREPLY *reply;
3954 while (LOCAL->netstream) { /* parse reply from server */
3955 if ((reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) != NULL) {
3956 /* continuation ready? */
3957 if (!strcmp (reply->tag,"+")) return reply;
3958 /* untagged data? */
3959 else if (!strcmp (reply->tag,"*")) {
3960 imap_parse_unsolicited (stream,reply);
3961 if (!tag) return reply; /* return if just wanted greeting */
3963 else { /* tagged data */
3964 if (tag && !compare_cstring (tag,reply->tag)) return reply;
3965 /* report bogon */
3966 sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
3967 (char *) reply->tag,(char *) reply->key,(char *) reply->text);
3968 mm_notify (stream,LOCAL->tmp,WARN);
3969 stream->unhealthy = T;
3973 return imap_fake (stream,tag,
3974 "[CLOSED] IMAP connection broken (server response)");
3977 /* IMAP parse reply
3978 * Accepts: MAIL stream
3979 * text of reply
3980 * Returns: parsed reply, or NIL if can't parse at least a tag and key
3984 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
3986 char *r;
3987 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
3988 /* init fields in case error */
3989 LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL;
3990 if (!(LOCAL->reply.line = text)) {
3991 /* NIL text means the stream died */
3992 if (LOCAL->netstream) net_close (LOCAL->netstream);
3993 LOCAL->netstream = NIL;
3994 return NIL;
3996 if (stream->debug) mm_dlog (LOCAL->reply.line);
3997 if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) {
3998 mm_notify (stream,"IMAP server sent a blank line",WARN);
3999 stream->unhealthy = T;
4000 return NIL;
4002 /* non-continuation replies */
4003 if (strcmp (LOCAL->reply.tag,"+")) {
4004 /* parse key */
4005 if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) {
4006 /* determine what is missing */
4007 sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",
4008 (char *) LOCAL->reply.tag);
4009 mm_notify (stream,LOCAL->tmp,WARN);
4010 stream->unhealthy = T;
4011 return NIL; /* can't parse this text */
4013 ucase (LOCAL->reply.key); /* canonicalize key to upper */
4014 /* get text as well, allow empty text */
4015 if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
4016 LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
4018 else { /* special handling of continuation */
4019 LOCAL->reply.key = "BAD"; /* so it barfs if not expecting continuation */
4020 if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
4021 LOCAL->reply.text = "";
4023 return &LOCAL->reply; /* return parsed reply */
4026 /* IMAP fake reply when stream determined to be dead
4027 * Accepts: MAIL stream
4028 * tag
4029 * text of fake reply (must start with "[CLOSED]")
4030 * Returns: parsed reply
4033 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
4035 mm_notify (stream,text,BYE); /* send bye alert */
4036 if (LOCAL->netstream) net_close (LOCAL->netstream);
4037 LOCAL->netstream = NIL; /* farewell, dear NET stream... */
4038 /* flush previous reply */
4039 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
4040 /* build new fake reply */
4041 LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*");
4042 LOCAL->reply.key = "NO";
4043 LOCAL->reply.text = text;
4044 return &LOCAL->reply; /* return parsed reply */
4048 /* IMAP check for OK response in tagged reply
4049 * Accepts: MAIL stream
4050 * parsed reply
4051 * Returns: T if OK else NIL
4054 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
4056 long ret = NIL;
4057 /* OK - operation succeeded */
4058 if (!strcmp (reply->key,"OK")) {
4059 imap_parse_response (stream,reply->text,NIL,NIL);
4060 ret = T;
4062 /* NO - operation failed */
4063 else if (!strcmp (reply->key,"NO"))
4064 imap_parse_response (stream,reply->text,WARN,NIL);
4065 else { /* BAD - operation rejected */
4066 if (!strcmp (reply->key,"BAD")) {
4067 imap_parse_response (stream,reply->text,ERROR,NIL);
4068 sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text);
4070 /* bad protocol received */
4071 else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
4072 (char *) reply->key,(char *) reply->text);
4073 mm_log (LOCAL->tmp,ERROR); /* either way, this is not good */
4075 return ret;
4078 /* IMAP parse and act upon unsolicited reply
4079 * Accepts: MAIL stream
4080 * parsed reply
4083 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
4085 unsigned long i = 0;
4086 unsigned long j,msgno;
4087 unsigned char *s,*t;
4088 char *r;
4089 /* see if key is a number */
4090 if (isdigit (*reply->key)) {
4091 msgno = strtoul (reply->key,(char **) &s,10);
4092 if (*s) { /* better be nothing after number */
4093 sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4094 (char *) reply->key);
4095 mm_notify (stream,LOCAL->tmp,WARN);
4096 stream->unhealthy = T;
4097 return;
4099 if (!reply->text) { /* better be some data */
4100 mm_notify (stream,"Missing message data",WARN);
4101 stream->unhealthy = T;
4102 return;
4104 /* get message data type, canonicalize upper */
4105 s = ucase (strtok_r (reply->text," ",&r));
4106 t = strtok_r (NIL,"\n",&r); /* and locate the text after it */
4107 /* now take the action */
4108 /* change in size of mailbox */
4109 if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs))
4110 mail_exists (stream,msgno);
4111 else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs))
4112 mail_recent (stream,msgno);
4113 else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) {
4114 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4115 MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
4116 if (elt) imap_gc_body (elt->private.msg.body);
4117 /* notify upper level */
4118 mail_expunged (stream,msgno);
4121 else if (t && (!strcmp (s,"FETCH") || !strcmp (s,"STORE")) &&
4122 msgno && (msgno <= stream->nmsgs)) {
4123 char *prop;
4124 GETS_DATA md;
4125 ENVELOPE **e;
4126 MESSAGECACHE *elt = mail_elt (stream,msgno);
4127 ENVELOPE *env = NIL;
4128 imapenvelope_t ie =
4129 (imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL);
4130 ++t; /* skip past open parenthesis */
4131 /* parse Lisp-form property list */
4132 while ((prop = (strtok_r (t," )",&r))) && (t = strtok_r (NIL,"\n",&r))) {
4133 INIT_GETS (md,stream,elt->msgno,NIL,0,0);
4134 e = NIL; /* not pointing at any envelope yet */
4135 /* canonicalize property, parse it */
4136 if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t);
4137 else if (!strcmp (prop,"INTERNALDATE") &&
4138 (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) {
4139 if (!mail_parse_date (elt,s)) {
4140 sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s);
4141 mm_notify (stream,LOCAL->tmp,WARN);
4142 stream->unhealthy = T;
4143 /* slam in default so we don't try again */
4144 mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000");
4146 fs_give ((void **) &s);
4148 /* unique identifier */
4149 else if (!strcmp (prop,"UID")) {
4150 LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10);
4151 LOCAL->lastuid.msgno = elt->msgno;
4153 else if (!strcmp (prop,"ENVELOPE")) {
4154 if (stream->scache) { /* short cache, flush old stuff */
4155 mail_free_body (&stream->body);
4156 stream->msgno = elt->msgno;
4157 e = &stream->env; /* get pointer to envelope */
4159 else e = &elt->private.msg.env;
4160 imap_parse_envelope (stream,e,&t,reply);
4162 else if (!strncmp (prop,"BODY",4)) {
4163 if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) {
4164 BODY **body;
4165 if (stream->scache){/* short cache, flush old stuff */
4166 if (stream->msgno != msgno) {
4167 mail_free_envelope (&stream->env);
4168 sprintf (LOCAL->tmp,"Body received for %lu but current is %lu",
4169 msgno,stream->msgno);
4170 stream->msgno = msgno;
4172 /* get pointer to body */
4173 body = &stream->body;
4175 else body = &elt->private.msg.body;
4176 /* flush any prior body */
4177 mail_free_body (body);
4178 /* instantiate and parse a new body */
4179 imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply);
4182 else if (prop[4] == '[') {
4183 STRINGLIST *stl = NIL;
4184 SIZEDTEXT text;
4185 /* will want to return envelope data */
4186 if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") ||
4187 !strcmp (md.what,"0]"))
4188 e = stream->scache ? &stream->env : &elt->private.msg.env;
4189 LOCAL->tmp[0] ='\0';/* no errors yet */
4190 /* found end of section? */
4191 if (!(s = strchr (md.what,']'))) {
4192 /* skip leading nesting */
4193 for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++);
4194 /* better be one of these */
4195 if (strncmp (s,"HEADER.FIELDS",13) &&
4196 (!s[13] || strcmp (s+13,".NOT")))
4197 sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what);
4198 /* get list of headers */
4199 else if (!(stl = imap_parse_stringlist (stream,&t,reply)))
4200 sprintf (LOCAL->tmp,"Bogus header field list: %.80s",
4201 (char *) t);
4202 else if (*t != ']')
4203 sprintf (LOCAL->tmp,"Unterminated header section: %.80s",
4204 (char *) t);
4205 /* point after the text */
4206 else if ((t = strchr (s = t,' ')) != NULL) *t++ = '\0';
4208 if (s && !LOCAL->tmp[0]) {
4209 *s++ = '\0'; /* tie off section specifier */
4210 if (*s == '<') { /* partial specifier? */
4211 md.first = strtoul (s+1,(char **) &s,10) + 1;
4212 if (*s++ != '>') /* make sure properly terminated */
4213 sprintf (LOCAL->tmp,"Unterminated partial data: %.80s",
4214 (char *) s-1);
4216 if (!LOCAL->tmp[0] && *s)
4217 sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s);
4219 if (LOCAL->tmp[0]) { /* got any errors? */
4220 mm_notify (stream,LOCAL->tmp,WARN);
4221 stream->unhealthy = T;
4222 mail_free_stringlist (&stl);
4224 else { /* parse text from server */
4225 text.data = (unsigned char *)
4226 imap_parse_string (stream,&t,reply,
4227 ((md.what[0] && (md.what[0] != 'H')) ||
4228 md.first || md.last) ? &md : NIL,
4229 &text.size,NIL);
4230 /* all done if partial */
4231 if (md.first || md.last) mail_free_stringlist (&stl);
4232 /* otherwise register it in the cache */
4233 else imap_cache (stream,msgno,md.what,stl,&text);
4235 fs_give ((void **) &md.what);
4237 else {
4238 sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop);
4239 mm_notify (stream,LOCAL->tmp,WARN);
4240 stream->unhealthy = T;
4244 /* one of the RFC822 props? */
4245 else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){
4246 SIZEDTEXT text;
4247 if (!prop[6]) { /* cache full message */
4248 md.what = "";
4249 text.data = (unsigned char *)
4250 imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
4251 imap_cache (stream,msgno,md.what,NIL,&text);
4253 else if (!strcmp (prop+7,"SIZE"))
4254 elt->rfc822_size = strtoul (t,(char **) &t,10);
4255 /* legacy properties */
4256 else if (!strcmp (prop+7,"HEADER")) {
4257 text.data = (unsigned char *)
4258 imap_parse_string (stream,&t,reply,NIL,&text.size,NIL);
4259 imap_cache (stream,msgno,"HEADER",NIL,&text);
4260 e = stream->scache ? &stream->env : &elt->private.msg.env;
4262 else if (!strcmp (prop+7,"TEXT")) {
4263 md.what = "TEXT";
4264 text.data = (unsigned char *)
4265 imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
4266 imap_cache (stream,msgno,md.what,NIL,&text);
4268 else {
4269 sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop);
4270 mm_notify (stream,LOCAL->tmp,WARN);
4271 stream->unhealthy = T;
4274 else {
4275 sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
4276 mm_notify (stream,LOCAL->tmp,WARN);
4277 stream->unhealthy = T;
4279 if (e && *e) env = *e; /* note envelope if we got one */
4281 if (prop) {
4282 sprintf (LOCAL->tmp,"Missing data for property: %.80s",prop);
4283 mm_notify (stream,LOCAL->tmp,WARN);
4284 stream->unhealthy = T;
4286 /* do callback if requested */
4287 if (ie && env) (*ie) (stream,msgno,env);
4289 /* obsolete response to COPY */
4290 else if (strcmp (s,"COPY")) {
4291 sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s);
4292 mm_notify (stream,LOCAL->tmp,WARN);
4293 stream->unhealthy = T;
4297 else if (!strcmp (reply->key,"FLAGS") && reply->text &&
4298 (*reply->text == '(') &&
4299 (s = strtok_r (reply->text+1," )",&r)))
4300 do if (*s != '\\') {
4301 for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] &&
4302 compare_cstring (s,stream->user_flags[i]); i++);
4303 if (i > NUSERFLAGS) {
4304 sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s",
4305 (char *) s);
4306 mm_notify (stream,LOCAL->tmp,WARN);
4308 else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s);
4310 while ((s = strtok_r (NIL," )",&r)) != NULL);
4311 else if (!strcmp (reply->key,"SEARCH")) {
4312 /* only do something if have text */
4313 if (reply->text && (t = strtok_r (reply->text," ",&r))) do
4314 if ((i = strtoul (t,NIL,10)) != 0L) {
4315 /* UIDs always passed to main program */
4316 if (LOCAL->uidsearch) mm_searched (stream,i);
4317 /* should be a msgno then */
4318 else if ((i <= stream->nmsgs) &&
4319 (!LOCAL->filter || mail_elt (stream,i)->private.filter)) {
4320 mail_elt (stream,i)->searched = T;
4321 if (!stream->silent) mm_searched (stream,i);
4323 } while ((t = strtok_r (NIL," ",&r)) != NULL);
4325 else if (!strcmp (reply->key,"SORT")) {
4326 sortresults_t sr = (sortresults_t)
4327 mail_parameters (NIL,GET_SORTRESULTS,NIL);
4328 LOCAL->sortsize = 0; /* initialize sort data */
4329 if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
4330 LOCAL->sortdata = (unsigned long *)
4331 fs_get ((stream->nmsgs + 1) * sizeof (unsigned long));
4332 /* only do something if have text */
4333 if (reply->text && (t = strtok_r (reply->text," ",&r))) {
4334 do if ((i = atol (t)) && (LOCAL->filter ?
4335 mail_elt (stream,i)->searched : T))
4336 LOCAL->sortdata[LOCAL->sortsize++] = i;
4337 while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs));
4339 LOCAL->sortdata[LOCAL->sortsize] = 0;
4340 /* also return via callback if requested */
4341 if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize);
4343 else if (!strcmp (reply->key,"THREAD")) {
4344 threadresults_t tr = (threadresults_t)
4345 mail_parameters (NIL,GET_THREADRESULTS,NIL);
4346 if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
4347 if ((s = reply->text) != NULL) {
4348 LOCAL->threaddata = imap_parse_thread (stream,&s);
4349 if (tr) (*tr) (stream,LOCAL->threaddata);
4350 if (s && *s) {
4351 sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s);
4352 mm_notify (stream,LOCAL->tmp,WARN);
4353 stream->unhealthy = T;
4358 else if (!strcmp (reply->key,"STATUS") && reply->text) {
4359 MAILSTATUS status;
4360 unsigned char *txt = reply->text;
4361 if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt &&
4362 (*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) &&
4363 (s - txt)) {
4364 *s = '\0'; /* tie off status data */
4365 /* initialize data block */
4366 status.flags = status.messages = status.recent = status.unseen =
4367 status.uidnext = status.uidvalidity = 0;
4368 while (*txt && (s = strchr (txt,' '))) {
4369 *s++ = '\0'; /* tie off status attribute name */
4370 /* get attribute value */
4371 i = strtoul (s,(char **) &s,10);
4372 if (!compare_cstring (txt,"MESSAGES")) {
4373 status.flags |= SA_MESSAGES;
4374 status.messages = i;
4376 else if (!compare_cstring (txt,"RECENT")) {
4377 status.flags |= SA_RECENT;
4378 status.recent = i;
4380 else if (!compare_cstring (txt,"UNSEEN")) {
4381 status.flags |= SA_UNSEEN;
4382 status.unseen = i;
4384 else if (!compare_cstring (txt,"UIDNEXT")) {
4385 status.flags |= SA_UIDNEXT;
4386 status.uidnext = i;
4388 else if (!compare_cstring (txt,"UIDVALIDITY")) {
4389 status.flags |= SA_UIDVALIDITY;
4390 status.uidvalidity = i;
4392 /* next attribute */
4393 txt = (*s == ' ') ? s + 1 : s;
4395 if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) <
4396 IMAPTMPLEN) {
4397 strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t);
4398 /* pass status to main program */
4399 mm_status (stream,LOCAL->tmp,&status);
4402 if (t) fs_give ((void **) &t);
4405 else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
4406 reply->text && (*reply->text == '(') &&
4407 (s = strchr (reply->text,')')) && (s[1] == ' ')) {
4408 char delimiter = '\0';
4409 *s++ = '\0'; /* tie off attribute list */
4410 /* parse attribute list */
4411 if ((t = strtok_r (reply->text+1," ",&r)) != NULL) do {
4412 if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS;
4413 else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT;
4414 else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED;
4415 else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED;
4416 else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN;
4417 else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN;
4418 else if (!compare_cstring (t,"\\All")) i |= LATT_ALL;
4419 else if (!compare_cstring (t,"\\Archive")) i |= LATT_ARCHIVE;
4420 else if (!compare_cstring (t,"\\Drafts")) i |= LATT_DRAFTS;
4421 else if (!compare_cstring (t,"\\Flagged")) i |= LATT_FLAGGED;
4422 else if (!compare_cstring (t,"\\Junk")) i |= LATT_JUNK;
4423 else if (!compare_cstring (t,"\\Sent")) i |= LATT_SENT;
4424 else if (!compare_cstring (t,"\\Trash")) i |= LATT_TRASH;
4425 /* ignore extension flags */
4427 while ((t = strtok_r (NIL," ",&r)) != NULL);
4428 switch (*++s) { /* process delimiter */
4429 case 'N': /* NIL */
4430 case 'n':
4431 s += 4; /* skip over NIL<space> */
4432 break;
4433 case '"': /* have a delimiter */
4434 delimiter = (*++s == '\\') ? *++s : *s;
4435 s += 3; /* skip over <delimiter><quote><space> */
4437 /* parse the mailbox name */
4438 if ((t = imap_parse_astring (stream,&s,reply,&j)) != NULL) {
4439 /* prepend prefix if requested */
4440 if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN))
4441 sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t);
4442 else s = t; /* otherwise just mailbox name */
4443 /* pass data to main program */
4444 if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i);
4445 else mm_list (stream,delimiter,s,i);
4446 fs_give ((void **) &t); /* flush mailbox name */
4449 else if (!strcmp (reply->key,"NAMESPACE")) {
4450 if (LOCAL->namespace) {
4451 mail_free_namespace (&LOCAL->namespace[0]);
4452 mail_free_namespace (&LOCAL->namespace[1]);
4453 mail_free_namespace (&LOCAL->namespace[2]);
4455 else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *));
4456 if ((s = reply->text) != NULL) { /* parse namespace results */
4457 LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply);
4458 LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply);
4459 LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply);
4460 if (s && *s) {
4461 sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s);
4462 mm_notify (stream,LOCAL->tmp,WARN);
4463 stream->unhealthy = T;
4466 else {
4467 mm_notify (stream,"Missing namespace list",WARN);
4468 stream->unhealthy = T;
4472 else if (!strcmp (reply->key,"ACL") && (s = reply->text) &&
4473 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4474 getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL);
4475 if (s && (*s++ == ' ')) {
4476 ACLLIST *al = mail_newacllist ();
4477 ACLLIST *ac = al;
4478 do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) &&
4479 s && (*s++ == ' '))
4480 ac->rights = imap_parse_astring (stream,&s,reply,NIL);
4481 while (ac->rights && s && (*s == ' ') && s++ &&
4482 (ac = ac->next = mail_newacllist ()));
4483 if (!ac->rights || (s && *s)) {
4484 sprintf (LOCAL->tmp,"Invalid ACL identifier/rights for %.80s",
4485 (char *) t);
4486 mm_notify (stream,LOCAL->tmp,WARN);
4487 stream->unhealthy = T;
4489 else if (ar) (*ar) (stream,t,al);
4490 mail_free_acllist (&al); /* clean up */
4492 /* no optional rights */
4493 else if (ar) (*ar) (stream,t,NIL);
4494 fs_give ((void **) &t); /* free mailbox name */
4497 else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) &&
4498 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4499 listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL);
4500 char *id,*r;
4501 if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){
4502 if (s && (*s++ == ' ') &&
4503 (r = imap_parse_astring (stream,&s,reply,NIL))) {
4504 if (s && (*s++ == ' ')) {
4505 STRINGLIST *rl = mail_newstringlist ();
4506 STRINGLIST *rc = rl;
4507 do rc->text.data = (unsigned char *)
4508 imap_parse_astring (stream,&s,reply,&rc->text.size);
4509 while (rc->text.data && s && (*s == ' ') && s++ &&
4510 (rc = rc->next = mail_newstringlist ()));
4511 if (!rc->text.data || (s && *s)) {
4512 sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s",
4513 (char *) t);
4514 mm_notify (stream,LOCAL->tmp,WARN);
4515 stream->unhealthy = T;
4517 else if (lr) (*lr) (stream,t,id,r,rl);
4518 /* clean up */
4519 mail_free_stringlist (&rl);
4521 /* no optional rights */
4522 else if (lr) (*lr) (stream,t,id,r,NIL);
4523 fs_give ((void **) &r); /* free rights */
4525 else {
4526 sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t);
4527 mm_notify (stream,LOCAL->tmp,WARN);
4528 stream->unhealthy = T;
4530 fs_give ((void **) &id); /* free identifier */
4532 else {
4533 sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifier for %.80s",(char *) t);
4534 mm_notify (stream,LOCAL->tmp,WARN);
4535 stream->unhealthy = T;
4537 fs_give ((void **) &t); /* free mailbox name */
4540 else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) &&
4541 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4542 myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL);
4543 char *r;
4544 if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) {
4545 if (s && *s) {
4546 sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t);
4547 mm_notify (stream,LOCAL->tmp,WARN);
4548 stream->unhealthy = T;
4550 else if (mr) (*mr) (stream,t,r);
4551 fs_give ((void **) &r); /* free rights */
4553 else {
4554 sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t);
4555 mm_notify (stream,LOCAL->tmp,WARN);
4556 stream->unhealthy = T;
4558 fs_give ((void **) &t); /* free mailbox name */
4561 /* this response has a bizarre syntax! */
4562 else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) &&
4563 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4564 /* in case error */
4565 sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t);
4566 if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) {
4567 quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL);
4568 QUOTALIST *ql = NIL;
4569 QUOTALIST *qc;
4570 /* parse non-empty quota resource list */
4571 if (*s != ')') for (ql = qc = mail_newquotalist (); T;
4572 qc = qc->next = mail_newquotalist ()) {
4573 if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s &&
4574 (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) {
4575 if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10);
4576 else if ((t = strchr (s,' ')) != NULL) t = s;
4577 if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
4578 if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
4579 else if ((t = strpbrk (s," )")) != NULL) t = s;
4580 /* another resource follows? */
4581 if (*s == ' ') continue;
4582 /* end of resource list? */
4583 if ((*s == ')') && !s[1]) {
4584 if (qt) (*qt) (stream,t,ql);
4585 break; /* all done */
4589 /* something bad happened */
4590 mm_notify (stream,LOCAL->tmp,WARN);
4591 stream->unhealthy = T;
4592 break; /* parse failed */
4594 /* all done with quota resource list now */
4595 if (ql) mail_free_quotalist (&ql);
4597 else {
4598 mm_notify (stream,LOCAL->tmp,WARN);
4599 stream->unhealthy = T;
4601 fs_give ((void **) &t); /* free root name */
4603 else if (!strcmp (reply->key,"ID") && (s = reply->text)){
4604 if(compare_cstring (s,"NIL")) LOCAL->id = imap_parse_idlist(s);
4606 else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) &&
4607 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4608 sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t);
4609 if (s && (*s++ == ' ')) {
4610 quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL);
4611 STRINGLIST *rl = mail_newstringlist ();
4612 STRINGLIST *rc = rl;
4613 do rc->text.data = (unsigned char *)
4614 imap_parse_astring (stream,&s,reply,&rc->text.size);
4615 while (rc->text.data && *s && (*s++ == ' ') &&
4616 (rc = rc->next = mail_newstringlist ()));
4617 if (!rc->text.data || (s && *s)) {
4618 mm_notify (stream,LOCAL->tmp,WARN);
4619 stream->unhealthy = T;
4621 else if (qr) (*qr) (stream,t,rl);
4622 /* clean up */
4623 mail_free_stringlist (&rl);
4625 else {
4626 mm_notify (stream,LOCAL->tmp,WARN);
4627 stream->unhealthy = T;
4629 fs_give ((void **) &t);
4632 else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
4633 imap_parse_response (stream,reply->text,NIL,T);
4634 else if (!strcmp (reply->key,"NO"))
4635 imap_parse_response (stream,reply->text,WARN,T);
4636 else if (!strcmp (reply->key,"BAD"))
4637 imap_parse_response (stream,reply->text,ERROR,T);
4638 else if (!strcmp (reply->key,"BYE")) {
4639 LOCAL->byeseen = T; /* note that a BYE seen */
4640 imap_parse_response (stream,reply->text,BYE,T);
4642 else if (!strcmp (reply->key,"CAPABILITY") && reply->text)
4643 imap_parse_capabilities (stream,reply->text);
4644 else if (!strcmp (reply->key,"MAILBOX") && reply->text) {
4645 if (LOCAL->prefix &&
4646 ((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN))
4647 sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text);
4648 else t = reply->text;
4649 mm_list (stream,NIL,t,NIL);
4651 else {
4652 sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4653 (char *) reply->key);
4654 mm_notify (stream,LOCAL->tmp,WARN);
4655 stream->unhealthy = T;
4659 /* Parse human-readable response text
4660 * Accepts: mail stream
4661 * text
4662 * error level for mm_notify()
4663 * non-NIL if want mm_notify() event even if no response code
4666 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy)
4668 char *s,*t,*r;
4669 size_t i;
4670 unsigned long j;
4671 MESSAGECACHE *elt;
4672 copyuid_t cu;
4673 appenduid_t au;
4674 SEARCHSET *source = NIL;
4675 SEARCHSET *dest = NIL;
4676 if (text && (*text == '[') && (t = strchr (s = text + 1,']')) &&
4677 ((i = t - s) < IMAPTMPLEN)) {
4678 LOCAL->tmp[i] = '\0'; /* make mungable copy of text code */
4679 if ((s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) != NULL) *s++ = '\0';
4680 if (s) { /* have argument? */
4681 ntfy = NIL; /* suppress mm_notify if normal SELECT data */
4682 if (!compare_cstring (t,"CAPABILITY")) imap_parse_capabilities(stream,s);
4683 else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') &&
4684 (t[i-1] == ')')) {
4685 t[i-1] = '\0'; /* tie off flags */
4686 stream->perm_seen = stream->perm_deleted = stream->perm_answered =
4687 stream->perm_draft = stream->kwd_create = NIL;
4688 stream->perm_user_flags = NIL;
4689 if ((s = strtok_r (s+1," ",&r)) != NULL) do {
4690 if (*s == '\\') { /* system flags */
4691 if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T;
4692 else if (!compare_cstring (s,"\\Deleted"))
4693 stream->perm_deleted = T;
4694 else if (!compare_cstring (s,"\\Flagged"))
4695 stream->perm_flagged = T;
4696 else if (!compare_cstring (s,"\\Answered"))
4697 stream->perm_answered = T;
4698 else if (!compare_cstring (s,"\\Draft")) stream->perm_draft = T;
4699 else if (!strcmp (s,"\\*")) stream->kwd_create = T;
4701 else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
4703 while ((s = strtok_r (NIL," ",&r)) != NULL);
4706 else if (!compare_cstring (t,"UIDVALIDITY") && (j = strtoul (s,NIL,10))){
4707 /* do this in separate if because of ntfy */
4708 if (j != stream->uid_validity) {
4709 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4710 stream->uid_validity = j;
4711 /* purge any UIDs in cache */
4712 for (j = 1; j <= stream->nmsgs; j++)
4713 if ((elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT)) != NULL)
4714 elt->private.uid = 0;
4717 else if (!compare_cstring (t,"UIDNEXT"))
4718 stream->uid_last = strtoul (s,NIL,10) - 1;
4719 else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) &&
4720 !compare_cstring (t,"COPYUID") &&
4721 (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) &&
4722 isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4723 (source = mail_parse_set (s,&s)) && (*s++ == ' ') &&
4724 (dest = mail_parse_set (s,&s)) && !*s)
4725 (*cu) (stream,LOCAL->appendmailbox,j,source,dest);
4726 else if (j && !compare_cstring (t,"APPENDUID") &&
4727 (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) &&
4728 isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4729 (dest = mail_parse_set (s,&s)) && !*s)
4730 (*au) (LOCAL->appendmailbox,j,dest);
4731 else { /* all other response code events */
4732 ntfy = T; /* must mm_notify() */
4733 if (!compare_cstring (t,"REFERRAL"))
4734 LOCAL->referral = cpystr (t + 9);
4736 mail_free_searchset (&source);
4737 mail_free_searchset (&dest);
4739 else { /* no arguments */
4740 if (!compare_cstring (t,"UIDNOTSTICKY")) {
4741 ntfy = NIL;
4742 stream->uid_nosticky = T;
4744 else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T;
4745 else if (!compare_cstring (t,"READ-WRITE"))
4746 stream->rdonly = NIL;
4747 else if (!compare_cstring (t,"PARSE") && !errflg)
4748 errflg = PARSE;
4751 /* give event to main program */
4752 if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg);
4755 /* Parse a namespace
4756 * Accepts: mail stream
4757 * current text pointer
4758 * parsed reply
4759 * Returns: namespace list, text pointer updated
4762 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
4763 IMAPPARSEDREPLY *reply)
4765 NAMESPACE *ret = NIL;
4766 NAMESPACE *nam = NIL;
4767 NAMESPACE *prev = NIL;
4768 PARAMETER *par = NIL;
4769 if (*txtptr) { /* only if argument given */
4770 /* ignore leading space */
4771 while (**txtptr == ' ') ++*txtptr;
4772 switch (**txtptr) {
4773 case 'N': /* if NIL */
4774 case 'n':
4775 ++*txtptr; /* bump past "N" */
4776 ++*txtptr; /* bump past "I" */
4777 ++*txtptr; /* bump past "L" */
4778 break;
4779 case '(':
4780 ++*txtptr; /* skip past open paren */
4781 while (**txtptr == '(') {
4782 ++*txtptr; /* skip past open paren */
4783 prev = nam; /* note previous if any */
4784 nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0,
4785 sizeof (NAMESPACE));
4786 if (!ret) ret = nam; /* if first time note first namespace */
4787 /* if previous link new block to it */
4788 if (prev) prev->next = nam;
4789 nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL);
4790 /* ignore whitespace */
4791 while (**txtptr == ' ') ++*txtptr;
4792 switch (**txtptr) { /* parse delimiter */
4793 case 'N':
4794 case 'n':
4795 *txtptr += 3; /* bump past "NIL" */
4796 break;
4797 case '"':
4798 if (*++*txtptr == '\\') nam->delimiter = *++*txtptr;
4799 else nam->delimiter = **txtptr;
4800 *txtptr += 2; /* bump past character and closing quote */
4801 break;
4802 default:
4803 sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s",
4804 (char *) *txtptr);
4805 mm_notify (stream,LOCAL->tmp,WARN);
4806 stream->unhealthy = T;
4807 *txtptr = NIL; /* stop parse */
4808 return ret;
4811 while (**txtptr == ' '){/* append new parameter to tail */
4812 if (nam->param) par = par->next = mail_newbody_parameter ();
4813 else nam->param = par = mail_newbody_parameter ();
4814 if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL,
4815 NIL,NIL))) {
4816 mm_notify (stream,"Missing namespace extension attribute",WARN);
4817 stream->unhealthy = T;
4818 par->attribute = cpystr ("UNKNOWN");
4820 /* skip space */
4821 while (**txtptr == ' ') ++*txtptr;
4822 if (**txtptr == '(') {/* have value list? */
4823 char *att = par->attribute;
4824 ++*txtptr; /* yes */
4825 do { /* parse each value */
4826 if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,
4827 NIL,LONGT))) {
4828 sprintf (LOCAL->tmp,
4829 "Missing value for namespace attribute %.80s",att);
4830 mm_notify (stream,LOCAL->tmp,WARN);
4831 stream->unhealthy = T;
4832 par->value = cpystr ("UNKNOWN");
4834 /* is there another value? */
4835 if (**txtptr == ' ') par = par->next = mail_newbody_parameter ();
4836 } while (!par->value);
4838 else {
4839 sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s",
4840 par->attribute);
4841 mm_notify (stream,LOCAL->tmp,WARN);
4842 stream->unhealthy = T;
4843 par->value = cpystr ("UNKNOWN");
4846 if (**txtptr == ')') ++*txtptr;
4847 else { /* missing trailing paren */
4848 sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s",
4849 (char *) *txtptr);
4850 mm_notify (stream,LOCAL->tmp,WARN);
4851 stream->unhealthy = T;
4852 return ret;
4855 if (**txtptr == ')') { /* expected trailing paren? */
4856 ++*txtptr; /* got it! */
4857 break;
4859 default:
4860 sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr);
4861 mm_notify (stream,LOCAL->tmp,WARN);
4862 stream->unhealthy = T;
4863 *txtptr = NIL; /* stop parse now */
4864 break;
4867 return ret;
4870 /* Parse a thread node list
4871 * Accepts: mail stream
4872 * current text pointer
4873 * Returns: thread node list, text pointer updated
4876 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr)
4878 char *s;
4879 THREADNODE *ret = NIL; /* returned tree */
4880 THREADNODE *last = NIL; /* last branch in this tree */
4881 THREADNODE *parent = NIL; /* parent of current node */
4882 THREADNODE *cur; /* current node */
4883 while (**txtptr == '(') { /* see a thread? */
4884 ++*txtptr; /* skip past open paren */
4885 while (**txtptr != ')') { /* parse thread */
4886 if (**txtptr == '(') { /* thread branch */
4887 cur = imap_parse_thread (stream,txtptr);
4888 /* add to parent */
4889 if (parent) parent = parent->next = cur;
4890 else { /* no parent, create dummy */
4891 if (last) last = last->branch = mail_newthreadnode (NIL);
4892 /* new tree */
4893 else ret = last = mail_newthreadnode (NIL);
4894 /* add to dummy parent */
4895 last->next = parent = cur;
4898 /* threaded message number */
4899 else if (isdigit (*(s = *txtptr)) &&
4900 ((cur = mail_newthreadnode (NIL))->num =
4901 strtoul (*txtptr,(char **) txtptr,10))) {
4902 if (LOCAL->filter && !mail_elt (stream,cur->num)->searched)
4903 cur->num = NIL; /* make dummy if filtering and not searched */
4904 /* add to parent */
4905 if (parent) parent = parent->next = cur;
4906 /* no parent, start new thread */
4907 else if (last) last = last->branch = parent = cur;
4908 /* create new tree */
4909 else ret = last = parent = cur;
4911 else { /* anything else is a bogon */
4912 char tmp[MAILTMPLEN];
4913 sprintf (tmp,"Bogus thread member: %.80s",s);
4914 mm_notify (stream,tmp,WARN);
4915 stream->unhealthy = T;
4916 return ret;
4918 /* skip past any space */
4919 if (**txtptr == ' ') ++*txtptr;
4921 ++*txtptr; /* skip past end of thread */
4922 parent = NIL; /* close this thread */
4924 return ret; /* return parsed thread */
4927 /* Parse RFC822 message header
4928 * Accepts: MAIL stream
4929 * envelope to parse into
4930 * header as sized text
4931 * stringlist if partial header
4934 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
4935 STRINGLIST *stl)
4937 ENVELOPE *nenv;
4938 /* parse what we can from this header */
4939 rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL,
4940 net_host (LOCAL->netstream),stream->dtb->flags);
4941 if (*env) { /* need to merge this header into envelope? */
4942 if (!(*env)->newsgroups) { /* need Newsgroups? */
4943 (*env)->newsgroups = nenv->newsgroups;
4944 (*env)->ngpathexists = nenv->ngpathexists;
4945 nenv->newsgroups = NIL;
4947 if (!(*env)->followup_to) { /* need Followup-To? */
4948 (*env)->followup_to = nenv->followup_to;
4949 nenv->followup_to = NIL;
4951 if (!(*env)->references) { /* need References? */
4952 (*env)->references = nenv->references;
4953 nenv->references = NIL;
4955 if (!(*env)->sparep) { /* need spare pointer? */
4956 (*env)->sparep = nenv->sparep;
4957 nenv->sparep = NIL;
4959 mail_free_envelope (&nenv);
4960 (*env)->imapenvonly = NIL; /* have complete envelope now */
4962 /* otherwise set it to this envelope */
4963 else (*env = nenv)->incomplete = stl ? T : NIL;
4966 /* IMAP parse envelope
4967 * Accepts: MAIL stream
4968 * pointer to envelope pointer
4969 * current text pointer
4970 * parsed reply
4972 * Updates text pointer
4975 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
4976 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
4978 ENVELOPE *oenv = *env;
4979 char c = **txtptr; /* grab first character */
4980 /* ignore leading spaces */
4981 while (c == ' ') c = *++*txtptr;
4982 if (c) ++*txtptr; /* skip past first character */
4983 switch (c) { /* dispatch on first character */
4984 case '(': /* if envelope S-expression */
4985 *env = mail_newenvelope (); /* parse the new envelope */
4986 (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4987 (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4988 (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
4989 (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
4990 (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
4991 (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
4992 (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
4993 (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
4994 (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL,
4995 LONGT);
4996 (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
4997 if (oenv) { /* need to merge old envelope? */
4998 (*env)->newsgroups = oenv->newsgroups;
4999 oenv->newsgroups = NIL;
5000 (*env)->ngpathexists = oenv->ngpathexists;
5001 (*env)->followup_to = oenv->followup_to;
5002 oenv->followup_to = NIL;
5003 (*env)->references = oenv->references;
5004 oenv->references = NIL;
5005 mail_free_envelope(&oenv);/* free old envelope */
5007 /* have IMAP envelope components only */
5008 else (*env)->imapenvonly = T;
5009 if (**txtptr != ')') {
5010 sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr);
5011 mm_notify (stream,LOCAL->tmp,WARN);
5012 stream->unhealthy = T;
5014 else ++*txtptr; /* skip past delimiter */
5015 break;
5016 case 'N': /* if NIL */
5017 case 'n':
5018 ++*txtptr; /* bump past "I" */
5019 ++*txtptr; /* bump past "L" */
5020 break;
5021 default:
5022 sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr);
5023 mm_notify (stream,LOCAL->tmp,WARN);
5024 stream->unhealthy = T;
5025 break;
5029 /* IMAP parse address list
5030 * Accepts: MAIL stream
5031 * current text pointer
5032 * parsed reply
5033 * Returns: address list, NIL on failure
5035 * Updates text pointer
5038 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
5039 IMAPPARSEDREPLY *reply)
5041 ADDRESS *adr = NIL;
5042 char c = **txtptr; /* sniff at first character */
5043 /* ignore leading spaces */
5044 while (c == ' ') c = *++*txtptr;
5045 if (c) ++*txtptr; /* skip past open paren */
5046 switch (c) {
5047 case '(': /* if envelope S-expression */
5048 adr = imap_parse_address (stream,txtptr,reply);
5049 if (**txtptr != ')') {
5050 sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",
5051 (char *) *txtptr);
5052 mm_notify (stream,LOCAL->tmp,WARN);
5053 stream->unhealthy = T;
5055 else ++*txtptr; /* skip past delimiter */
5056 break;
5057 case 'N': /* if NIL */
5058 case 'n':
5059 ++*txtptr; /* bump past "I" */
5060 ++*txtptr; /* bump past "L" */
5061 break;
5062 default:
5063 sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
5064 mm_notify (stream,LOCAL->tmp,WARN);
5065 stream->unhealthy = T;
5066 break;
5068 return adr;
5071 /* IMAP parse address
5072 * Accepts: MAIL stream
5073 * current text pointer
5074 * parsed reply
5075 * Returns: address, NIL on failure
5077 * Updates text pointer
5080 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
5081 IMAPPARSEDREPLY *reply)
5083 long ingroup = 0;
5084 ADDRESS *adr = NIL;
5085 ADDRESS *ret = NIL;
5086 ADDRESS *prev = NIL;
5087 char c = **txtptr; /* sniff at first address character */
5088 switch (c) {
5089 case '(': /* if envelope S-expression */
5090 while (c == '(') { /* recursion dies on small stack machines */
5091 ++*txtptr; /* skip past open paren */
5092 if (adr) prev = adr; /* note previous if any */
5093 adr = mail_newaddr (); /* instantiate address and parse its fields */
5094 adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5095 adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5096 adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5097 adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5098 if (**txtptr != ')') { /* handle trailing paren */
5099 sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr);
5100 mm_notify (stream,LOCAL->tmp,WARN);
5101 stream->unhealthy = T;
5103 else ++*txtptr; /* skip past close paren */
5104 c = **txtptr; /* set up for while test */
5105 /* ignore leading spaces in front of next */
5106 while (c == ' ') c = *++*txtptr;
5108 if (!adr->mailbox) { /* end of group? */
5109 /* decrement group if all looks well */
5110 if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup;
5111 else {
5112 if (ingroup) { /* in a group? */
5113 sprintf (LOCAL->tmp,/* yes, must be bad syntax */
5114 "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
5115 adr->personal ? adr->personal : "",
5116 adr->adl ? adr->adl : "",
5117 adr->host ? adr->host : "");
5118 mm_notify (stream,LOCAL->tmp,WARN);
5120 else mm_notify (stream,"End of group encountered when not in group",
5121 WARN);
5122 stream->unhealthy = T;
5123 mail_free_address (&adr);
5124 adr = prev;
5125 prev = NIL;
5128 else if (!adr->host) { /* start of group? */
5129 if (adr->personal || adr->adl) {
5130 sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s",
5131 adr->personal ? adr->personal : "",
5132 adr->adl ? adr->adl : "");
5133 mm_notify (stream,LOCAL->tmp,WARN);
5134 stream->unhealthy = T;
5135 mail_free_address (&adr);
5136 adr = prev;
5137 prev = NIL;
5139 else ++ingroup; /* in a group now */
5141 if (adr) { /* good address */
5142 if (!ret) ret = adr; /* if first time note first adr */
5143 /* if previous link new block to it */
5144 if (prev) prev->next = adr;
5145 /* flush bogus personal name */
5146 if (LOCAL->loser && adr->personal && strchr (adr->personal,'@'))
5147 fs_give ((void **) &adr->personal);
5150 break;
5151 case 'N': /* if NIL */
5152 case 'n':
5153 *txtptr += 3; /* bump past NIL */
5154 break;
5155 default:
5156 sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
5157 mm_notify (stream,LOCAL->tmp,WARN);
5158 stream->unhealthy = T;
5159 break;
5161 return ret;
5164 /* IMAP parse flags
5165 * Accepts: current message cache
5166 * current text pointer
5168 * Updates text pointer
5171 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
5172 unsigned char **txtptr)
5174 char *flag;
5175 char c = '\0';
5176 struct { /* old flags */
5177 unsigned int valid : 1;
5178 unsigned int seen : 1;
5179 unsigned int deleted : 1;
5180 unsigned int flagged : 1;
5181 unsigned int answered : 1;
5182 unsigned int draft : 1;
5183 unsigned long user_flags;
5184 } old;
5185 old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted;
5186 old.flagged = elt->flagged; old.answered = elt->answered;
5187 old.draft = elt->draft; old.user_flags = elt->user_flags;
5188 elt->valid = T; /* mark have valid flags now */
5189 elt->user_flags = NIL; /* zap old flag values */
5190 elt->seen = elt->deleted = elt->flagged = elt->answered = elt->draft =
5191 elt->recent = NIL;
5192 do { /* parse list of flags */
5193 /* point at a flag */
5194 while (*(flag = ++*txtptr) == ' ');
5195 /* scan for end of flag */
5196 while (**txtptr && (**txtptr != ' ') && (**txtptr != ')')) ++*txtptr;
5197 c = **txtptr; /* save delimiter */
5198 **txtptr = '\0'; /* tie off flag */
5199 if (!*flag) break; /* null flag */
5200 /* if starts with \ must be sys flag */
5201 else if (*flag == '\\') {
5202 if (!compare_cstring (flag,"\\Seen")) elt->seen = T;
5203 else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T;
5204 else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T;
5205 else if (!compare_cstring (flag,"\\Answered")) elt->answered = T;
5206 else if (!compare_cstring (flag,"\\Recent")) elt->recent = T;
5207 else if (!compare_cstring (flag,"\\Draft")) elt->draft = T;
5209 /* otherwise user flag */
5210 else elt->user_flags |= imap_parse_user_flag (stream,flag);
5211 } while (c && (c != ')'));
5212 if (c) ++*txtptr; /* bump past delimiter */
5213 else {
5214 mm_notify (stream,"Unterminated flags list",WARN);
5215 stream->unhealthy = T;
5217 if (!old.valid || (old.seen != elt->seen) ||
5218 (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
5219 (old.answered != elt->answered) || (old.draft != elt->draft) ||
5220 (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno);
5224 /* IMAP parse user flag
5225 * Accepts: MAIL stream
5226 * flag name
5227 * Returns: flag bit position
5230 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
5232 long i;
5233 /* sniff through all user flags */
5234 for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
5235 if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i);
5236 return (unsigned long) 0; /* not found */
5239 /* IMAP parse atom-string
5240 * Accepts: MAIL stream
5241 * current text pointer
5242 * parsed reply
5243 * returned string length
5244 * Returns: string
5246 * Updates text pointer
5249 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
5250 IMAPPARSEDREPLY *reply,unsigned long *len)
5252 unsigned long i;
5253 unsigned char c,*s,*ret;
5254 /* ignore leading spaces */
5255 for (c = **txtptr; c == ' '; c = *++*txtptr);
5256 switch (c) {
5257 case '"': /* quoted string? */
5258 case '{': /* literal? */
5259 ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL);
5260 break;
5261 default: /* must be atom */
5262 for (c = *(s = *txtptr); /* find end of atom */
5263 c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') &&
5264 (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80);
5265 c = *++*txtptr);
5266 if ((i = *txtptr - s) != 0L) { /* atom ends at atom_special */
5267 if (len) *len = i; /* return length of atom */
5268 ret = strncpy ((char *) fs_get (i + 1),s,i);
5269 ret[i] = '\0'; /* tie off string */
5271 else { /* no atom found */
5272 sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr);
5273 mm_notify (stream,LOCAL->tmp,WARN);
5274 stream->unhealthy = T;
5275 if (len) *len = 0;
5276 ret = NIL;
5278 break;
5280 return ret;
5283 /* IMAP parse string
5284 * Accepts: MAIL stream
5285 * current text pointer
5286 * parsed reply
5287 * mailgets data
5288 * returned string length
5289 * filter newline flag
5290 * Returns: string
5292 * Updates text pointer
5295 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
5296 IMAPPARSEDREPLY *reply,GETS_DATA *md,
5297 unsigned long *len,long flags)
5299 char *st;
5300 char *string = NIL;
5301 unsigned long i,j,k;
5302 int bogon = NIL;
5303 unsigned char c = **txtptr; /* sniff at first character */
5304 mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
5305 readprogress_t rp =
5306 (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
5307 /* ignore leading spaces */
5308 while (c == ' ') c = *++*txtptr;
5309 if (c) st = ++*txtptr; /* remember start of string */
5310 switch (c) {
5311 case '"': /* if quoted string */
5312 i = 0; /* initial byte count */
5313 /* search for end of string */
5314 for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) {
5315 /* backslash quotes next character */
5316 if (c == '\\') c = *++*txtptr;
5317 /* CHAR8 not permitted in quoted string */
5318 if (!bogon && (bogon = (c & 0x80))) {
5319 sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x",
5320 (unsigned int) c);
5321 mm_notify (stream,LOCAL->tmp,WARN);
5322 stream->unhealthy = T;
5324 else if (!c) { /* NUL not permitted either */
5325 mm_notify (stream,"Unterminated quoted string",WARN);
5326 stream->unhealthy = T;
5327 if (len) *len = 0; /* punt, since may be at end of string */
5328 return NIL;
5331 ++*txtptr; /* bump past delimiter */
5332 string = (char *) fs_get ((size_t) i + 1);
5333 for (j = 0; j < i; j++) { /* copy the string */
5334 if (*st == '\\') ++st; /* quoted character */
5335 string[j] = *st++;
5337 string[j] = '\0'; /* tie off string */
5338 if (len) *len = i; /* set return value too */
5339 if (md && mg) { /* have special routine to slurp string? */
5340 STRING bs;
5341 if (md->first) { /* partial fetch? */
5342 md->first--; /* restore origin octet */
5343 md->last = i; /* number of octets that we got */
5345 INIT (&bs,mail_string,string,i);
5346 (*mg) (mail_read,&bs,i,md);
5348 break;
5350 case 'N': /* if NIL */
5351 case 'n':
5352 ++*txtptr; /* bump past "I" */
5353 ++*txtptr; /* bump past "L" */
5354 if (len) *len = 0;
5355 break;
5356 case '{': /* if literal string */
5357 if (!isdigit (**txtptr)) {
5358 sprintf (LOCAL->tmp,"Invalid server literal length %.80s",*txtptr);
5359 mm_notify (stream,LOCAL->tmp,WARN);
5360 stream->unhealthy = T; /* read and discard */
5361 i = 0;
5363 /* get size of string */
5364 else if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) {
5365 sprintf (LOCAL->tmp,"Absurd server literal length %lu",i);
5366 mm_notify (stream,LOCAL->tmp,WARN);
5367 stream->unhealthy = T; /* read and discard */
5368 for (j = IMAPTMPLEN - 1; i; i -= j) {
5369 if (j > i) j = i;
5370 net_getbuffer (LOCAL->netstream,j,LOCAL->tmp);
5373 if (len) *len = i; /* set return value */
5374 if (md && mg) { /* have special routine to slurp string? */
5375 if (md->first) { /* partial fetch? */
5376 md->first--; /* restore origin octet */
5377 md->last = i; /* number of octets that we got */
5379 else md->flags |= MG_COPY;/* otherwise flag need to copy */
5380 string = (*mg) (net_getbuffer,LOCAL->netstream,i,md);
5382 else { /* must slurp into free storage */
5383 string = (char *) fs_get ((size_t) i + 1);
5384 *string = '\0'; /* init in case getbuffer fails */
5385 /* get the literal */
5386 if (rp) for (k = 0; (j = min ((long) MAILTMPLEN,(long) i)) != 0L; i -= j) {
5387 net_getbuffer (LOCAL->netstream,j,string + k);
5388 (*rp) (md,k += j);
5390 else net_getbuffer (LOCAL->netstream,i,string);
5392 fs_give ((void **) &reply->line);
5393 if (flags && string) /* need to filter newlines? */
5394 for (st = string; (st = strpbrk (st,"\015\012\011")) != NULL; *st++ = ' ');
5395 /* get new reply text line */
5396 if (!(reply->line = net_getline (LOCAL->netstream)))
5397 reply->line = cpystr ("");
5398 if (stream->debug) mm_dlog (reply->line);
5399 *txtptr = reply->line; /* set text pointer to point at it */
5400 break;
5401 default:
5402 sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,(char *) *txtptr);
5403 mm_notify (stream,LOCAL->tmp,WARN);
5404 stream->unhealthy = T;
5405 if (len) *len = 0;
5406 break;
5408 return (unsigned char *) string;
5411 /* Register text in IMAP cache
5412 * Accepts: MAIL stream
5413 * message number
5414 * IMAP segment specifier
5415 * header string list (if a HEADER section specifier)
5416 * sized text to register
5417 * Returns: non-zero if cache non-empty
5420 long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg,
5421 STRINGLIST *stl,SIZEDTEXT *text)
5423 char *t,tmp[MAILTMPLEN];
5424 unsigned long i;
5425 BODY *b;
5426 SIZEDTEXT *ret;
5427 STRINGLIST *stc;
5428 MESSAGECACHE *elt = mail_elt (stream,msgno);
5429 /* top-level header never does mailgets */
5430 if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") ||
5431 !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) {
5432 ret = &elt->private.msg.header.text;
5433 if (text) { /* don't do this if no text */
5434 if (ret->data) fs_give ((void **) &ret->data);
5435 mail_free_stringlist (&elt->private.msg.lines);
5436 elt->private.msg.lines = stl;
5437 /* prevent cache reuse of .NOT */
5438 if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.'))
5439 for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5440 if (stream->scache) { /* short caching puts it in the stream */
5441 if (stream->msgno != msgno) {
5442 /* flush old stuff */
5443 mail_free_envelope (&stream->env);
5444 mail_free_body (&stream->body);
5445 stream->msgno = msgno;
5447 imap_parse_header (stream,&stream->env,text,stl);
5449 /* regular caching */
5450 else imap_parse_header (stream,&elt->private.msg.env,text,stl);
5453 /* top level text */
5454 else if (!strcmp (seg,"TEXT")) {
5455 ret = &elt->private.msg.text.text;
5456 if (text && ret->data) fs_give ((void **) &ret->data);
5458 else if (!*seg) { /* full message */
5459 ret = &elt->private.msg.full.text;
5460 if (text && ret->data) fs_give ((void **) &ret->data);
5463 else { /* nested, find non-contents specifier */
5464 for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++);
5465 if (*t) *t++ = '\0'; /* tie off section from data specifier */
5466 if (!(b = mail_body (stream,msgno,seg))) {
5467 sprintf (tmp,"Unknown section number: %.80s",seg);
5468 mm_notify (stream,tmp,WARN);
5469 stream->unhealthy = T;
5470 return NIL;
5472 if (*t) { /* if a non-numberic subpart */
5473 if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) &&
5474 (!strcmp (t,"HEADER") || !strcmp (t,"0") ||
5475 !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) {
5476 ret = &b->nested.msg->header.text;
5477 if (text) {
5478 if (ret->data) fs_give ((void **) &ret->data);
5479 mail_free_stringlist (&b->nested.msg->lines);
5480 b->nested.msg->lines = stl;
5481 /* prevent cache reuse of .NOT */
5482 if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.'))
5483 for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5484 imap_parse_header (stream,&b->nested.msg->env,text,stl);
5487 else if (i && !strcmp (t,"TEXT")) {
5488 ret = &b->nested.msg->text.text;
5489 if (text && ret->data) fs_give ((void **) &ret->data);
5491 /* otherwise it must be MIME */
5492 else if (!strcmp (t,"MIME")) {
5493 ret = &b->mime.text;
5494 if (text && ret->data) fs_give ((void **) &ret->data);
5496 else {
5497 sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t);
5498 mm_notify (stream,tmp,WARN);
5499 stream->unhealthy = T;
5500 return NIL;
5503 else { /* ordinary contents */
5504 ret = &b->contents.text;
5505 if (text && ret->data) fs_give ((void **) &ret->data);
5508 if (text) { /* update cache if requested */
5509 ret->data = text->data;
5510 ret->size = text->size;
5512 return ret->data ? LONGT : NIL;
5515 /* IMAP parse body structure
5516 * Accepts: MAIL stream
5517 * body structure to write into
5518 * current text pointer
5519 * parsed reply
5521 * Updates text pointer
5524 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
5525 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5527 int i;
5528 char *s;
5529 PART *part = NIL;
5530 char c = **txtptr; /* grab first character */
5531 /* ignore leading spaces */
5532 while (c == ' ') c = *++*txtptr;
5533 if (c) ++*txtptr; /* skip past first character */
5534 switch (c) { /* dispatch on first character */
5535 case '(': /* body structure list */
5536 if (**txtptr == '(') { /* multipart body? */
5537 body->type= TYPEMULTIPART;/* yes, set its type */
5538 do { /* instantiate new body part */
5539 if (part) part = part->next = mail_newbody_part ();
5540 else body->nested.part = part = mail_newbody_part ();
5541 /* parse it */
5542 imap_parse_body_structure (stream,&part->body,txtptr,reply);
5543 } while (**txtptr == '(');/* for each body part */
5544 if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL)
5545 ucase (body->subtype);
5546 else {
5547 mm_notify (stream,"Missing multipart subtype",WARN);
5548 stream->unhealthy = T;
5549 body->subtype = cpystr (rfc822_default_subtype (body->type));
5551 if (**txtptr == ' ') /* multipart parameters */
5552 body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5553 if (**txtptr == ' ') { /* disposition */
5554 imap_parse_disposition (stream,body,txtptr,reply);
5555 if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5557 if (**txtptr == ' ') { /* language */
5558 body->language = imap_parse_language (stream,txtptr,reply);
5559 if (LOCAL->cap.extlevel < BODYEXTLANG)
5560 LOCAL->cap.extlevel = BODYEXTLANG;
5562 if (**txtptr == ' ') { /* location */
5563 body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5564 if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5566 while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5567 if (**txtptr != ')') { /* validate ending */
5568 sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",
5569 (char *) *txtptr);
5570 mm_notify (stream,LOCAL->tmp,WARN);
5571 stream->unhealthy = T;
5573 else ++*txtptr; /* skip past delimiter */
5576 else { /* not multipart, parse type name */
5577 if (**txtptr == ')') { /* empty body? */
5578 ++*txtptr; /* bump past it */
5579 break; /* and punt */
5581 body->type = TYPEOTHER; /* assume unknown type */
5582 body->encoding = ENCOTHER;/* and unknown encoding */
5583 /* parse type */
5584 if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) {
5585 ucase (s); /* application always gets uppercase form */
5586 for (i = 0; /* look in existing table */
5587 (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++);
5588 if (i <= TYPEMAX) { /* only if found a slot */
5589 body->type = i; /* set body type */
5590 if (!body_types[i]) { /* assign empty slot */
5591 body_types[i] = s;
5592 s = NIL; /* don't free this string */
5595 if (s) fs_give ((void **) &s);
5597 if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL)
5598 ucase (body->subtype); /* parse subtype */
5599 else {
5600 mm_notify (stream,"Missing body subtype",WARN);
5601 stream->unhealthy = T;
5602 body->subtype = cpystr (rfc822_default_subtype (body->type));
5604 body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5605 body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5606 body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5607 LONGT);
5608 if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) {
5609 ucase (s); /* application always gets uppercase form */
5610 for (i = 0; /* search for body encoding */
5611 (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]);
5612 i++);
5613 if (i > ENCMAX) body->encoding = ENCOTHER;
5614 else { /* only if found a slot */
5615 body->encoding = i; /* set body encoding */
5616 /* assign empty slot */
5617 if (!body_encodings[i]) {
5618 body_encodings[i] = s;
5619 s = NIL; /* don't free this string */
5622 if (s) fs_give ((void **) &s);
5624 \f /* parse size of contents in bytes */
5625 body->size.bytes = strtoul (*txtptr,(char **) txtptr,10);
5626 switch (body->type) { /* possible extra stuff */
5627 case TYPEMESSAGE: /* message envelope and body */
5628 /* non MESSAGE/RFC822 is basic type */
5629 if (strcmp (body->subtype,"RFC822")) break;
5630 { /* make certain server sends an envelope */
5631 ENVELOPE *env = NIL;
5632 imap_parse_envelope (stream,&env,txtptr,reply);
5633 if (!env) {
5634 mm_notify (stream,"Missing body message envelope",WARN);
5635 stream->unhealthy = T;
5636 fs_give ((void **) &body->subtype);
5637 body->subtype = cpystr ("RFC822_MISSING_ENVELOPE");
5638 break;
5640 (body->nested.msg = mail_newmsg ())->env = env;
5642 body->nested.msg->body = mail_newbody ();
5643 imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply);
5644 /* drop into text case */
5645 case TYPETEXT: /* size in lines */
5646 body->size.lines = strtoul (*txtptr,(char **) txtptr,10);
5647 break;
5648 default: /* otherwise nothing special */
5649 break;
5652 if (**txtptr == ' ') { /* extension data - md5 */
5653 body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5654 if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5;
5656 if (**txtptr == ' ') { /* disposition */
5657 imap_parse_disposition (stream,body,txtptr,reply);
5658 if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5660 if (**txtptr == ' ') { /* language */
5661 body->language = imap_parse_language (stream,txtptr,reply);
5662 if (LOCAL->cap.extlevel < BODYEXTLANG)
5663 LOCAL->cap.extlevel = BODYEXTLANG;
5665 if (**txtptr == ' ') { /* location */
5666 body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5667 if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5669 while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5670 if (**txtptr != ')') { /* validate ending */
5671 sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",
5672 (char *) *txtptr);
5673 mm_notify (stream,LOCAL->tmp,WARN);
5674 stream->unhealthy = T;
5676 else ++*txtptr; /* skip past delimiter */
5678 break;
5679 case 'N': /* if NIL */
5680 case 'n':
5681 ++*txtptr; /* bump past "I" */
5682 ++*txtptr; /* bump past "L" */
5683 break;
5684 default: /* otherwise quite bogus */
5685 sprintf (LOCAL->tmp,"Bogus body structure: %.80s",(char *) *txtptr);
5686 mm_notify (stream,LOCAL->tmp,WARN);
5687 stream->unhealthy = T;
5688 break;
5692 /* IMAP parse body parameter
5693 * Accepts: MAIL stream
5694 * current text pointer
5695 * parsed reply
5696 * Returns: body parameter
5697 * Updates text pointer
5700 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
5701 unsigned char **txtptr,
5702 IMAPPARSEDREPLY *reply)
5704 PARAMETER *ret = NIL;
5705 PARAMETER *par = NIL;
5706 char c,*s;
5707 /* ignore leading spaces */
5708 while ((c = *(*txtptr)++) == ' ');
5709 if (c == '(') do { /* parse parameter list */
5710 /* append new parameter to tail */
5711 if (ret) par = par->next = mail_newbody_parameter ();
5712 else ret = par = mail_newbody_parameter ();
5713 if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL,
5714 LONGT))) {
5715 mm_notify (stream,"Missing parameter attribute",WARN);
5716 stream->unhealthy = T;
5717 par->attribute = cpystr ("UNKNOWN");
5719 if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){
5720 sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
5721 mm_notify (stream,LOCAL->tmp,WARN);
5722 stream->unhealthy = T;
5723 par->value = cpystr ("UNKNOWN");
5725 switch (c = **txtptr) { /* see what comes after */
5726 case ' ': /* flush whitespace */
5727 while ((c = *++*txtptr) == ' ');
5728 break;
5729 case ')': /* end of attribute/value pairs */
5730 ++*txtptr; /* skip past closing paren */
5731 break;
5732 case '\0':
5733 mm_notify (stream,"Unterminated parameter list", WARN);
5734 stream->unhealthy = T;
5735 break;
5736 default:
5737 sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
5738 mm_notify (stream,LOCAL->tmp,WARN);
5739 stream->unhealthy = T;
5740 break;
5742 } while (c && (c != ')'));
5743 /* empty parameter, must be NIL */
5744 else if (((c == 'N') || (c == 'n')) &&
5745 ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
5746 ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
5747 else {
5748 sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,
5749 (char *) (*txtptr) - 1);
5750 mm_notify (stream,LOCAL->tmp,WARN);
5751 stream->unhealthy = T;
5753 return ret;
5756 /* IMAP parse body disposition
5757 * Accepts: MAIL stream
5758 * body structure to write into
5759 * current text pointer
5760 * parsed reply
5763 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
5764 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5766 switch (*++*txtptr) {
5767 case '(':
5768 ++*txtptr; /* skip open paren */
5769 body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5770 LONGT);
5771 body->disposition.parameter =
5772 imap_parse_body_parameter (stream,txtptr,reply);
5773 if (**txtptr != ')') { /* validate ending */
5774 sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",
5775 (char *) *txtptr);
5776 mm_notify (stream,LOCAL->tmp,WARN);
5777 stream->unhealthy = T;
5779 else ++*txtptr; /* skip past delimiter */
5780 break;
5781 case 'N': /* if NIL */
5782 case 'n':
5783 ++*txtptr; /* bump past "N" */
5784 ++*txtptr; /* bump past "I" */
5785 ++*txtptr; /* bump past "L" */
5786 break;
5787 default:
5788 sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",(char *) *txtptr);
5789 mm_notify (stream,LOCAL->tmp,WARN);
5790 stream->unhealthy = T;
5791 /* try to skip to next space */
5792 while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')'));
5793 break;
5797 /* IMAP parse body language
5798 * Accepts: MAIL stream
5799 * current text pointer
5800 * parsed reply
5801 * Returns: string list or NIL if empty or error
5804 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
5805 IMAPPARSEDREPLY *reply)
5807 unsigned long i;
5808 char *s;
5809 STRINGLIST *ret = NIL;
5810 /* language is a list */
5811 if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
5812 else if ((s = imap_parse_string (stream,txtptr,reply,NIL,&i,LONGT)) != NULL) {
5813 (ret = mail_newstringlist ())->text.data = (unsigned char *) s;
5814 ret->text.size = i;
5816 return ret;
5819 /* IMAP parse string list
5820 * Accepts: MAIL stream
5821 * current text pointer
5822 * parsed reply
5823 * Returns: string list or NIL if empty or error
5826 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
5827 IMAPPARSEDREPLY *reply)
5829 STRINGLIST *stl = NIL;
5830 STRINGLIST *stc = NIL;
5831 unsigned char *t = *txtptr;
5832 /* parse the list */
5833 if (*t++ == '(') while (*t != ')') {
5834 if (stl) stc = stc->next = mail_newstringlist ();
5835 else stc = stl = mail_newstringlist ();
5836 /* parse astring */
5837 if (!(stc->text.data =
5838 imap_parse_astring (stream,&t,reply,&stc->text.size))) {
5839 sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t);
5840 mm_notify (stream,LOCAL->tmp,WARN);
5841 stream->unhealthy = T;
5842 mail_free_stringlist (&stl);
5843 break;
5845 else if (*t == ' ') ++t; /* another token follows */
5847 if (stl) *txtptr = ++t; /* update return string */
5848 return stl;
5851 /* IMAP parse unknown body extension data
5852 * Accepts: MAIL stream
5853 * current text pointer
5854 * parsed reply
5856 * Updates text pointer
5859 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
5860 IMAPPARSEDREPLY *reply)
5862 unsigned long i,j;
5863 switch (*++*txtptr) { /* action depends upon first character */
5864 case '(':
5865 while (**txtptr && (**txtptr != ')'))
5866 imap_parse_extension (stream,txtptr,reply);
5867 if (**txtptr) ++*txtptr; /* bump past closing parenthesis */
5868 break;
5869 case '"': /* if quoted string */
5870 while ((*++*txtptr != '"') && **txtptr) if (**txtptr == '\\') ++*txtptr;
5871 if (**txtptr) ++*txtptr; /* bump past closing quote */
5872 break;
5873 case 'N': /* if NIL */
5874 case 'n':
5875 ++*txtptr; /* bump past "N" */
5876 ++*txtptr; /* bump past "I" */
5877 ++*txtptr; /* bump past "L" */
5878 break;
5879 case '{': /* get size of literal */
5880 ++*txtptr; /* bump past open squiggle */
5881 if ((i = strtoul (*txtptr,(char **) txtptr,10)) != 0L) do
5882 net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
5883 LOCAL->tmp);
5884 while (i -= j);
5885 /* get new reply text line */
5886 if (!(reply->line = net_getline (LOCAL->netstream)))
5887 reply->line = cpystr ("");
5888 if (stream->debug) mm_dlog (reply->line);
5889 *txtptr = reply->line; /* set text pointer to point at it */
5890 break;
5891 case '0': case '1': case '2': case '3': case '4':
5892 case '5': case '6': case '7': case '8': case '9':
5893 strtoul (*txtptr,(char **) txtptr,10);
5894 break;
5895 default:
5896 sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr);
5897 mm_notify (stream,LOCAL->tmp,WARN);
5898 stream->unhealthy = T;
5899 /* try to skip to next space */
5900 while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')'));
5901 break;
5905 /* IMAP parse capabilities
5906 * Accepts: MAIL stream
5907 * capability list
5910 void imap_parse_capabilities (MAILSTREAM *stream,char *t)
5912 char *s,*r;
5913 unsigned long i;
5914 THREADER *thr,*th;
5915 if (!LOCAL->gotcapability) { /* need to save previous capabilities? */
5916 /* no, flush threaders */
5917 if ((thr = LOCAL->cap.threader) != NULL) while ((th = thr) != NULL) {
5918 fs_give ((void **) &th->name);
5919 thr = th->next;
5920 fs_give ((void **) &th);
5922 /* zap capabilities */
5923 memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
5924 LOCAL->gotcapability = T; /* flag that capabilities arrived */
5926 for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) {
5927 if (!compare_cstring (t,"IMAP4"))
5928 LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5929 else if (!compare_cstring (t,"IMAP4rev1"))
5930 LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5931 else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T;
5932 else if (!compare_cstring (t,"IMAP2bis"))
5933 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5934 else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T;
5935 else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T;
5936 else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T;
5937 else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T;
5938 else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T;
5939 else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T;
5940 else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T;
5941 else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T;
5942 else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T;
5943 else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T;
5944 else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T;
5945 else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T;
5946 else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T;
5947 else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T;
5948 else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T;
5949 else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T;
5950 else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T;
5951 else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T;
5952 else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T;
5953 else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T;
5954 else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T;
5955 else if (!compare_cstring (t,"X-GM-EXT-1")) LOCAL->cap.x_gm_ext1 = T;
5956 else if (((t[0] == 'S') || (t[0] == 's')) &&
5957 ((t[1] == 'O') || (t[1] == 'o')) &&
5958 ((t[2] == 'R') || (t[2] == 'r')) &&
5959 ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T;
5960 /* capability with value? */
5961 else if ((s = strchr (t,'=')) != NULL) {
5962 *s++ = '\0'; /* separate token from value */
5963 if (!compare_cstring (t,"THREAD") && !LOCAL->loser) {
5964 THREADER *thread = (THREADER *) fs_get (sizeof (THREADER));
5965 thread->name = cpystr (s);
5966 thread->dispatch = NIL;
5967 thread->next = LOCAL->cap.threader;
5968 LOCAL->cap.threader = thread;
5970 else if (!compare_cstring (t,"AUTH")) {
5971 if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) &&
5972 (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i);
5973 else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T;
5976 /* ignore other capabilities */
5978 /* disable LOGIN if PLAIN also advertised */
5979 if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) &&
5980 (LOCAL->cap.auth & (1 << i)) &&
5981 (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS))
5982 LOCAL->cap.auth &= ~(1 << i);
5985 /* IMAP load cache
5986 * Accepts: MAIL stream
5987 * sequence
5988 * flags
5989 * Returns: parsed reply from fetch
5992 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags)
5994 int i = 2;
5995 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ?
5996 "UID FETCH" : "FETCH";
5997 IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl;
5998 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
5999 flags & FT_UID);
6000 args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence;
6001 args[1] = &aarg; aarg.type = ATOM;
6002 aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
6003 ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel];
6004 axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
6005 ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer;
6006 abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
6007 atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
6008 if (LEVELIMAP4 (stream)) { /* include UID if IMAP4 or IMAP4rev1 */
6009 aarg.text = (void *) "(UID";
6010 if (flags & FT_NEEDENV) { /* if need envelopes */
6011 args[i++] = &aenv; /* include envelope */
6012 /* extra header poop if IMAP4rev1 */
6013 if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
6014 args[i++] = &ahhr; /* header header */
6015 if (axtr.text) args[i++] = &axtr;
6016 args[i++] = &ahtr; /* header trailer */
6018 /* fetch body if requested */
6019 if (flags & FT_NEEDBODY) args[i++] = &abdy;
6021 args[i++] = &atrl; /* fetch trailer */
6023 /* easy if IMAP2 */
6024 else aarg.text = (void *) (flags & FT_NEEDENV) ?
6025 ((flags & FT_NEEDBODY) ?
6026 "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
6027 "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
6028 args[i] = NIL; /* tie off command */
6029 return imap_send (stream,cmd,args);
6032 /* Reform sequence for losing server that doesn't handle ranges right
6033 * Accepts: MAIL stream
6034 * sequence
6035 * non-zero if UID
6036 * Returns: sequence
6039 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags)
6041 unsigned long i,j,star;
6042 char *s,*t,*tl,*rs;
6043 /* can't win if empty */
6044 if (!stream->nmsgs) return sequence;
6045 /* get highest possible range value */
6046 star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs;
6047 /* flush old reformed sequence */
6048 if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
6049 rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence));
6050 for (s = sequence; (t = strpbrk (s,",:")) != NULL; ) switch (*t++) {
6051 case ',': /* single message */
6052 strncpy (rs,s,i = t - s); /* copy string up to that point */
6053 rs += i; /* advance destination pointer */
6054 s += i; /* and source */
6055 break;
6056 case ':': /* message range */
6057 i = (*s == '*') ? star : strtoul (s,NIL,10);
6058 if (*t == '*') { /* range ends with star */
6059 j = star;
6060 tl = t+1;
6062 else { /* numeric range end */
6063 j = strtoul (t,(char **) &tl,10);
6064 if (!tl) tl = t + strlen (t);
6066 if (i <= j) { /* if first less than second */
6067 if (*tl) tl++; /* skip past end of range if present */
6068 strncpy (rs,s,i = tl - s);/* copy string up to that point */
6069 rs += i; /* advance destination and source pointers */
6070 s += i;
6072 else { /* here's the workaround for losing servers */
6073 strncpy (rs,t,i = tl - t);/* swap the order */
6074 rs[i] = ':'; /* delimit */
6075 strncpy (rs+i+1,s,j = (t-1) - s);
6076 rs += i + 1 + j; /* advance destination pointer */
6077 if (*tl) *rs++ = *tl++; /* write trailing delimiter if present */
6078 s = tl; /* advance source pointer */
6081 if (*s) strcpy (rs,s); /* write remainder of sequence */
6082 else *rs = '\0'; /* tie off string */
6083 return LOCAL->reform;
6086 /* IMAP return host name
6087 * Accepts: MAIL stream
6088 * Returns: host name
6091 char *imap_host (MAILSTREAM *stream)
6093 if (stream->dtb != &imapdriver)
6094 fatal ("imap_host called on non-IMAP stream!");
6095 /* return host name on stream if open */
6096 return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) :
6097 ".NO-IMAP-CONNECTION.";
6101 /* IMAP return IMAP capability structure
6102 * Accepts: MAIL stream
6103 * Returns: IMAP capability structure
6106 IMAPCAP *imap_cap (MAILSTREAM *stream)
6108 if (stream->dtb != &imapdriver)
6109 fatal ("imap_cap called on non-IMAP stream!");
6110 return &LOCAL->cap; /* return capability structure */