* New version 2.26
[alpine.git] / imap / src / c-client / imap4r1.c
blob85145817b756219cfe73a5a56c71a2044f090522
1 /*
2 * Copyright 2016-2022 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
69 /* IMAP4 I/O stream local data */
71 typedef struct imap_local {
72 NETSTREAM *netstream; /* TCP I/O stream */
73 IMAPPARSEDREPLY reply; /* last parsed reply */
74 MAILSTATUS *stat; /* status to fill in */
75 IMAPCAP cap; /* server capabilities */
76 char *appendmailbox; /* mailbox being appended to */
77 unsigned int authed: 1; /* state: authed or not */
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 long imap_renew (MAILSTREAM *stream,MAILSTREAM *m);
174 MAILSTREAM *imap_open (MAILSTREAM *stream);
175 IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
176 char *usr,char *tmp);
177 long imap_anon (MAILSTREAM *stream,char *tmp);
178 long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr);
179 long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr);
180 void *imap_challenge (void *stream,unsigned long *len);
181 long imap_response (void *stream,char *base,char *s,unsigned long size);
182 void imap_close (MAILSTREAM *stream,long options);
183 void imap_fast (MAILSTREAM *stream,char *sequence,long flags);
184 void imap_flags (MAILSTREAM *stream,char *sequence,long flags);
185 long imap_overview (MAILSTREAM *stream,overview_t ofn);
186 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
187 long flags);
188 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
189 unsigned long first,unsigned long last,STRINGLIST *lines,
190 long flags);
191 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno);
192 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid);
193 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
194 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
195 long imap_search_x_gm_ext1 (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
196 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
197 SORTPGM *pgm,long flags);
198 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
199 SEARCHPGM *spg,long flags);
200 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
201 SEARCHPGM *spg,long flags);
202 long imap_ping (MAILSTREAM *stream);
203 void imap_check (MAILSTREAM *stream);
204 long imap_expunge (MAILSTREAM *stream,char *sequence,long options);
205 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
206 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
207 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
208 char *flags,char *date,STRING *message,
209 APPENDDATA *map,long options);
210 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
211 char *flags,char *date,STRING *message);
213 void imap_gc (MAILSTREAM *stream,long gcflags);
214 void imap_gc_body (BODY *body);
215 void imap_capability (MAILSTREAM *stream);
216 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]);
218 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]);
219 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s);
220 long imap_soutr (MAILSTREAM *stream,char *string);
221 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
222 SIZEDTEXT *as,long wildok,char *limit);
223 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
224 STRING *st);
225 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
226 char **s,SEARCHPGM *pgm,char *limit);
227 char *imap_send_spgm_trim (char *base,char *s,char *text);
228 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
229 char **s,SEARCHSET *set,char *prefix,
230 char *limit);
231 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
232 char **s,char *name,STRINGLIST *list,
233 char *limit);
234 void imap_send_sdate (char **s,char *name,unsigned short date);
235 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag);
236 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text);
237 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text);
238 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
239 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
240 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy);
241 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
242 IMAPPARSEDREPLY *reply);
243 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr);
244 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
245 STRINGLIST *stl);
246 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
247 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
248 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
249 IMAPPARSEDREPLY *reply);
250 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
251 IMAPPARSEDREPLY *reply);
252 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
253 unsigned char **txtptr);
254 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag);
255 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
256 IMAPPARSEDREPLY *reply,unsigned long *len);
257 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
258 IMAPPARSEDREPLY *reply,GETS_DATA *md,
259 unsigned long *len,long flags);
260 void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr,
261 IMAPPARSEDREPLY *reply);
262 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
263 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
264 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
265 unsigned char **txtptr,
266 IMAPPARSEDREPLY *reply);
267 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
268 unsigned char **txtptr,IMAPPARSEDREPLY *reply);
269 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
270 IMAPPARSEDREPLY *reply);
271 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
272 IMAPPARSEDREPLY *reply);
273 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
274 IMAPPARSEDREPLY *reply);
275 void imap_parse_capabilities (MAILSTREAM *stream,char *t);
276 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags);
277 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags);
278 long imap_setid(MAILSTREAM *stream, IDLIST *idlist);
279 IDLIST *imap_parse_idlist(char *text);
281 /* Driver dispatch used by MAIL */
283 DRIVER imapdriver = {
284 "imap", /* driver name */
285 /* driver flags */
286 DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN,
287 (DRIVER *) NIL, /* next driver */
288 imap_valid, /* mailbox is valid for us */
289 imap_parameters, /* manipulate parameters */
290 imap_scan, /* scan mailboxes */
291 imap_list, /* find mailboxes */
292 imap_lsub, /* find subscribed mailboxes */
293 imap_subscribe, /* subscribe to mailbox */
294 imap_unsubscribe, /* unsubscribe from mailbox */
295 imap_create, /* create mailbox */
296 imap_delete, /* delete mailbox */
297 imap_rename, /* rename mailbox */
298 imap_status, /* status of mailbox */
299 imap_open, /* open mailbox */
300 imap_close, /* close mailbox */
301 imap_fast, /* fetch message "fast" attributes */
302 imap_flags, /* fetch message flags */
303 imap_overview, /* fetch overview */
304 imap_structure, /* fetch message envelopes */
305 NIL, /* fetch message header */
306 NIL, /* fetch message body */
307 imap_msgdata, /* fetch partial message */
308 imap_uid, /* unique identifier */
309 imap_msgno, /* message number */
310 imap_flag, /* modify flags */
311 NIL, /* per-message modify flags */
312 imap_search, /* search for message based on criteria */
313 imap_sort, /* sort messages */
314 imap_thread, /* thread messages */
315 imap_ping, /* ping mailbox to see if still alive */
316 imap_check, /* check for new messages */
317 imap_expunge, /* expunge deleted messages */
318 imap_copy, /* copy messages to another mailbox */
319 imap_append, /* append string message to mailbox */
320 imap_gc, /* garbage collect stream */
321 imap_renew /* renew stream */
324 /* prototype stream */
325 MAILSTREAM imapproto = {&imapdriver};
327 /* driver parameters */
328 static unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
329 static long imap_lookahead = IMAPLOOKAHEAD;
330 static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
331 static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD;
332 static long imap_defaultport = 0;
333 static long imap_sslport = 0;
334 static long imap_tryssl = NIL;
335 static long imap_prefetch = IMAPLOOKAHEAD;
336 static long imap_closeonerror = NIL;
337 static imapenvelope_t imap_envelope = NIL;
338 static imapreferral_t imap_referral = NIL;
339 static char *imap_extrahdrs = NIL;
341 /* constants */
342 static char *hdrheader[] = {
343 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
344 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
345 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
346 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
347 "BODY.PEEK[HEADER.FIELDS (Newsgroups"
349 static char *hdrtrailer ="Followup-To References)]";
351 /* IMAP validate mailbox
352 * Accepts: mailbox name
353 * Returns: our driver if name is valid, NIL otherwise
356 DRIVER *imap_valid (char *name)
358 return mail_valid_net (name,&imapdriver,NIL,NIL);
362 /* IMAP manipulate driver parameters
363 * Accepts: function code
364 * function-dependent value
365 * Returns: function-dependent return value
368 void *imap_parameters (long function,void *value)
370 switch ((int) function) {
371 case GET_NAMESPACE:
372 if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace &&
373 !((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace)
374 imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL);
375 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace;
376 break;
377 case GET_THREADERS:
378 value = (void *)
379 ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader;
380 break;
381 case SET_FETCHLOOKAHEAD: /* must use pointer from GET_FETCHLOOKAHEAD */
382 fatal ("SET_FETCHLOOKAHEAD not permitted");
383 case GET_FETCHLOOKAHEAD:
384 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead;
385 break;
386 case SET_MAXLOGINTRIALS:
387 imap_maxlogintrials = (long) value;
388 break;
389 case GET_MAXLOGINTRIALS:
390 value = (void *) imap_maxlogintrials;
391 break;
392 case SET_LOOKAHEAD:
393 imap_lookahead = (long) value;
394 break;
395 case GET_LOOKAHEAD:
396 value = (void *) imap_lookahead;
397 break;
398 case SET_UIDLOOKAHEAD:
399 imap_uidlookahead = (long) value;
400 break;
401 case GET_UIDLOOKAHEAD:
402 value = (void *) imap_uidlookahead;
403 break;
405 case SET_IMAPPORT:
406 imap_defaultport = (long) value;
407 break;
408 case GET_IMAPPORT:
409 value = (void *) imap_defaultport;
410 break;
411 case SET_SSLIMAPPORT:
412 imap_sslport = (long) value;
413 break;
414 case GET_SSLIMAPPORT:
415 value = (void *) imap_sslport;
416 break;
417 case SET_PREFETCH:
418 imap_prefetch = (long) value;
419 break;
420 case GET_PREFETCH:
421 value = (void *) imap_prefetch;
422 break;
423 case SET_CLOSEONERROR:
424 imap_closeonerror = (long) value;
425 break;
426 case GET_CLOSEONERROR:
427 value = (void *) imap_closeonerror;
428 break;
429 case SET_IMAPENVELOPE:
430 imap_envelope = (imapenvelope_t) value;
431 break;
432 case GET_IMAPENVELOPE:
433 value = (void *) imap_envelope;
434 break;
435 case SET_IMAPREFERRAL:
436 imap_referral = (imapreferral_t) value;
437 break;
438 case GET_IMAPREFERRAL:
439 value = (void *) imap_referral;
440 break;
441 case SET_IMAPEXTRAHEADERS:
442 imap_extrahdrs = (char *) value;
443 break;
444 case GET_IMAPEXTRAHEADERS:
445 value = (void *) imap_extrahdrs;
446 break;
447 case SET_IMAPTRYSSL:
448 imap_tryssl = (long) value;
449 break;
450 case GET_IMAPTRYSSL:
451 value = (void *) imap_tryssl;
452 break;
453 case SET_FETCHLOOKAHEADLIMIT:
454 imap_fetchlookaheadlimit = (long) value;
455 break;
456 case GET_FETCHLOOKAHEADLIMIT:
457 value = (void *) imap_fetchlookaheadlimit;
458 break;
460 case SET_IDLETIMEOUT:
461 fatal ("SET_IDLETIMEOUT not permitted");
462 case GET_IDLETIMEOUT:
463 value = (void *) IDLETIMEOUT;
464 break;
465 case SET_IDSTREAM: /* set IMAP server ID */
466 fatal ("SET_IDSTREAM not permitted");
467 case GET_IDSTREAM: /* get IMAP server ID */
468 value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->id;
469 break;
470 default:
471 value = NIL; /* error case */
472 break;
474 return value;
477 /* IMAP scan mailboxes
478 * Accepts: mail stream
479 * reference
480 * pattern to search
481 * string to scan
484 void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
486 imap_list_work (stream,"SCAN",ref,pat,contents);
490 /* IMAP list mailboxes
491 * Accepts: mail stream
492 * reference
493 * pattern to search
496 void imap_list (MAILSTREAM *stream,char *ref,char *pat)
498 imap_list_work (stream,"LIST",ref,pat,NIL);
502 /* IMAP list subscribed mailboxes
503 * Accepts: mail stream
504 * reference
505 * pattern to search
508 void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
510 void *sdb = NIL;
511 char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
512 /* do it on the server */
513 imap_list_work (stream,"LSUB",ref,pat,NIL);
514 if (*pat == '{') { /* if remote pattern, must be IMAP */
515 if (!imap_valid (pat)) return;
516 ref = NIL; /* good IMAP pattern, punt reference */
518 /* if remote reference, must be valid IMAP */
519 if (ref && (*ref == '{') && !imap_valid (ref)) return;
520 /* kludgy application of reference */
521 if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
522 else strcpy (mbx,pat);
524 if ((s = sm_read (tmp,&sdb)) != NULL) do if (imap_valid (s) && pmatch (s,mbx))
525 mm_lsub (stream,NIL,s,NIL);
526 /* until no more subscriptions */
527 while ((s = sm_read (tmp,&sdb)) != NULL);
530 /* IMAP find list of mailboxes
531 * Accepts: mail stream
532 * list command
533 * reference
534 * pattern to search
535 * string to scan
538 void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
539 char *contents)
541 MAILSTREAM *st = stream;
542 int pl;
543 char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN];
544 IMAPARG *args[4],aref,apat,acont;
545 if (ref && *ref) { /* have a reference? */
546 if (!(imap_valid (ref) && /* make sure valid IMAP name and open stream */
547 ((stream && LOCAL && LOCAL->netstream) ||
548 (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return;
549 /* calculate prefix length */
550 pl = strchr (ref,'}') + 1 - ref;
551 strncpy (prefix,ref,pl); /* build prefix */
552 prefix[pl] = '\0'; /* tie off prefix */
553 ref += pl; /* update reference */
555 else {
556 if (!(imap_valid (pat) && /* make sure valid IMAP name and open stream */
557 ((stream && LOCAL && LOCAL->netstream) ||
558 (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return;
559 /* calculate prefix length */
560 pl = strchr (pat,'}') + 1 - pat;
561 strncpy (prefix,pat,pl); /* build prefix */
562 prefix[pl] = '\0'; /* tie off prefix */
563 pat += pl; /* update reference */
565 LOCAL->prefix = prefix; /* note prefix */
566 if (contents) { /* want to do a scan? */
567 if (LEVELSCAN (stream)) { /* make sure permitted */
568 args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
569 aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
570 apat.type = LISTMAILBOX; apat.text = (void *) pat;
571 acont.type = ASTRING; acont.text = (void *) contents;
572 imap_send (stream,cmd,args);
574 else mm_log ("Scan not valid on this IMAP server",ERROR);
577 else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */
578 args[0] = &aref; args[1] = &apat; args[2] = NIL;
579 aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
580 apat.type = LISTMAILBOX; apat.text = (void *) pat;
581 /* referrals armed? */
582 if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) {
583 /* yes, convert LIST -> RLIST */
584 if (!compare_cstring (cmd,"LIST")) cmd = "RLIST";
585 /* and convert LSUB -> RLSUB */
586 else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB";
588 imap_send (stream,cmd,args);
590 else if (LEVEL1176 (stream)) {/* convert to IMAP2 format wildcard */
591 /* kludgy application of reference */
592 if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
593 else strcpy (mbx,pat);
594 for (s = mbx; *s; s++) if (*s == '%') *s = '*';
595 args[0] = &apat; args[1] = NIL;
596 apat.type = LISTMAILBOX; apat.text = (void *) mbx;
597 if (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
598 strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) &&
599 !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
600 LOCAL->cap.rfc1176 = NIL; /* must be RFC-1064 */
602 LOCAL->prefix = NIL; /* no more prefix */
603 /* close temporary stream if we made one */
604 if (stream != st) mail_close (stream);
607 /* IMAP subscribe to mailbox
608 * Accepts: mail stream
609 * mailbox to add to subscription list
610 * Returns: T on success, NIL on failure
613 long imap_subscribe (MAILSTREAM *stream,char *mailbox)
615 MAILSTREAM *st = stream;
616 long ret = ((stream && LOCAL && LOCAL->netstream) ||
617 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
618 imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
619 "Subscribe" : "Subscribe Mailbox",NIL) : NIL;
620 /* toss out temporary stream */
621 if (st != stream) mail_close (stream);
622 return ret;
626 /* IMAP unsubscribe to mailbox
627 * Accepts: mail stream
628 * mailbox to delete from manage list
629 * Returns: T on success, NIL on failure
632 long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
634 MAILSTREAM *st = stream;
635 long ret = ((stream && LOCAL && LOCAL->netstream) ||
636 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
637 imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
638 "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL;
639 /* toss out temporary stream */
640 if (st != stream) mail_close (stream);
641 return ret;
644 /* IMAP create mailbox
645 * Accepts: mail stream
646 * mailbox name to create
647 * Returns: T on success, NIL on failure
650 long imap_create (MAILSTREAM *stream,char *mailbox)
652 return imap_manage (stream,mailbox,"Create",NIL);
656 /* IMAP delete mailbox
657 * Accepts: mail stream
658 * mailbox name to delete
659 * Returns: T on success, NIL on failure
662 long imap_delete (MAILSTREAM *stream,char *mailbox)
664 return imap_manage (stream,mailbox,"Delete",NIL);
668 /* IMAP rename mailbox
669 * Accepts: mail stream
670 * old mailbox name
671 * new mailbox name
672 * Returns: T on success, NIL on failure
675 long imap_rename (MAILSTREAM *stream,char *old,char *newname)
677 return imap_manage (stream,old,"Rename",newname);
680 /* IMAP manage a mailbox
681 * Accepts: mail stream
682 * mailbox to manipulate
683 * command to execute
684 * optional second argument
685 * Returns: T on success, NIL on failure
688 long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
690 MAILSTREAM *st = stream;
691 IMAPPARSEDREPLY *reply;
692 long ret = NIL;
693 char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
694 IMAPARG *args[3],ambx,amb2;
695 imapreferral_t ir =
696 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
697 ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
698 amb2.text = (void *) mbx2;
699 args[0] = &ambx; args[1] = args[2] = NIL;
700 /* require valid names and open stream */
701 if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) &&
702 (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) &&
703 ((stream && LOCAL && LOCAL->netstream) ||
704 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
705 if (arg2) args[1] = &amb2; /* second arg present? */
706 if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) &&
707 ir && LOCAL->referral) {
708 long code = -1;
709 switch (*command) { /* which command was it? */
710 case 'S': code = REFSUBSCRIBE; break;
711 case 'U': code = REFUNSUBSCRIBE; break;
712 case 'C': code = REFCREATE; break;
713 case 'D': code = REFDELETE; break;
714 case 'R': code = REFRENAME; break;
715 default:
716 fatal ("impossible referral command");
718 if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code)))
719 ret = imap_manage (NIL,mailbox,command,(*command == 'R') ?
720 (mailbox + strlen (mailbox) + 1) : NIL);
722 mm_log (reply->text,ret ? NIL : ERROR);
723 /* toss out temporary stream */
724 if (st != stream) mail_close (stream);
726 return ret;
729 /* IMAP status
730 * Accepts: mail stream
731 * mailbox name
732 * status flags
733 * Returns: T on success, NIL on failure
736 long imap_status (MAILSTREAM *stream,char *mbx,long flags)
738 IMAPARG *args[3],ambx,aflg;
739 char tmp[MAILTMPLEN];
740 NETMBX mb;
741 unsigned long i;
742 long ret = NIL;
743 MAILSTREAM *tstream = NIL;
744 /* use given stream if (rev1 or halfopen) and
745 right host */
746 if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) &&
747 mail_usable_network_stream (stream,mbx)) ||
748 (stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))))
749 return NIL;
750 /* parse mailbox name */
751 mail_valid_net_parse (mbx,&mb);
752 args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
753 ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
754 if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */
755 imapreferral_t ir;
756 aflg.type = FLAGS; aflg.text = (void *) tmp;
757 args[1] = &aflg; args[2] = NIL;
758 tmp[0] = tmp[1] = '\0'; /* build flag list */
759 if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
760 if (flags & SA_RECENT) strcat (tmp," RECENT");
761 if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
762 if (flags & SA_UIDNEXT) strcat (tmp," UIDNEXT");
763 if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY");
764 tmp[0] = '(';
765 strcat (tmp,")");
766 /* send "STATUS mailbox flag" */
767 if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
768 else if ((ir = (imapreferral_t)
769 mail_parameters (stream,GET_IMAPREFERRAL,NIL)) &&
770 LOCAL->referral &&
771 (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS)))
772 ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL));
775 /* IMAP2 way */
776 else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) {
777 MAILSTATUS status;
778 status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY);
779 status.messages = stream->nmsgs;
780 status.recent = stream->recent;
781 status.unseen = 0;
782 if (flags & SA_UNSEEN) { /* must search to get unseen messages */
783 /* clear search vector */
784 for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
785 if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL)))
786 for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
787 if (mail_elt (stream,i)->searched) status.unseen++;
789 strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox);
790 /* pass status to main program */
791 mm_status (stream,tmp,&status);
792 ret = T; /* note success */
794 if (tstream) mail_close (tstream);
795 return ret; /* success */
798 /* IMAP renew
799 * Accepts: stream to renew
800 * returns 0 if success, 1 if failure
802 long imap_renew (MAILSTREAM *stream, MAILSTREAM *m)
804 IMAPLOCAL *MLOCAL = (IMAPLOCAL *) m->local;
805 NETSTREAM *xnetstream;
807 xnetstream = LOCAL->netstream;
808 LOCAL->netstream = MLOCAL->netstream;
809 MLOCAL->netstream = xnetstream;
811 return 0L;
814 /* IMAP open
815 * Accepts: stream to open
816 * Returns: stream to use on success, NIL on failure
819 MAILSTREAM *imap_open (MAILSTREAM *stream)
821 unsigned long i,j, preauthed;
822 char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN];
823 NETMBX mb;
824 IMAPPARSEDREPLY *reply = NIL;
825 imapreferral_t ir =
826 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
827 /* return prototype for OP_PROTOTYPE call */
828 if (!stream) return &imapproto;
829 mail_valid_net_parse (stream->mailbox,&mb);
830 usr[0] = '\0'; /* initially no user name */
831 if (LOCAL) { /* if stream opened earlier by us */
832 /* recycle if still alive */
833 if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) {
834 i = stream->silent; /* temporarily mark silent */
835 stream->silent = T; /* don't give mm_exists() events */
836 j = imap_ping (stream); /* learn if stream still alive */
837 stream->silent = i; /* restore prior state */
838 if (j) { /* was stream still alive? */
839 sprintf (tmp,"Reusing connection to %s",net_host (LOCAL->netstream));
840 if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
841 LOCAL->user);
842 if (!stream->silent) mm_log (tmp,(long) NIL);
843 /* unselect if now want halfopen */
844 if (stream->halfopen) imap_send (stream,"UNSELECT",NIL);
846 else imap_close (stream,NIL);
848 else imap_close (stream,NIL);
850 /* copy flags from name */
851 if (mb.dbgflag) stream->debug = T;
852 if (mb.readonlyflag) stream->rdonly = T;
853 if (mb.anoflag) stream->anonymous = T;
854 if (mb.secflag) stream->secure = T;
855 if (mb.trysslflag || imap_tryssl) stream->tryssl = T;
857 if (!LOCAL) { /* open new connection if no recycle */
858 NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
859 unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
860 unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT;
861 stream->local = /* instantiate localdata */
862 (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL));
863 /* assume IMAP2bis server */
864 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
865 /* in case server is a loser */
866 if (mb.loser) LOCAL->loser = T;
867 /* desirable authenticators */
868 LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) |
869 (mb.authuser[0] ? AU_AUTHUSER : NIL);
870 /* IMAP connection open logic is more complex than net_open() normally
871 * deals with, because of the simap and rimap hacks.
872 * If the session is anonymous, a specific port is given, or if /ssl or
873 * /starttls is set, do net_open() since those conditions override everything
874 * else.
876 if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag)
877 reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps",
878 sslport)) ?
879 imap_reply (stream,NIL) : NIL;
881 * No overriding conditions, so get the best connection that we can. In
882 * order, attempt to open via simap, tryssl, rimap, and finally TCP.
884 /* try simap */
885 else if ((reply = imap_rimap (stream,"*imap",&mb,usr,tmp)) != NULL);
886 else if (ssld && /* try tryssl if enabled */
887 (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) &&
888 (LOCAL->netstream =
889 net_open_work (ssld,mb.host,"*imaps",sslport,mb.port,
890 (mb.novalidate ? NET_NOVALIDATECERT : 0) |
891 NET_SILENT | NET_TRYSSL))) {
892 if (net_sout (LOCAL->netstream,"",0)) {
893 mb.sslflag = T;
894 reply = imap_reply (stream,NIL);
896 else { /* flush fake SSL stream */
897 net_close (LOCAL->netstream);
898 LOCAL->netstream = NIL;
901 /* try rimap first, then TCP */
902 else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) &&
903 (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL)))
904 reply = imap_reply (stream,NIL);
905 /* make sure greeting is good */
906 if (!reply || strcmp (reply->tag,"*") ||
907 (strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) {
908 if (reply) mm_log (reply->text,ERROR);
909 return NIL; /* lost during greeting */
912 if(stream->unhealthy){
913 mm_log("Aborting due to bad protocol before authentication", ERROR);
914 return NIL;
917 preauthed = !strcmp (reply->key,"PREAUTH");
918 if(preauthed) LOCAL->authed = T;
919 /* STARTTLS is not allowed in PREAUTH state */
920 if (LOCAL->netstream && preauthed){
921 sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
922 if (!LOCAL->gotcapability) imap_capability (stream);
923 if (LOCAL->netstream
924 && stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag && mb.tlsflag){
925 mm_log("STARTTLS not allowed on PREAUTH state. Closing Connection", ERROR);
926 return NIL;
929 /* if connected and not preauthenticated */
930 if (LOCAL->netstream && !preauthed) {
931 sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
932 /* get server capabilities */
933 if (!LOCAL->gotcapability) imap_capability (stream);
934 if (LOCAL->netstream && /* does server support STARTTLS? */
935 stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag &&
936 imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) {
937 if(stream->unhealthy){
938 mm_log("Aborting due to bad protocol before authentication", ERROR);
939 return NIL;
941 mb.tlsflag = T; /* TLS OK, get into TLS at this end */
942 LOCAL->netstream->dtb = ssld;
943 if (!(LOCAL->netstream->stream =
944 (*stls) (LOCAL->netstream->stream,mb.host,
945 SSL_MTHD(mb) | (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) {
946 /* drat, drop this connection */
947 if (LOCAL->netstream) net_close (LOCAL->netstream);
948 LOCAL->netstream = NIL;
950 /* get capabilities now that TLS in effect */
951 if (LOCAL->netstream) imap_capability (stream);
952 if(stream->unhealthy){
953 mm_log("Aborting due to bad protocol before authentication", ERROR);
954 return NIL;
957 else if (mb.tlsflag) { /* user specified /starttls but can't do it */
958 mm_log ("Unable to negotiate TLS with this server",ERROR);
959 return NIL;
961 if (LOCAL->netstream) { /* still in the land of the living? */
962 if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
963 /* remote name for authentication */
964 strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)?
965 net_remotehost (LOCAL->netstream) :
966 net_host (LOCAL->netstream),NETMAXHOST-1);
967 mb.host[NETMAXHOST-1] = '\0';
969 /* need new capabilities after login */
970 LOCAL->gotcapability = NIL;
971 if (!(stream->anonymous ? imap_anon (stream,tmp) :
972 (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) :
973 imap_login (stream,&mb,tmp,usr)))) {
974 /* failed, is there a referral? */
975 if (mb.tlsflag) LOCAL->tlsflag = T;
976 if (ir && LOCAL->referral &&
977 (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) {
978 imap_close (stream,NIL);
979 fs_give ((void **) &stream->mailbox);
980 /* set as new mailbox name to open */
981 stream->mailbox = s;
982 return imap_open (stream);
984 return NIL; /* authentication failed */
986 else if (ir && LOCAL->referral &&
987 (s = (*ir) (stream,LOCAL->referral,REFAUTH))) {
988 imap_close (stream,NIL);
989 fs_give ((void **) &stream->mailbox);
990 stream->mailbox = s; /* set as new mailbox name to open */
991 /* recurse to log in on real site */
992 return imap_open (stream);
994 else LOCAL->authed = T;
997 /* get server capabilities again */
998 if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream);
999 /* save state for future recycling */
1000 if (mb.tlsflag) LOCAL->tlsflag = T;
1001 if (mb.tls1) LOCAL->tls1 = T;
1002 if (mb.tls1_1) LOCAL->tls1_1 = T;
1003 if (mb.tls1_2) LOCAL->tls1_2 = T;
1004 if (mb.tls1_3) LOCAL->tls1_3 = T;
1005 if (mb.tlssslv23) LOCAL->tlssslv23 = T;
1006 if (mb.notlsflag) LOCAL->notlsflag = T;
1007 if (mb.sslflag) LOCAL->sslflag = T;
1008 if (mb.novalidate) LOCAL->novalidate = T;
1009 if (mb.loser) LOCAL->loser = T;
1012 if (LOCAL->netstream) { /* still have a connection? */
1013 stream->perm_seen = stream->perm_deleted = stream->perm_answered =
1014 stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T;
1015 stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff;
1016 stream->sequence++; /* bump sequence number */
1017 sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
1018 net_host (LOCAL->netstream) : mb.host);
1019 if (!((i = net_port (LOCAL->netstream)) & 0xffff0000))
1020 sprintf (tmp + strlen (tmp),":%lu",i);
1021 strcat (tmp,"/imap");
1022 if (LOCAL->tlsflag) strcat (tmp,"/starttls");
1023 if (LOCAL->tls1) strcat (tmp,"/tls1");
1024 if (LOCAL->tls1_1) strcat (tmp,"/tls1_1");
1025 if (LOCAL->tls1_2) strcat (tmp,"/tls1_2");
1026 if (LOCAL->tls1_3) strcat (tmp,"/tls1_3");
1027 if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
1028 if (LOCAL->notlsflag) strcat (tmp,"/nostarttls");
1029 if (LOCAL->sslflag) strcat (tmp,"/ssl");
1030 if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
1031 if (LOCAL->loser) strcat (tmp,"/loser");
1032 if (stream->secure) strcat (tmp,"/secure");
1033 if (stream->rdonly) strcat (tmp,"/readonly");
1034 if (stream->anonymous) strcat (tmp,"/anonymous");
1035 else { /* record user name */
1036 if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr);
1037 if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
1038 LOCAL->user);
1040 strcat (tmp,"}");
1042 if(LEVELID(stream)){ /* Set ID of app */
1043 IDLIST *idapp = (IDLIST *) mail_parameters(NIL, GET_IDPARAMS, NIL);
1044 if(idapp && !LOCAL->setid){
1045 imap_setid(stream, idapp);
1046 LOCAL->setid++;
1049 if (!stream->halfopen) { /* wants to open a mailbox? */
1050 IMAPARG *args[2];
1051 IMAPARG ambx;
1052 ambx.type = ASTRING;
1053 ambx.text = (void *) mb.mailbox;
1054 args[0] = &ambx; args[1] = NIL;
1055 stream->nmsgs = 0;
1056 if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
1057 "EXAMINE": "SELECT",args))) {
1058 strcat (tmp,mb.mailbox);/* mailbox name */
1059 if (!stream->nmsgs && !stream->silent)
1060 mm_log ("Mailbox is empty",(long) NIL);
1061 /* note if an INBOX or not */
1062 stream->inbox = !compare_cstring (mb.mailbox,"INBOX");
1064 else if (ir && LOCAL->referral &&
1065 (s = (*ir) (stream,LOCAL->referral,REFSELECT))) {
1066 imap_close (stream,NIL);
1067 fs_give ((void **) &stream->mailbox);
1068 stream->mailbox = s; /* set as new mailbox name to open */
1069 return imap_open (stream);
1071 else {
1072 mm_log (reply->text,ERROR);
1073 if (imap_closeonerror) return NIL;
1074 stream->halfopen = T; /* let him keep it half-open */
1077 if (stream->halfopen) { /* half-open connection? */
1078 strcat (tmp,"<no_mailbox>");
1079 /* make sure dummy message counts */
1080 mail_exists (stream,(long) 0);
1081 mail_recent (stream,(long) 0);
1083 fs_give ((void **) &stream->mailbox);
1084 stream->mailbox = cpystr (tmp);
1086 /* success if stream open */
1087 return LOCAL->netstream ? stream : NIL;
1090 /* IMAP rimap connect
1091 * Accepts: MAIL stream
1092 * NETMBX specification
1093 * service to use
1094 * user name
1095 * scratch buffer
1096 * Returns: parsed reply if success, else NIL
1099 IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
1100 char *usr,char *tmp)
1102 unsigned long i;
1103 char c[2];
1104 NETSTREAM *tstream;
1105 IMAPPARSEDREPLY *reply = NIL;
1106 /* try rimap open */
1107 if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) {
1108 /* if success, see if reasonable banner */
1109 if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
1110 i = 0; /* copy to buffer */
1111 do tmp[i++] = *c;
1112 while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
1113 (*c != '\012') && (i < (MAILTMPLEN-1)));
1114 tmp[i] = '\0'; /* tie off */
1115 /* snarfed a valid greeting? */
1116 if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) &&
1117 (*c == '\012') &&
1118 !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){
1119 /* parse line as IMAP */
1120 imap_parse_unsolicited (stream,reply);
1121 /* make sure greeting is good */
1122 if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
1123 LOCAL->netstream = tstream;
1124 return reply; /* return success */
1128 net_close (tstream); /* failed, punt the temporary netstream */
1130 return NIL;
1133 /* IMAP log in as anonymous
1134 * Accepts: stream to authenticate
1135 * scratch buffer
1136 * Returns: T on success, NIL on failure
1139 long imap_anon (MAILSTREAM *stream,char *tmp)
1141 IMAPPARSEDREPLY *reply;
1142 char *s = net_localhost (LOCAL->netstream);
1143 if (LOCAL->cap.authanon) {
1144 char tag[16];
1145 unsigned long i;
1146 char *broken = "[CLOSED] IMAP connection broken (anonymous auth)";
1147 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1148 /* build command */
1149 sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag);
1150 if (!imap_soutr (stream,tmp)) {
1151 mm_log (broken,ERROR);
1152 return NIL;
1154 if (imap_challenge (stream,&i)) imap_response (stream,NIL,s,strlen (s));
1155 /* get response */
1156 if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken);
1157 /* what we wanted? */
1158 if (compare_cstring (reply->tag,tag)) {
1159 /* abort if don't have tagged response */
1160 while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1161 imap_soutr (stream,"*");
1164 else {
1165 IMAPARG *args[2];
1166 IMAPARG ausr;
1167 ausr.type = ASTRING;
1168 ausr.text = (void *) s;
1169 args[0] = &ausr; args[1] = NIL;
1170 /* send "LOGIN anonymous <host>" */
1171 reply = imap_send (stream,"LOGIN ANONYMOUS",args);
1173 /* success if reply OK */
1174 if (imap_OK (stream,reply)) return T;
1175 mm_log (reply->text,ERROR);
1176 return NIL;
1179 /* IMAP authenticate
1180 * Accepts: stream to authenticate
1181 * parsed network mailbox structure
1182 * scratch buffer
1183 * place to return user name
1184 * Returns: T on success, NIL on failure
1187 long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
1189 unsigned long trial,ua,uasaved = NIL;
1190 int ok;
1191 char tag[16];
1192 char *lsterr = NIL, *base;
1193 AUTHENTICATOR *at, *atsaved;
1194 IMAPPARSEDREPLY *reply;
1195 for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua &&
1196 (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
1197 if(mb && *mb->auth){
1198 if(!compare_cstring(at->name, mb->auth))
1199 atsaved = at;
1200 else{
1201 uasaved = ua;
1202 continue;
1205 if (lsterr) { /* previous authenticator failed? */
1206 sprintf (tmp,"Retrying using %s authentication after %.80s",
1207 at->name,lsterr);
1208 mm_log (tmp,NIL);
1209 delete_password(mb, usr);
1210 fs_give ((void **) &lsterr);
1212 trial = 0; /* initial trial count */
1213 tmp[0] = '\0'; /* no error */
1214 do { /* gensym a new tag */
1215 if (lsterr) { /* previous attempt with this one failed? */
1216 sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
1217 mm_log (tmp,WARN);
1218 fs_give ((void **) &lsterr);
1219 delete_password(mb, usr);
1221 LOCAL->saslcancel = NIL;
1222 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
1223 /* build command */
1224 sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name);
1225 base = (at->flags & AU_SINGLE) && LOCAL->cap.sasl_ir
1226 ? (char *) tmp : NIL;
1227 if (base || imap_soutr (stream,tmp)) {
1228 /* report we tried this authenticator */
1229 if(base && stream && stream->debug) mm_dlog (base);
1230 /* hide client authentication responses */
1231 if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T;
1232 ok = (*at->client) (imap_challenge,imap_response,base,"imap",mb,stream,
1233 net_port(LOCAL->netstream),&trial,usr);
1234 LOCAL->sensitive = NIL; /* unhide */
1236 if(base && !trial){ /* do it now, instead of later */
1237 mm_log ("IMAP Authentication cancelled",ERROR);
1238 return NIL;
1240 /* make sure have a response */
1241 if (!(reply = &LOCAL->reply)->tag)
1242 reply = imap_fake (stream,tag,
1243 "[CLOSED] IMAP connection broken (authenticate)");
1244 else if (compare_cstring (reply->tag,tag))
1245 while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
1246 imap_soutr (stream,"*");
1247 /* good if SASL ok and success response */
1248 if (ok && imap_OK (stream,reply)){
1249 if(stream->auth.name) fs_give((void **) &stream->auth.name);
1250 stream->auth.name = cpystr(at->name); /* save method name */
1251 return T;
1253 if (!trial) { /* if main program requested cancellation */
1254 mm_log ("IMAP Authentication cancelled",ERROR);
1255 delete_password(mb, usr);
1256 return NIL;
1258 /* no error if protocol-initiated cancel */
1259 lsterr = cpystr (reply->text);
1262 while (LOCAL->netstream && !LOCAL->byeseen && trial &&
1263 (trial < imap_maxlogintrials));
1265 if (lsterr) { /* previous authenticator failed? */
1266 if (!LOCAL->saslcancel) { /* don't do this if a cancel */
1267 sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr);
1268 mm_log (tmp,ERROR);
1270 if(LOCAL->netstream && !LOCAL->byeseen)
1271 delete_password(mb, usr);
1272 fs_give ((void **) &lsterr);
1274 if(mb && *mb->auth){
1275 if(!uasaved) sprintf (tmp,"Client does not support AUTH=%.80s authenticator",mb->auth);
1276 else if (!atsaved) sprintf (tmp,"IMAP server does not support AUTH=%.80s authenticator",mb->auth);
1277 if (!uasaved || !atsaved) mm_log (tmp,ERROR);
1279 return NIL; /* ran out of authenticators */
1282 /* IMAP login
1283 * Accepts: stream to login
1284 * parsed network mailbox structure
1285 * scratch buffer of length MAILTMPLEN
1286 * place to return user name
1287 * Returns: T on success, NIL on failure
1290 long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
1292 unsigned long trial = 0;
1293 IMAPPARSEDREPLY *reply;
1294 IMAPARG *args[3];
1295 IMAPARG ausr,apwd;
1296 long ret = NIL;
1297 char *app_pwd = NIL;
1298 if (stream->secure) /* never do LOGIN if want security */
1299 mm_log ("Can't do secure authentication with this server",ERROR);
1300 /* never do LOGIN if server disabled it */
1301 else if (LOCAL->cap.logindisabled)
1302 mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR);
1303 else if (mb->authuser[0]) /* never do LOGIN with /authuser */
1304 mm_log ("Can't do /authuser with this server",ERROR);
1305 else { /* OK to try login */
1306 ausr.type = apwd.type = ASTRING;
1307 ausr.text = (void *) usr;
1308 apwd.text = (void *) pwd;
1309 args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
1310 do {
1311 if(app_pwd) fs_give((void **) &app_pwd);
1312 pwd[0] = '\0';
1313 mm_login (mb,usr, &app_pwd,trial++);
1314 if(app_pwd){
1315 strncpy(pwd, app_pwd, MAILTMPLEN);
1316 pwd[MAILTMPLEN-1] = '\0';
1318 if (pwd[0]) { /* send login command if have password */
1319 LOCAL->sensitive = T; /* hide this command */
1320 /* send "LOGIN usr pwd" */
1321 if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args)))
1322 ret = LONGT; /* success */
1323 else {
1324 delete_password(mb, usr);
1325 mm_log (reply->text,WARN);
1326 if (!LOCAL->referral && (trial == imap_maxlogintrials))
1327 mm_log ("Too many login failures",ERROR);
1329 LOCAL->sensitive = NIL; /* unhide */
1331 /* user refused to give password */
1332 else mm_log ("Login aborted",ERROR);
1333 } while (!ret && pwd[0] && (trial < imap_maxlogintrials) &&
1334 LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral);
1336 if(app_pwd) fs_give((void **) &app_pwd);
1337 memset((void *) pwd, 0, MAILTMPLEN);
1338 return ret;
1341 /* Get challenge to authenticator in binary
1342 * Accepts: stream
1343 * pointer to returned size
1344 * Returns: challenge or NIL if not challenge
1347 void *imap_challenge (void *s,unsigned long *len)
1349 char tmp[MAILTMPLEN];
1350 void *ret = NIL;
1351 MAILSTREAM *stream = (MAILSTREAM *) s;
1352 IMAPPARSEDREPLY *reply = NIL;
1353 /* get tagged response or challenge */
1354 while (stream && LOCAL->netstream &&
1355 (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) &&
1356 !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
1357 /* parse challenge if have one */
1358 if (stream && LOCAL->netstream && reply && reply->tag &&
1359 (*reply->tag == '+') && !reply->tag[1] && reply->text &&
1360 !(ret = rfc822_base64 ((unsigned char *) reply->text,
1361 strlen (reply->text),len))) {
1362 sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s",
1363 (char *) reply->text);
1364 mm_log (tmp,ERROR);
1366 return ret;
1370 /* Send authenticator response in BASE64
1371 * Accepts: MAIL stream
1372 * string to send
1373 * length of string
1374 * Returns: T if successful, else NIL
1377 long imap_response (void *s,char *base,char *response,unsigned long size)
1379 MAILSTREAM *stream = (MAILSTREAM *) s;
1380 unsigned long i,j,ret;
1381 char *t,*u;
1382 if (response) { /* make CRLFless BASE64 string */
1383 if (size) {
1384 if(base){
1385 char *s, *v;
1387 v = (char *) rfc822_binary ((void *) response,size,&i);
1388 t = fs_get((strlen(base) + strlen(v) + 1 + 2)*sizeof(char));
1389 for(s = base, u = t; *s; s++) *u++ = *s;
1390 *u++ = ' ';
1391 for (s = v,j = 0; j < i; j++) if (s[j] > ' ') *u++ = s[j];
1392 fs_give((void **) &v);
1393 } else {
1394 for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
1395 j < i; j++) if (t[j] > ' ') *u++ = t[j];
1397 *u = '\0'; /* tie off string for mm_dlog() */
1398 if (stream->debug) mail_dlog (t,LOCAL->sensitive);
1400 /* append CRLF */
1401 *u++ = '\015'; *u++ = '\012';
1402 ret = net_sout (LOCAL->netstream,t,u - t);
1403 fs_give ((void **) &t);
1405 else ret = imap_soutr (stream,"");
1407 else { /* abort requested */
1408 ret = base ? NIL : imap_soutr (stream,"*");
1409 LOCAL->saslcancel = T; /* mark protocol-requested SASL cancel */
1411 return ret;
1414 /* IMAP close
1415 * Accepts: MAIL stream
1416 * option flags
1419 void imap_close (MAILSTREAM *stream,long options)
1421 THREADER *thr,*t;
1422 IMAPPARSEDREPLY *reply;
1423 if (stream && LOCAL) { /* send "LOGOUT" */
1424 if (!LOCAL->byeseen) { /* don't even think of doing it if saw a BYE */
1425 /* expunge silently if requested */
1426 if (options & CL_EXPUNGE)
1427 imap_send (stream,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL);
1428 if (LOCAL->netstream &&
1429 !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
1430 mm_log (reply->text,WARN);
1432 /* close NET connection if still open */
1433 if (LOCAL->netstream) net_close (LOCAL->netstream);
1434 LOCAL->netstream = NIL;
1435 /* free up memory */
1436 if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
1437 if (LOCAL->namespace) {
1438 mail_free_namespace (&LOCAL->namespace[0]);
1439 mail_free_namespace (&LOCAL->namespace[1]);
1440 mail_free_namespace (&LOCAL->namespace[2]);
1441 fs_give ((void **) &LOCAL->namespace);
1443 if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
1444 /* flush threaders */
1445 if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
1446 fs_give ((void **) &t->name);
1447 thr = t->next;
1448 fs_give ((void **) &t);
1450 if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
1451 if (LOCAL->user) fs_give ((void **) &LOCAL->user);
1452 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
1453 if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
1454 if (LOCAL->id) mail_free_idlist(&LOCAL->id);
1455 /* nuke the local data */
1456 fs_give ((void **) &stream->local);
1460 /* IMAP fetch fast information
1461 * Accepts: MAIL stream
1462 * sequence
1463 * option flags
1465 * Generally, imap_structure is preferred
1468 void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
1470 IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID);
1471 if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
1475 /* IMAP fetch flags
1476 * Accepts: MAIL stream
1477 * sequence
1478 * option flags
1481 void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
1482 { /* send "FETCH sequence FLAGS" */
1483 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1484 IMAPPARSEDREPLY *reply;
1485 IMAPARG *args[3],aseq,aatt;
1486 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
1487 flags & FT_UID);
1488 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
1489 aatt.type = ATOM; aatt.text = (void *) "FLAGS";
1490 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1491 if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
1492 mm_log (reply->text,ERROR);
1495 /* IMAP fetch overview
1496 * Accepts: MAIL stream, sequence bits set
1497 * pointer to overview return function
1498 * Returns: T if successful, NIL otherwise
1501 long imap_overview (MAILSTREAM *stream,overview_t ofn)
1503 MESSAGECACHE *elt;
1504 ENVELOPE *env;
1505 OVERVIEW ov;
1506 char *s,*t;
1507 unsigned long i,start,last,len,slen;
1508 if (!LOCAL->netstream) return NIL;
1509 /* build overview sequence */
1510 for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
1511 if ((elt = mail_elt (stream,i))->sequence) {
1512 if (!elt->private.msg.env) {
1513 if (s) { /* continuing a sequence */
1514 if (i == last + 1) last = i;
1515 else { /* end of range */
1516 if (last != start) sprintf (t,":%lu,%lu",last,i);
1517 else sprintf (t,",%lu",i);
1518 if ((len - (slen = (t += strlen (t)) - s)) < 20) {
1519 fs_resize ((void **) &s,len += MAILTMPLEN);
1520 t = s + slen; /* relocate current pointer */
1522 start = last = i; /* begin a new range */
1525 else { /* first time, start new buffer */
1526 s = (char *) fs_get (len = MAILTMPLEN);
1527 sprintf (s,"%lu",start = last = i);
1528 t = s + strlen (s); /* end of buffer */
1532 /* last sequence */
1533 if (last != start) sprintf (t,":%lu",last);
1534 if (s) { /* prefetch as needed */
1535 imap_fetch (stream,s,FT_NEEDENV);
1536 fs_give ((void **) &s);
1538 ov.optional.lines = 0; /* now overview each message */
1539 ov.optional.xref = NIL;
1540 if (ofn) for (i = 1; i <= stream->nmsgs; i++)
1541 if (((elt = mail_elt (stream,i))->sequence) &&
1542 (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
1543 ov.subject = env->subject;
1544 ov.from = env->from;
1545 ov.date = env->date;
1546 ov.message_id = env->message_id;
1547 ov.references = env->references;
1548 ov.optional.octets = elt->rfc822_size;
1549 (*ofn) (stream,mail_uid (stream,i),&ov,i);
1551 return LONGT;
1554 /* IMAP fetch structure
1555 * Accepts: MAIL stream
1556 * message # to fetch
1557 * pointer to return body
1558 * option flags
1559 * Returns: envelope of this message, body returned in body value
1561 * Fetches the "fast" information as well
1564 ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
1565 long flags)
1567 unsigned long i,j,k,x;
1568 char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN];
1569 MESSAGECACHE *elt;
1570 ENVELOPE **env;
1571 BODY **b;
1572 IMAPPARSEDREPLY *reply = NIL;
1573 IMAPARG *args[3],aseq,aatt;
1574 SEARCHSET *set = LOCAL->lookahead;
1575 LOCAL->lookahead = NIL;
1576 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
1577 aseq.type = SEQUENCE; aseq.text = (void *) seq;
1578 aatt.type = ATOM; aatt.text = NIL;
1579 if (flags & FT_UID) /* see if can find msgno from UID */
1580 for (i = 1; i <= stream->nmsgs; i++)
1581 if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1582 msgno = i; /* found msgno, use it from now on */
1583 flags &= ~FT_UID; /* no longer a UID fetch */
1585 sprintf (s = seq,"%lu",msgno);/* initial sequence */
1586 if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
1587 /* UID fetching is requested and we can't map the UID to a message sequence
1588 * number. Assume that the message isn't cached at all.
1590 if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV +
1591 (body ? FT_NEEDBODY : NIL) +
1592 (flags & (FT_UID + FT_NOHDRS)))))
1593 mm_log (reply->text,ERROR);
1594 /* now hunt for this UID */
1595 for (i = 1; i <= stream->nmsgs; i++)
1596 if ((elt = mail_elt (stream,i))->private.uid == msgno) {
1597 if (body) *body = elt->private.msg.body;
1598 return elt->private.msg.env;
1600 if (body) *body = NIL; /* can't find the UID */
1601 return NIL;
1603 elt = mail_elt (stream,msgno);/* get cache pointer */
1604 if (stream->scache) { /* short caching? */
1605 env = &stream->env; /* use temporaries on the stream */
1606 b = &stream->body;
1607 if (msgno != stream->msgno){/* flush old poop if a different message */
1608 mail_free_envelope (env);
1609 mail_free_body (b);
1610 stream->msgno = msgno; /* this is now the current short cache msg */
1614 else { /* normal cache */
1615 env = &elt->private.msg.env;/* get envelope and body pointers */
1616 b = &elt->private.msg.body;
1617 /* prefetch if don't have envelope */
1618 if (!(flags & FT_NOLOOKAHEAD) &&
1619 ((!*env || (*env)->incomplete) ||
1620 (body && !*b && LEVELIMAP2bis (stream)))) {
1621 if (set) { /* have a lookahead list? */
1622 MESSAGE *msg;
1623 for (k = imap_fetchlookaheadlimit;
1624 k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30));
1625 set = set->next) {
1626 i = (set->first == 0xffffffff) ? stream->nmsgs :
1627 min (set->first,stream->nmsgs);
1628 if ((j = (set->last == 0xffffffff) ? stream->nmsgs :
1629 min (set->last,stream->nmsgs)) != 0L) {
1630 if (i > j) { /* swap the range if backwards */
1631 x = i; i = j; j = x;
1633 /* find first message not msgno or in cache */
1634 while (((i == msgno) ||
1635 ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1636 (!body || msg->body))) && (i++ < j));
1637 /* until range or lookahead finished */
1638 while (k && (i <= j)) {
1639 /* find first cached message in range */
1640 for (x = i + 1; (x <= j) &&
1641 !((msg = &(mail_elt (stream,x)->private.msg))->env &&
1642 (!body || msg->body)); x++);
1643 if (i == --x) { /* only one message? */
1644 sprintf (s += strlen (s),",%lu",i++);
1645 k--; /* prefetching one message */
1647 else { /* a range to prefetch */
1648 sprintf (s += strlen (s),",%lu:%lu",i,x);
1649 i = 1 + x - i; /* number of messages in this range */
1650 /* still can look ahead some more? */
1651 if ((k = (k > i) ? k - i : 0) != 0)
1652 /* yes, scan further in this range */
1653 for (i = x + 2; (i <= j) &&
1654 ((i == msgno) ||
1655 ((msg = &(mail_elt (stream,i)->private.msg))->env &&
1656 (!body || msg->body)));
1657 i++);
1661 else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) {
1662 sprintf (s += strlen (s),",%lu",i);
1663 k--; /* prefetching one message */
1667 /* build message number list */
1668 else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++)
1669 if (!mail_elt (stream,i)->private.msg.env) {
1670 s += strlen (s); /* find string end, see if nearing end */
1671 if ((s - seq) > (MAILTMPLEN - 20)) break;
1672 sprintf (s,",%lu",i); /* append message */
1673 for (j = i + 1, k--; /* hunt for last message without an envelope */
1674 k && (j <= stream->nmsgs) &&
1675 !mail_elt (stream,j)->private.msg.env; j++, k--);
1676 /* if different, make a range */
1677 if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
1682 if (!stream->lock) { /* no-op if stream locked */
1683 /* Build the fetch attributes. Unlike imap_fetch(), this tries not to
1684 * fetch data that is already cached. However, since it is based on the
1685 * message requested and not on any of the prefetched messages, it can
1686 * goof, either by fetching data already cached or not prefetching data
1687 * that isn't cached (but was cached in the message requested).
1688 * Fortunately, no great harm is done. If it doesn't prefetch the data,
1689 * it will get it when the affected message(s) are requested.
1691 if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID");
1692 else tmp[0] = '\0'; /* initialize command */
1693 /* need envelope? */
1694 if (!*env || (*env)->incomplete) {
1695 strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */
1696 if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
1697 if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
1698 hdrheader[LOCAL->cap.extlevel],
1699 imap_extrahdrs,hdrtrailer);
1700 else sprintf (tmp + strlen (tmp)," %s %s",
1701 hdrheader[LOCAL->cap.extlevel],hdrtrailer);
1704 /* need body? */
1705 if (body && !*b && LEVELIMAP2bis (stream))
1706 strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY");
1707 if (!elt->day) strcat (tmp," INTERNALDATE");
1708 if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
1709 if (tmp[0]) { /* anything to do? */
1710 tmp[0] = '('; /* make into a list */
1711 strcat (tmp," FLAGS)"); /* always get current flags */
1712 aatt.text = (void *) tmp; /* do the built command */
1713 if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
1714 /* failed, probably RFC-1176 server */
1715 if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
1716 aatt.text = (void *) "ALL";
1717 if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
1718 /* doesn't have body capabilities */
1719 LOCAL->cap.imap2bis = NIL;
1720 else mm_log (reply->text,ERROR);
1722 else mm_log (reply->text,ERROR);
1726 if (body) { /* wants to return body */
1727 if (!*b && !LEVELIMAP2bis (stream)) {
1728 /* simulate body structure fetch for IMAP2 */
1729 *b = mail_initbody (mail_newbody ());
1730 (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type));
1731 ((*b)->parameter = mail_newbody_parameter ())->attribute =
1732 cpystr ("CHARSET");
1733 (*b)->parameter->value = cpystr ("US-ASCII");
1734 s = mail_fetch_text (stream,msgno,NIL,&i,flags);
1735 (*b)->size.bytes = i;
1736 while (i--) if (*s++ == '\n') (*b)->size.lines++;
1738 *body = *b; /* return the body */
1740 return *env; /* return the envelope */
1743 /* IMAP fetch message data
1744 * Accepts: MAIL stream
1745 * message number
1746 * section specifier
1747 * offset of first designated byte or 0 to start at beginning
1748 * maximum number of bytes or 0 for all bytes
1749 * lines to fetch if header
1750 * flags
1751 * Returns: T on success, NIL on failure
1754 long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
1755 unsigned long first,unsigned long last,STRINGLIST *lines,
1756 long flags)
1758 int i;
1759 char *t,tmp[MAILTMPLEN],partial[40],seq[40];
1760 char *noextend,*nopartial,*nolines,*nopeek,*nononpeek;
1761 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
1762 IMAPPARSEDREPLY *reply;
1763 IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg;
1764 noextend = nopartial = nolines = nopeek = nononpeek = NIL;
1765 /* does searching desire a lookahead? */
1766 if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) &&
1767 !stream->scache) {
1768 sprintf (seq,"%lu:%lu",msgno,
1769 (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs));
1770 aseq.type = SEQUENCE;
1771 aseq.text = (void *) seq;
1773 else { /* no, do it the easy way */
1774 aseq.type = NUMBER;
1775 aseq.text = (void *) msgno;
1777 aatt.type = ATOM; /* assume atomic attribute */
1778 alns.type = LIST; alns.text = (void *) lines;
1779 acls.type = BODYCLOSE; acls.text = (void *) partial;
1780 aflg.type = ATOM; aflg.text = (void *) "FLAGS";
1781 args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
1782 auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL;
1783 partial[0] = '\0'; /* initially no partial specifier */
1784 if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */
1785 /* HEADER fetching with special handling? */
1786 if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) {
1787 if (lines) { /* want specific header lines? */
1788 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1789 aatt.text = (void *) ((flags & FT_NOT) ?
1790 "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1791 args[2] = &alns; args[3] = &acls;
1793 /* must be prefetching */
1794 else aatt.text = (void *) ((flags & FT_PEEK) ?
1795 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1796 "(BODY[HEADER] BODY[TEXT])");
1798 else { /* simple case */
1799 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1800 aatt.text = (void *) section;
1801 args[2] = &acls;
1803 if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1);
1806 /* IMAP4 did not have:
1807 * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1808 * . TEXT body part (can simulate top-level with RFC822.TEXT or
1809 * RFC822.TEXT.PEEK)
1810 * . MIME body part
1811 * . (usable) partial fetching
1812 * . (usable) selective header line fetching
1814 else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */
1815 /* BODY[HEADER] becomes BODY.PEEK[0] */
1816 if (!strcmp (section,"HEADER"))
1817 aatt.text = (void *)
1818 ((flags & FT_PREFETCHTEXT) ?
1819 ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1820 "(BODY[0] RFC822.TEXT)") :
1821 ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]"));
1822 /* BODY[TEXT] becomes RFC822.TEXT */
1823 else if (!strcmp (section,"TEXT"))
1824 aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" :
1825 "RFC822.TEXT");
1826 else if (!section[0]) /* BODY[] becomes RFC822 */
1827 aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
1828 /* nested header */
1829 else if ((t = strstr (section,".HEADER")) != NULL) {
1830 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1831 args[2] = &acls; /* will need to close section */
1832 aatt.text = (void *) tmp; /* convert .HEADER to .0 */
1833 strncpy (tmp,section,t-section);
1834 strcpy (tmp+(t-section),".0");
1836 else { /* IMAP4 body part */
1837 aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
1838 args[2] = &acls; /* will need to close section */
1839 aatt.text = (void *) section;
1841 if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4";
1842 if (first || last) nopartial = "4";
1843 if (lines) nolines = "4";
1846 /* IMAP2bis did not have:
1847 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1848 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1849 * . MIME body part
1850 * . partial fetching
1851 * . selective header line fetching
1852 * . non-peeking header fetching
1853 * . peeking body fetching
1855 /* IMAP2bis compatibility */
1856 else if (LEVELIMAP2bis (stream)) {
1857 /* BODY[HEADER] becomes RFC822.HEADER */
1858 if (!strcmp (section,"HEADER")) {
1859 aatt.text = (void *)
1860 ((flags & FT_PREFETCHTEXT) ?
1861 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1862 if (flags & FT_PEEK) flags &= ~FT_PEEK;
1863 else nononpeek = "2bis";
1865 /* BODY[TEXT] becomes RFC822.TEXT */
1866 else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1867 /* BODY[] becomes RFC822 */
1868 else if (!section[0]) aatt.text = (void *) "RFC822";
1869 else { /* IMAP2bis body part */
1870 aatt.type = BODYTEXT;
1871 args[2] = &acls; /* will need to close section */
1872 aatt.text = (void *) section;
1874 if (strstr (section,".HEADER") || strstr (section,".MIME") ||
1875 strstr (section,".TEXT")) noextend = "2bis";
1876 if (first || last) nopartial = "2bis";
1877 if (lines) nolines = "2bis";
1878 if (flags & FT_PEEK) nopeek = "2bis";
1881 /* IMAP2 did not have:
1882 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1883 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1884 * . MIME body part
1885 * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1886 * . partial fetching
1887 * . selective header line fetching
1888 * . non-peeking header fetching
1889 * . peeking body fetching
1891 else { /* IMAP2 (RFC 1176/1064) compatibility */
1892 /* BODY[HEADER] */
1893 if (!strcmp (section,"HEADER")) {
1894 aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
1895 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1896 if (flags & FT_PEEK) flags &= ~FT_PEEK;
1897 nononpeek = "2";
1899 /* BODY[TEXT] becomes RFC822.TEXT */
1900 else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
1901 /* BODY[1] treated like RFC822.TEXT */
1902 else if (!strcmp (section,"1")) {
1903 SIZEDTEXT text;
1904 MESSAGECACHE *elt = mail_elt (stream,msgno);
1905 /* have a cached RFC822.TEXT? */
1906 if (elt->private.msg.text.text.data) {
1907 text.size = elt->private.msg.text.text.size;
1908 /* should move instead of copy */
1909 text.data = memcpy (fs_get (text.size+1),
1910 elt->private.msg.text.text.data,text.size);
1911 (t = (char *) text.data)[text.size] = '\0';
1912 imap_cache (stream,msgno,"1",NIL,&text);
1913 return LONGT; /* don't have to do any fetches */
1915 /* otherwise do RFC822.TEXT */
1916 aatt.text = (void *) "RFC822.TEXT";
1918 /* BODY[] becomes RFC822 */
1919 else if (!section[0]) aatt.text = (void *) "RFC822";
1920 else noextend = "2"; /* how did we get here? */
1921 if (flags & FT_PEEK) nopeek = "2";
1922 if (first || last) nopartial = "2";
1923 if (lines) nolines = "2";
1926 /* Report unavailable functionalities. The application can use the helpful
1927 * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1928 * imap4r1.h to avoid triggering these errors. There aren't any workarounds
1929 * for these restrictions.
1931 if (noextend) {
1932 sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1933 noextend);
1934 mm_log (tmp,ERROR);
1935 return NIL; /* can't do anything close either */
1937 if (nopartial) {
1938 sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1939 nopartial);
1940 mm_notify (stream,tmp,WARN);
1942 if (nolines) {
1943 sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1944 nolines);
1945 mm_notify (stream,tmp,WARN);
1948 /* trying to do unsupported peek behavior? */
1949 if ((t = nopeek) || (t = nononpeek)) {
1950 /* get most recent \Seen setting */
1951 if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs)))
1952 mm_log (reply->text,WARN);
1953 /* note current setting of \Seen flag */
1954 if (!(i = mail_elt (stream,msgno)->seen)) {
1955 sprintf (tmp,nopeek ? /* only babble if \Seen not set */
1956 "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1957 "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t);
1958 mm_notify (stream,tmp,NIL);
1960 /* send the fetch command */
1961 if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1962 mm_log (reply->text,ERROR);
1963 return NIL; /* failure */
1965 /* send command if need to reset \Seen */
1966 if (((nopeek && !i && mail_elt (stream,msgno)->seen &&
1967 (aflg.text = "-FLAGS \\Seen")) ||
1968 ((nononpeek && !mail_elt (stream,msgno)->seen) &&
1969 (aflg.text = "+FLAGS \\Seen"))) &&
1970 !imap_OK (stream,reply = imap_send (stream,"STORE",auxargs)))
1971 mm_log (reply->text,WARN);
1973 /* simple case if traditional behavior */
1974 else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
1975 mm_log (reply->text,ERROR);
1976 return NIL; /* failure */
1978 /* simulate BODY[1] return for RFC 1064/1176 */
1979 if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) {
1980 SIZEDTEXT text;
1981 MESSAGECACHE *elt = mail_elt (stream,msgno);
1982 text.size = elt->private.msg.text.text.size;
1983 /* should move instead of copy */
1984 text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data,
1985 text.size);
1986 (t = (char *) text.data)[text.size] = '\0';
1987 imap_cache (stream,msgno,"1",NIL,&text);
1989 return LONGT;
1992 /* IMAP fetch UID
1993 * Accepts: MAIL stream
1994 * message number
1995 * Returns: UID
1998 unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
2000 MESSAGECACHE *elt;
2001 IMAPPARSEDREPLY *reply;
2002 IMAPARG *args[3],aseq,aatt;
2003 char *s,seq[MAILTMPLEN];
2004 unsigned long i,j,k;
2005 /* IMAP2 didn't have UIDs */
2006 if (!LEVELIMAP4 (stream)) return msgno;
2007 /* do we know its UID yet? */
2008 if (!(elt = mail_elt (stream,msgno))->private.uid) {
2009 aseq.type = SEQUENCE; aseq.text = (void *) seq;
2010 aatt.type = ATOM; aatt.text = (void *) "UID";
2011 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
2012 sprintf (seq,"%lu",msgno);
2013 if ((k = imap_uidlookahead) != 0L) {/* build UID list */
2014 for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
2015 if (!mail_elt (stream,i)->private.uid) {
2016 s += strlen (s); /* find string end, see if nearing end */
2017 if ((s - seq) > (MAILTMPLEN - 20)) break;
2018 sprintf (s,",%lu",i); /* append message */
2019 for (j = i + 1, k--; /* hunt for last message without a UID */
2020 k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
2021 j++, k--);
2022 /* if different, make a range */
2023 if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
2026 /* send "FETCH msgno UID" */
2027 if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
2028 mm_log (reply->text,ERROR);
2030 return elt->private.uid; /* return our UID now */
2033 /* IMAP fetch message number from UID
2034 * Accepts: MAIL stream
2035 * UID
2036 * Returns: message number
2039 unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
2041 IMAPPARSEDREPLY *reply;
2042 IMAPARG *args[3],aseq,aatt;
2043 char seq[MAILTMPLEN];
2044 int holes = 0;
2045 unsigned long i,msgno;
2046 /* IMAP2 didn't have UIDs */
2047 if (!LEVELIMAP4 (stream)) return uid;
2048 /* This really should be a binary search, but since there are likely to be
2049 * holes in the msgno->UID map it's hard to do.
2051 for (msgno = 1; msgno <= stream->nmsgs; msgno++) {
2052 if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T;
2053 else if (i == uid) return msgno;
2055 if (holes) { /* have holes in cache? */
2056 /* yes, have server hunt for UID */
2057 LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0;
2058 aseq.type = SEQUENCE; aseq.text = (void *) seq;
2059 aatt.type = ATOM; aatt.text = (void *) "UID";
2060 args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
2061 sprintf (seq,"%lu",uid);
2062 /* send "UID FETCH uid UID" */
2063 if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
2064 mm_log (reply->text,ERROR);
2065 if (LOCAL->lastuid.uid) { /* got any results from FETCH? */
2066 if ((LOCAL->lastuid.uid == uid) &&
2067 /* what, me paranoid? */
2068 (LOCAL->lastuid.msgno <= stream->nmsgs) &&
2069 (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid))
2070 /* got it the easy way */
2071 return LOCAL->lastuid.msgno;
2072 /* sigh, do another linear search... */
2073 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2074 if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
2077 return 0; /* didn't find the UID anywhere */
2080 /* IMAP modify flags
2081 * Accepts: MAIL stream
2082 * sequence
2083 * flag(s)
2084 * option flags
2087 void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
2089 char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
2090 IMAPPARSEDREPLY *reply;
2091 IMAPARG *args[4],aseq,ascm,aflg;
2092 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2093 flags & ST_UID);
2094 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2095 ascm.type = ATOM; ascm.text = (void *)
2096 ((flags & ST_SET) ?
2097 ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
2098 "+Flags.silent" : "+Flags") :
2099 ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
2100 "-Flags.silent" : "-Flags"));
2101 aflg.type = FLAGS; aflg.text = (void *) flag;
2102 args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
2103 /* send "STORE sequence +Flags flag" */
2104 if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
2105 mm_log (reply->text,ERROR);
2108 /* IMAP search for messages
2109 * Accepts: MAIL stream
2110 * character set
2111 * search program
2112 * option flags
2113 * Returns: T on success, NIL on failure
2116 long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
2118 unsigned long i,j,k;
2119 char *s;
2120 IMAPPARSEDREPLY *reply;
2121 MESSAGECACHE *elt;
2123 if(LOCAL->cap.x_gm_ext1 && pgm && pgm->x_gm_ext1)
2124 return imap_search_x_gm_ext1(stream, charset, pgm, flags);
2126 if ((flags & SE_NOSERVER) || /* if want to do local search */
2127 LOCAL->loser || /* or loser */
2128 (!LEVELIMAP4 (stream) && /* or old server but new functions... */
2129 (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or ||
2130 pgm->not || pgm->header || pgm->larger || pgm->smaller ||
2131 pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
2132 pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
2133 pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
2134 pgm->followup_to || pgm->references)) ||
2135 (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) {
2136 if ((flags & SE_NOLOCAL) ||
2137 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2138 return NIL;
2140 /* do silly ALL or seq-only search locally */
2141 else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) &&
2142 !(pgm->uid || pgm->or || pgm->not ||
2143 pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc ||
2144 pgm->subject || pgm->body || pgm->text ||
2145 pgm->larger || pgm->smaller ||
2146 pgm->sentbefore || pgm->senton || pgm->sentsince ||
2147 pgm->before || pgm->on || pgm->since ||
2148 pgm->answered || pgm->unanswered ||
2149 pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft ||
2150 pgm->flagged || pgm->unflagged || pgm->recent || pgm->old ||
2151 pgm->seen || pgm->unseen ||
2152 pgm->keyword || pgm->unkeyword ||
2153 pgm->return_path || pgm->sender ||
2154 pgm->reply_to || pgm->in_reply_to || pgm->message_id ||
2155 pgm->newsgroups || pgm->followup_to || pgm->references)) {
2156 if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER))
2157 fatal ("impossible mail_search_default() failure");
2160 else { /* do server-based SEARCH */
2161 char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH";
2162 IMAPARG *args[4],apgm,aatt,achs;
2163 SEARCHSET *ss,*set;
2164 args[1] = args[2] = args[3] = NIL;
2165 apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
2166 if (charset) { /* optional charset argument requested */
2167 args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
2168 aatt.type = ATOM; aatt.text = (void *) "CHARSET";
2169 achs.type = ASTRING; achs.text = (void *) charset;
2171 else args[0] = &apgm; /* no charset argument */
2172 /* tell receiver that these will be UIDs */
2173 LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
2174 reply = imap_send (stream,cmd,args);
2175 /* did server barf with that searchpgm? */
2176 if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) &&
2177 !strcmp (reply->key,"BAD")) {
2178 LOCAL->filter = T; /* retry, filtering SEARCH results */
2179 for (i = 1; i <= stream->nmsgs; i++)
2180 mail_elt (stream,i)->private.filter = NIL;
2181 for (set = ss; set; set = set->next) if ((i = set->first) != 0L) {
2182 /* single message becomes one-message range */
2183 if (!(j = set->last)) j = i;
2184 else if (j < i) { /* swap reversed range */
2185 i = set->last; j = set->first;
2187 while (i <= j) mail_elt (stream,i++)->private.filter = T;
2189 pgm->msgno = NIL; /* and without the searchset */
2190 reply = imap_send (stream,cmd,args);
2191 pgm->msgno = ss; /* restore searchset */
2192 LOCAL->filter = NIL; /* turn off filtering */
2194 LOCAL->uidsearch = NIL;
2195 /* do locally if server won't grok */
2196 if (!strcmp (reply->key,"BAD")) {
2197 if ((flags & SE_NOLOCAL) ||
2198 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
2199 return NIL;
2201 else if (!imap_OK (stream,reply)) {
2202 mm_log (reply->text,ERROR);
2203 return NIL;
2207 /* can never pre-fetch with a short cache */
2208 if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
2209 !stream->scache) { /* only if prefetching permitted */
2210 s = LOCAL->tmp; /* build sequence in temporary buffer */
2211 *s = '\0'; /* initially nothing */
2212 /* search through mailbox */
2213 for (i = 1; k && (i <= stream->nmsgs); ++i)
2214 /* for searched messages with no envelope */
2215 if ((elt = mail_elt (stream,i)) && elt->searched &&
2216 !mail_elt (stream,i)->private.msg.env) {
2217 /* prepend with comma if not first time */
2218 if (LOCAL->tmp[0]) *s++ = ',';
2219 sprintf (s,"%lu",j = i);/* output message number */
2220 s += strlen (s); /* point at end of string */
2221 k--; /* count one up */
2222 /* search for possible end of range */
2223 while (k && (i < stream->nmsgs) &&
2224 (elt = mail_elt (stream,i+1))->searched &&
2225 !elt->private.msg.env) i++,k--;
2226 if (i != j) { /* if a range */
2227 sprintf (s,":%lu",i); /* output delimiter and end of range */
2228 s += strlen (s); /* point at end of string */
2230 if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
2232 if (LOCAL->tmp[0]) { /* anything to pre-fetch? */
2233 /* pre-fetch envelopes for the first imap_prefetch number of messages */
2234 if (!imap_OK (stream,reply =
2235 imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV +
2236 ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
2237 ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
2238 mm_log (reply->text,ERROR);
2239 fs_give ((void **) &s); /* flush copy of sequence */
2242 return LONGT;
2245 /* IMAP sort messages
2246 * Accepts: mail stream
2247 * character set
2248 * search program
2249 * sort program
2250 * option flags
2251 * Returns: vector of sorted message sequences or NIL if error
2254 unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
2255 SORTPGM *pgm,long flags)
2257 unsigned long i,j,start,last;
2258 unsigned long *ret = NIL;
2259 pgm->nmsgs = 0; /* start off with no messages */
2260 /* can use server-based sort? */
2261 if (LEVELSORT (stream) && !(flags & SE_NOSERVER) &&
2262 (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) {
2263 char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
2264 IMAPARG *args[4],apgm,achs,aspg;
2265 IMAPPARSEDREPLY *reply;
2266 SEARCHSET *ss = NIL;
2267 SEARCHPGM *tsp = NIL;
2268 apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
2269 achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
2270 aspg.type = SEARCHPROGRAM;
2271 /* did he provide a searchpgm? */
2272 if (!(aspg.text = (void *) spg)) {
2273 for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2274 if (mail_elt (stream,i)->searched) {
2275 if (ss) { /* continuing a sequence */
2276 if (i == last + 1) last = i;
2277 else { /* end of range */
2278 if (last != start) ss->last = last;
2279 (ss = ss->next = mail_newsearchset ())->first = i;
2280 start = last = i; /* begin a new range */
2283 else { /* first time, start new searchpgm */
2284 (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2285 ss->first = start = last = i;
2288 /* nothing to sort if no messages */
2289 if (!(aspg.text = (void *) tsp)) return NIL;
2290 /* else install last sequence */
2291 if (last != start) ss->last = last;
2294 args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2295 /* ask server to do it */
2296 reply = imap_send (stream,cmd,args);
2297 if (tsp) { /* was there a temporary searchpgm? */
2298 aspg.text = NIL; /* yes, flush it */
2299 mail_free_searchpgm (&tsp);
2300 /* did server barf with that searchpgm? */
2301 if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2302 LOCAL->filter = T; /* retry, filtering SORT/THREAD results */
2303 reply = imap_send (stream,cmd,args);
2304 LOCAL->filter = NIL; /* turn off filtering */
2307 /* do locally if server barfs */
2308 if (!strcmp (reply->key,"BAD"))
2309 return (flags & SE_NOLOCAL) ? NIL :
2310 imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER);
2311 /* server sorted OK? */
2312 else if (imap_OK (stream,reply)) {
2313 pgm->nmsgs = LOCAL->sortsize;
2314 ret = LOCAL->sortdata;
2315 LOCAL->sortdata = NIL; /* mail program is responsible for flushing */
2317 else mm_log (reply->text,ERROR);
2320 /* not much can do if short caching */
2321 else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
2322 else { /* try to be a bit more clever */
2323 char *s,*t;
2324 unsigned long len;
2325 MESSAGECACHE *elt;
2326 SORTCACHE **sc;
2327 SORTPGM *sp;
2328 long ftflags = 0;
2329 /* see if need envelopes */
2330 for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) {
2331 case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
2332 ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL);
2334 if (spg) { /* only if a search needs to be done */
2335 int silent = stream->silent;
2336 stream->silent = T; /* don't pass up mm_searched() events */
2337 /* search for messages */
2338 mail_search_full (stream,charset,spg,flags & SE_NOSERVER);
2339 stream->silent = silent; /* restore silence state */
2341 /* initialize progress counters */
2342 pgm->nmsgs = pgm->progress.cached = 0;
2343 /* pass 1: count messages to sort */
2344 for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
2345 if ((elt = mail_elt (stream,i))->searched) {
2346 pgm->nmsgs++;
2347 if (ftflags ? !elt->private.msg.env : !elt->day) {
2348 if (s) { /* continuing a sequence */
2349 if (i == last + 1) last = i;
2350 else { /* end of range */
2351 if (last != start) sprintf (t,":%lu,%lu",last,i);
2352 else sprintf (t,",%lu",i);
2353 start = last = i; /* begin a new range */
2354 if ((len - (j = ((t += strlen (t)) - s)) < 20)) {
2355 fs_resize ((void **) &s,len += MAILTMPLEN);
2356 t = s + j; /* relocate current pointer */
2360 else { /* first time, start new buffer */
2361 s = (char *) fs_get (len = MAILTMPLEN);
2362 sprintf (s,"%lu",start = last = i);
2363 t = s + strlen (s); /* end of buffer */
2367 /* last sequence */
2368 if (last != start) sprintf (t,":%lu",last);
2369 if (s) { /* load cache for all messages being sorted */
2370 imap_fetch (stream,s,ftflags);
2371 fs_give ((void **) &s);
2373 if (pgm->nmsgs) { /* pass 2: sort cache */
2374 sortresults_t sr = (sortresults_t)
2375 mail_parameters (NIL,GET_SORTRESULTS,NIL);
2376 sc = mail_sort_loadcache (stream,pgm);
2377 /* pass 3: sort messages */
2378 if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
2379 fs_give ((void **) &sc); /* don't need sort vector any more */
2380 /* also return via callback if requested */
2381 if (sr) (*sr) (stream,ret,pgm->nmsgs);
2384 return ret;
2387 /* IMAP thread messages
2388 * Accepts: mail stream
2389 * thread type
2390 * character set
2391 * search program
2392 * option flags
2393 * Returns: thread node tree or NIL if error
2396 THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
2397 SEARCHPGM *spg,long flags)
2399 THREADER *thr;
2400 if (!(flags & SE_NOSERVER) &&
2401 (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger))))
2402 /* does server have this threader type? */
2403 for (thr = LOCAL->cap.threader; thr; thr = thr->next)
2404 if (!compare_cstring (thr->name,type))
2405 return imap_thread_work (stream,type,charset,spg,flags);
2406 /* server doesn't support it, do locally */
2407 return (flags & SE_NOLOCAL) ? NIL:
2408 mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2411 /* IMAP thread messages worker routine
2412 * Accepts: mail stream
2413 * thread type
2414 * character set
2415 * search program
2416 * option flags
2417 * Returns: thread node tree
2420 THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
2421 SEARCHPGM *spg,long flags)
2423 unsigned long i,start,last;
2424 char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
2425 IMAPARG *args[4],apgm,achs,aspg;
2426 IMAPPARSEDREPLY *reply;
2427 THREADNODE *ret = NIL;
2428 SEARCHSET *ss = NIL;
2429 SEARCHPGM *tsp = NIL;
2430 apgm.type = ATOM; apgm.text = (void *) type;
2431 achs.type = ASTRING;
2432 achs.text = (void *) (charset ? charset : "US-ASCII");
2433 aspg.type = SEARCHPROGRAM;
2434 /* did he provide a searchpgm? */
2435 if (!(aspg.text = (void *) spg)) {
2436 for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
2437 if (mail_elt (stream,i)->searched) {
2438 if (ss) { /* continuing a sequence */
2439 if (i == last + 1) last = i;
2440 else { /* end of range */
2441 if (last != start) ss->last = last;
2442 (ss = ss->next = mail_newsearchset ())->first = i;
2443 start = last =i; /* begin a new range */
2446 else { /* first time, start new searchpgm */
2447 (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
2448 ss->first = start = last = i;
2451 /* nothing to sort if no messages */
2452 if (!(aspg.text = (void *) tsp)) return NIL;
2453 /* else install last sequence */
2454 if (last != start) ss->last = last;
2457 args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
2458 /* ask server to do it */
2459 reply = imap_send (stream,cmd,args);
2460 if (tsp) { /* was there a temporary searchpgm? */
2461 aspg.text = NIL; /* yes, flush it */
2462 mail_free_searchpgm (&tsp);
2463 /* did server barf with that searchpgm? */
2464 if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
2465 LOCAL->filter = T; /* retry, filtering SORT/THREAD results */
2466 reply = imap_send (stream,cmd,args);
2467 LOCAL->filter = NIL; /* turn off filtering */
2470 /* do locally if server barfs */
2471 if (!strcmp (reply->key,"BAD"))
2472 ret = (flags & SE_NOLOCAL) ? NIL:
2473 mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
2474 /* server threaded OK? */
2475 else if (imap_OK (stream,reply)) {
2476 ret = LOCAL->threaddata;
2477 LOCAL->threaddata = NIL; /* mail program is responsible for flushing */
2479 else mm_log (reply->text,ERROR);
2480 return ret;
2483 /* IMAP ping mailbox
2484 * Accepts: MAIL stream
2485 * Returns: T if stream still alive, else NIL
2488 long imap_ping (MAILSTREAM *stream)
2490 return (LOCAL->netstream && /* send "NOOP" */
2491 imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
2495 /* IMAP check mailbox
2496 * Accepts: MAIL stream
2499 void imap_check (MAILSTREAM *stream)
2501 /* send "CHECK" */
2502 IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
2503 mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
2506 /* IMAP expunge mailbox
2507 * Accepts: MAIL stream
2508 * sequence to expunge if non-NIL
2509 * expunge options
2510 * Returns: T if success, NIL if failure
2513 long imap_expunge (MAILSTREAM *stream,char *sequence,long options)
2515 long ret = NIL;
2516 IMAPPARSEDREPLY *reply = NIL;
2517 if (sequence) { /* wants selective expunging? */
2518 if (options & EX_UID) { /* UID EXPUNGE form? */
2519 if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */
2520 IMAPARG *args[2],aseq;
2521 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2522 args[0] = &aseq; args[1] = NIL;
2523 ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args));
2525 else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR);
2527 /* otherwise try to make into UID EXPUNGE */
2528 else if (mail_sequence (stream,sequence)) {
2529 unsigned long i,j;
2530 char *t = (char *) fs_get (IMAPTMPLEN);
2531 char *s = t;
2532 /* search through mailbox */
2533 for (*s = '\0', i = 1; i <= stream->nmsgs; ++i)
2534 if (mail_elt (stream,i)->sequence) {
2535 if (t[0]) *s++ = ','; /* prepend with comma if not first time */
2536 sprintf (s,"%lu",mail_uid (stream,j = i));
2537 s += strlen (s); /* point at end of string */
2538 /* search for possible end of range */
2539 while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++;
2540 if (i != j) { /* output end of range */
2541 sprintf (s,":%lu",mail_uid (stream,i));
2542 s += strlen (s); /* point at end of string */
2544 if ((s - t) > (IMAPTMPLEN - 50)) {
2545 mm_log ("Excessively complex sequence",ERROR);
2546 return NIL;
2549 /* now do as UID EXPUNGE */
2550 ret = imap_expunge (stream,t,EX_UID);
2551 fs_give ((void **) &t);
2554 /* ordinary EXPUNGE */
2555 else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL));
2556 if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR);
2557 return ret;
2560 /* IMAP copy message(s)
2561 * Accepts: MAIL stream
2562 * sequence
2563 * destination mailbox
2564 * option flags
2565 * Returns: T if successful else NIL
2568 long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
2570 char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
2571 char *s;
2572 long ret = NIL;
2573 IMAPPARSEDREPLY *reply;
2574 IMAPARG *args[3],aseq,ambx;
2575 imapreferral_t ir =
2576 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2577 mailproxycopy_t pc =
2578 (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
2579 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
2580 flags & CP_UID);
2581 aseq.type = SEQUENCE; aseq.text = (void *) sequence;
2582 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2583 args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
2584 /* note mailbox in case APPENDUID */
2585 LOCAL->appendmailbox = mailbox;
2586 /* send "COPY sequence mailbox" */
2587 ret = imap_OK (stream,reply = imap_send (stream,cmd,args));
2588 LOCAL->appendmailbox = NIL; /* no longer appending */
2589 if (ret) { /* success, delete messages if move */
2590 if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
2591 ST_SET + ((flags&CP_UID) ? ST_UID : NIL));
2593 /* failed, do referral action if any */
2594 else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
2595 (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
2596 ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL));
2597 /* otherwise issue error message */
2598 else mm_log (reply->text,ERROR);
2599 return ret;
2602 /* IMAP mail append message from stringstruct
2603 * Accepts: MAIL stream
2604 * destination mailbox
2605 * append callback
2606 * data for callback
2607 * Returns: T if append successful, else NIL
2610 long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
2612 MAILSTREAM *st = stream;
2613 IMAPARG *args[3],ambx,amap;
2614 IMAPPARSEDREPLY *reply = NIL;
2615 APPENDDATA map;
2616 char tmp[MAILTMPLEN];
2617 long debug = stream ? stream->debug : NIL;
2618 long ret = NIL;
2619 imapreferral_t ir =
2620 (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
2621 /* mailbox must be good */
2622 if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2623 /* create a stream if given one no good */
2624 if ((stream && LOCAL && LOCAL->netstream) ||
2625 (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2626 (debug ? OP_DEBUG : NIL)))) {
2627 /* note mailbox in case APPENDUID */
2628 LOCAL->appendmailbox = mailbox;
2629 /* use multi-append? */
2630 if (LEVELMULTIAPPEND (stream)) {
2631 ambx.type = ASTRING; ambx.text = (void *) tmp;
2632 amap.type = MULTIAPPEND; amap.text = (void *) &map;
2633 map.af = af; map.data = data;
2634 args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2635 /* success if OK */
2636 ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args));
2637 LOCAL->appendmailbox = NIL;
2639 /* do succession of single appends */
2640 else while ((*af) (stream,data,&map.flags,&map.date,&map.message) &&
2641 map.message &&
2642 (ret = imap_OK (stream,reply =
2643 imap_append_single (stream,tmp,map.flags,
2644 map.date,map.message))));
2645 LOCAL->appendmailbox = NIL;
2646 /* don't do referrals if success or no reply */
2647 if (ret || !reply) mailbox = NIL;
2648 /* otherwise generate referral */
2649 else if (!(mailbox = (ir && LOCAL->referral) ?
2650 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2651 mm_log (reply->text,ERROR);
2652 /* close temporary stream */
2653 if (st != stream) stream = mail_close (stream);
2654 if (mailbox) /* chase referral if any */
2655 ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date,
2656 map.message,&map,debug);
2658 else mm_log ("Can't access server for append",ERROR);
2660 return ret; /* return */
2663 /* IMAP mail append message referral retry
2664 * Accepts: destination mailbox
2665 * temporary buffer
2666 * append callback
2667 * data for callback
2668 * flags from previous attempt
2669 * date from previous attempt
2670 * message stringstruct from previous attempt
2671 * options (currently non-zero to set OP_DEBUG)
2672 * Returns: T if append successful, else NIL
2675 long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
2676 char *flags,char *date,STRING *message,
2677 APPENDDATA *map,long options)
2679 MAILSTREAM *stream;
2680 IMAPARG *args[3],ambx,amap;
2681 IMAPPARSEDREPLY *reply;
2682 imapreferral_t ir =
2683 (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL);
2684 /* barf if bad mailbox */
2685 while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
2686 /* create a stream if given one no good */
2687 if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
2688 (options ? OP_DEBUG : NIL)))) {
2689 sprintf (tmp,"Can't access referral server: %.80s",mailbox);
2690 mm_log (tmp,ERROR);
2691 return NIL;
2693 /* got referral server, use multi-append? */
2694 if (LEVELMULTIAPPEND (stream)) {
2695 ambx.type = ASTRING; ambx.text = (void *) tmp;
2696 amap.type = MULTIAPPENDREDO; amap.text = (void *) map;
2697 args[0] = &ambx; args[1] = &amap; args[2] = NIL;
2698 /* do multiappend on referral site */
2699 if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) {
2700 mail_close (stream); /* multiappend OK, close stream */
2701 return LONGT; /* all done */
2704 /* do multiple single appends */
2705 else while (imap_OK (stream,reply =
2706 imap_append_single (stream,tmp,flags,date,message)))
2707 if (!((*af) (stream,data,&flags,&date,&message) && message)) {
2708 mail_close (stream); /* last message, close stream */
2709 return LONGT; /* all done */
2711 /* generate error if no nested referral */
2712 if (!(mailbox = (ir && LOCAL->referral) ?
2713 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
2714 mm_log (reply->text,ERROR);
2715 mail_close (stream); /* close previous referral stream */
2717 return NIL; /* bogus mailbox */
2720 /* IMAP append single message
2721 * Accepts: mail stream
2722 * destination mailbox
2723 * initial flags
2724 * internal date
2725 * stringstruct of message to append
2726 * Returns: reply from append
2729 IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
2730 char *flags,char *date,STRING *message)
2732 MESSAGECACHE elt;
2733 IMAPARG *args[5],ambx,aflg,adat,amsg;
2734 IMAPPARSEDREPLY *reply;
2735 char tmp[MAILTMPLEN];
2736 int i;
2737 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2738 args[i = 0] = &ambx;
2739 if (flags) {
2740 aflg.type = FLAGS; aflg.text = (void *) flags;
2741 args[++i] = &aflg;
2743 if (date) { /* ensure date in INTERNALDATE format */
2744 if (!mail_parse_date (&elt,date)) {
2745 /* flush previous reply */
2746 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
2747 /* build new fake reply */
2748 LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*");
2749 LOCAL->reply.key = "BAD";
2750 LOCAL->reply.text = "Bad date in append";
2751 return &LOCAL->reply;
2753 adat.type = ASTRING;
2754 adat.text = (void *) (date = mail_date (tmp,&elt));
2755 args[++i] = &adat;
2757 amsg.type = LITERAL; amsg.text = (void *) message;
2758 args[++i] = &amsg;
2759 args[++i] = NIL;
2760 /* easy if IMAP4[rev1] */
2761 if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args);
2762 else { /* try the IMAP2bis way */
2763 args[1] = &amsg; args[2] = NIL;
2764 reply = imap_send (stream,"APPEND",args);
2766 return reply;
2769 /* IMAP garbage collect stream
2770 * Accepts: Mail stream
2771 * garbage collection flags
2774 void imap_gc (MAILSTREAM *stream,long gcflags)
2776 unsigned long i;
2777 MESSAGECACHE *elt;
2778 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2779 /* make sure the cache is large enough */
2780 (*mc) (stream,stream->nmsgs,CH_SIZE);
2781 if (gcflags & GC_TEXTS) { /* garbage collect texts? */
2782 if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
2783 if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) != NULL)
2784 imap_gc_body (elt->private.msg.body);
2785 imap_gc_body (stream->body);
2787 /* gc cache if requested and unlocked */
2788 if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
2789 if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
2790 (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
2793 /* IMAP garbage collect body texts
2794 * Accepts: body to GC
2797 void imap_gc_body (BODY *body)
2799 PART *part;
2800 if (body) { /* have a body? */
2801 if (body->mime.text.data) /* flush MIME data */
2802 fs_give ((void **) &body->mime.text.data);
2803 /* flush text contents */
2804 if (body->contents.text.data)
2805 fs_give ((void **) &body->contents.text.data);
2806 body->mime.text.size = body->contents.text.size = 0;
2807 /* multipart? */
2808 if (body->type == TYPEMULTIPART)
2809 for (part = body->nested.part; part; part = part->next)
2810 imap_gc_body (&part->body);
2811 /* MESSAGE/RFC822? */
2812 else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
2813 imap_gc_body (body->nested.msg->body);
2814 if (body->nested.msg->full.text.data)
2815 fs_give ((void **) &body->nested.msg->full.text.data);
2816 if (body->nested.msg->header.text.data)
2817 fs_give ((void **) &body->nested.msg->header.text.data);
2818 if (body->nested.msg->text.text.data)
2819 fs_give ((void **) &body->nested.msg->text.text.data);
2820 body->nested.msg->full.text.size = body->nested.msg->header.text.size =
2821 body->nested.msg->text.text.size = 0;
2826 /* IMAP get capabilities
2827 * Accepts: mail stream
2830 void imap_capability (MAILSTREAM *stream)
2832 THREADER *thr,*t;
2833 LOCAL->gotcapability = NIL; /* flush any previous capabilities */
2834 /* request new capabilities */
2835 imap_send (stream,"CAPABILITY",NIL);
2836 if (!LOCAL->gotcapability) { /* did server get any? */
2837 /* no, flush threaders just in case */
2838 if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
2839 fs_give ((void **) &t->name);
2840 thr = t->next;
2841 fs_give ((void **) &t);
2843 /* zap most capabilities */
2844 memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
2845 /* assume IMAP2bis server if failure */
2846 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
2850 /* IMAP set ACL
2851 * Accepts: mail stream
2852 * mailbox name
2853 * authentication identifier
2854 * new access rights
2855 * Returns: T on success, NIL on failure
2858 long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights)
2860 IMAPARG *args[4],ambx,aid,art;
2861 ambx.type = aid.type = art.type = ASTRING;
2862 ambx.text = (void *) mailbox; aid.text = (void *) id;
2863 art.text = (void *) rights;
2864 args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL;
2865 return imap_acl_work (stream,"SETACL",args);
2869 /* IMAP delete ACL
2870 * Accepts: mail stream
2871 * mailbox name
2872 * authentication identifier
2873 * Returns: T on success, NIL on failure
2876 long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id)
2878 IMAPARG *args[3],ambx,aid;
2879 ambx.type = aid.type = ASTRING;
2880 ambx.text = (void *) mailbox; aid.text = (void *) id;
2881 args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2882 return imap_acl_work (stream,"DELETEACL",args);
2886 /* IMAP get ACL
2887 * Accepts: mail stream
2888 * mailbox name
2889 * Returns: T on success with data returned via callback, NIL on failure
2892 long imap_getacl (MAILSTREAM *stream,char *mailbox)
2894 IMAPARG *args[2],ambx;
2895 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2896 args[0] = &ambx; args[1] = NIL;
2897 return imap_acl_work (stream,"GETACL",args);
2900 /* IMAP list rights
2901 * Accepts: mail stream
2902 * mailbox name
2903 * authentication identifier
2904 * Returns: T on success with data returned via callback, NIL on failure
2907 long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id)
2909 IMAPARG *args[3],ambx,aid;
2910 ambx.type = aid.type = ASTRING;
2911 ambx.text = (void *) mailbox; aid.text = (void *) id;
2912 args[0] = &ambx; args[1] = &aid; args[2] = NIL;
2913 return imap_acl_work (stream,"LISTRIGHTS",args);
2917 /* IMAP my rights
2918 * Accepts: mail stream
2919 * mailbox name
2920 * Returns: T on success with data returned via callback, NIL on failure
2923 long imap_myrights (MAILSTREAM *stream,char *mailbox)
2925 IMAPARG *args[2],ambx;
2926 ambx.type = ASTRING; ambx.text = (void *) mailbox;
2927 args[0] = &ambx; args[1] = NIL;
2928 return imap_acl_work (stream,"MYRIGHTS",args);
2932 /* IMAP ACL worker routine
2933 * Accepts: mail stream
2934 * command
2935 * command arguments
2936 * Returns: T on success, NIL on failure
2939 long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[])
2941 long ret = NIL;
2942 if (LEVELACL (stream)) { /* send command */
2943 IMAPPARSEDREPLY *reply;
2944 if (imap_OK (stream,reply = imap_send (stream,command,args)))
2945 ret = LONGT;
2946 else mm_log (reply->text,ERROR);
2948 else mm_log ("ACL not available on this IMAP server",ERROR);
2949 return ret;
2952 /* IMAP set quota
2953 * Accepts: mail stream
2954 * quota root name
2955 * resource limit list as a stringlist
2956 * Returns: T on success with data returned via callback, NIL on failure
2959 long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits)
2961 long ret = NIL;
2962 if (LEVELQUOTA (stream)) { /* send "SETQUOTA" */
2963 IMAPPARSEDREPLY *reply;
2964 IMAPARG *args[3],aqrt,alim;
2965 aqrt.type = ASTRING; aqrt.text = (void *) qroot;
2966 alim.type = SNLIST; alim.text = (void *) limits;
2967 args[0] = &aqrt; args[1] = &alim; args[2] = NIL;
2968 if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args)))
2969 ret = LONGT;
2970 else mm_log (reply->text,ERROR);
2972 else mm_log ("Quota not available on this IMAP server",ERROR);
2973 return ret;
2976 IDLIST *imap_parse_idlist (char *text)
2978 IDLIST *ret = NULL;
2979 char *s;
2980 char tmp[MAILTMPLEN];
2982 if(text == NULL) return NULL;
2983 for(s = text; *s == ' '; s++); /* move past spaces */
2984 if(*s == '(') s++;
2985 if(*s++ == '"'){
2986 char *t;
2987 for(t = s; *t && *t != '"'; t++);
2988 if(*t == '"'){
2989 ret = fs_get(sizeof(IDLIST));
2990 *t = '\0';
2991 ret->name = cpystr(s);
2992 *t = '"';
2993 for(s = t+1; *s == ' '; s++); /* move past spaces */
2994 if(*s++ == '"'){
2995 for(t = s; *t && *t != '"'; t++);
2996 if(*t == '"'){
2997 *t = '\0';
2998 ret->value = cpystr(s);
2999 *t++ = '"';
3000 ret->next = imap_parse_idlist(t);
3002 else {
3003 sprintf(tmp,"ID value not found for name %.80s, at %.80s", ret->name, s);
3004 fs_give((void **)&ret->name);
3005 fs_give((void **)&ret);
3006 mm_log (tmp, NIL); /* this is an technically an error */
3009 else { /* failed!, quit */
3010 sprintf(tmp,"ID name \"%.80s\" has no value", ret->name);
3011 fs_give((void **)&ret->name);
3012 fs_give((void **)&ret);
3013 mm_log (tmp, NIL); /* this is an technically an error */
3017 return ret;
3020 long imap_setid (MAILSTREAM *stream, IDLIST *idlist)
3022 long ret = NIL;
3023 if (LEVELID (stream)) { /* send "ID (params)" */
3024 IMAPPARSEDREPLY *reply;
3025 IMAPARG *args[2],aqrt;
3026 IDLIST *list;
3027 char *qroot, *p;
3028 long len = 0L;
3030 if(idlist == NULL) return ret;
3031 for (list = idlist; list != NULL; list = list->next)
3032 len += strlen(list->name) + strlen(list->value) + 6;
3033 if(len > 0){
3034 len += 1L; /* in case there is only one field */
3035 qroot = fs_get(len+1);
3036 memset((void *)&qroot[0], 0, len+1);
3037 p = qroot;
3038 for (list = idlist; list != NULL; list = list->next){
3039 sprintf(p, " \"%s\" \"%s\"", list->name, list->value);
3040 p += strlen(p);
3042 *p = ')';
3043 qroot[0] = '(';
3044 aqrt.type = ATOM; aqrt.text = (void *) qroot;
3045 args[0] = &aqrt; args[1] = NIL;
3046 if (imap_OK (stream,reply = imap_send (stream,"ID",args)))
3047 ret = LONGT;
3048 else mm_log (reply->text,ERROR);
3049 if(qroot) fs_give((void **) &qroot);
3050 } else mm_log("Empty or malformed ID list", ERROR);
3052 else mm_log ("ID capability not available on this IMAP server",ERROR);
3053 return ret;
3056 /* IMAP get quota
3057 * Accepts: mail stream
3058 * quota root name
3059 * Returns: T on success with data returned via callback, NIL on failure
3062 long imap_getquota (MAILSTREAM *stream,char *qroot)
3064 long ret = NIL;
3065 if (LEVELQUOTA (stream)) { /* send "GETQUOTA" */
3066 IMAPPARSEDREPLY *reply;
3067 IMAPARG *args[2],aqrt;
3068 aqrt.type = ASTRING; aqrt.text = (void *) qroot;
3069 args[0] = &aqrt; args[1] = NIL;
3070 if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args)))
3071 ret = LONGT;
3072 else mm_log (reply->text,ERROR);
3074 else mm_log ("Quota not available on this IMAP server",ERROR);
3075 return ret;
3079 /* IMAP get quota root
3080 * Accepts: mail stream
3081 * mailbox name
3082 * Returns: T on success with data returned via callback, NIL on failure
3085 long imap_getquotaroot (MAILSTREAM *stream,char *mailbox)
3087 long ret = NIL;
3088 if (LEVELQUOTA (stream)) { /* send "GETQUOTAROOT" */
3089 IMAPPARSEDREPLY *reply;
3090 IMAPARG *args[2],ambx;
3091 ambx.type = ASTRING; ambx.text = (void *) mailbox;
3092 args[0] = &ambx; args[1] = NIL;
3093 if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args)))
3094 ret = LONGT;
3095 else mm_log (reply->text,ERROR);
3097 else mm_log ("Quota not available on this IMAP server",ERROR);
3098 return ret;
3101 /* Internal routines */
3104 /* IMAP send command
3105 * Accepts: MAIL stream
3106 * command
3107 * argument list
3108 * Returns: parsed reply
3111 #define CMDBASE LOCAL->tmp /* command base */
3113 IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
3115 IMAPPARSEDREPLY *reply;
3116 IMAPARG *arg,**arglst;
3117 SORTPGM *spg;
3118 STRINGLIST *list;
3119 SIZEDTEXT st;
3120 APPENDDATA *map;
3121 sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL);
3122 size_t i;
3123 void *a;
3124 char c,*s,*t,tag[10];
3125 stream->unhealthy = NIL; /* make stream healthy again */
3126 /* gensym a new tag */
3127 sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
3128 if (!LOCAL->netstream) /* make sure have a session */
3129 return imap_fake (stream,tag,"[CLOSED] IMAP connection lost");
3130 mail_lock (stream); /* lock up the stream */
3131 if (sc) /* tell client sending a command */
3132 (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") &&
3133 compare_cstring (cmd,"STORE") &&
3134 compare_cstring (cmd,"SEARCH")) ?
3135 NIL : SC_EXPUNGEDEFERRED));
3136 /* ignore referral from previous command */
3137 if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
3138 sprintf (CMDBASE,"%s %s",tag,cmd);
3139 s = CMDBASE + strlen (CMDBASE);
3140 if ((arglst = args) != NULL) while ((arg = *arglst++) != NULL) {
3141 *s++ = ' '; /* delimit argument with space */
3142 switch (arg->type) {
3143 case ATOM: /* atom */
3144 for (t = (char *) arg->text; *t; *s++ = *t++);
3145 break;
3146 case NUMBER: /* number */
3147 sprintf (s,"%lu",(unsigned long) arg->text);
3148 s += strlen (s);
3149 break;
3150 case FLAGS: /* flag list as a single string */
3151 if (*(t = (char *) arg->text) != '(') {
3152 *s++ = '('; /* wrap parens around string */
3153 while (*t) *s++ = *t++;
3154 *s++ = ')'; /* wrap parens around string */
3156 else while (*t) *s++ = *t++;
3157 break;
3158 case ASTRING: /* atom or string, must be literal? */
3159 st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3160 if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) != NULL)
3161 return reply;
3162 break;
3163 case LITERAL: /* literal, as a stringstruct */
3164 if ((reply = imap_send_literal (stream,tag,&s,arg->text)) != NULL) return reply;
3165 break;
3167 case LIST: /* list of strings */
3168 list = (STRINGLIST *) arg->text;
3169 c = '('; /* open paren */
3170 do { /* for each list item */
3171 *s++ = c; /* write prefix character */
3172 if ((reply = imap_send_astring (stream,tag,&s,&list->text,NIL,
3173 CMDBASE+MAXCOMMAND)) != NULL) return reply;
3174 c = ' '; /* prefix character for subsequent strings */
3176 while ((list = list->next) != NULL);
3177 *s++ = ')'; /* close list */
3178 break;
3179 case SEARCHPROGRAM: /* search program */
3180 if ((reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
3181 CMDBASE+MAXCOMMAND)) != NULL)
3182 return reply;
3183 break;
3184 case SORTPROGRAM: /* search program */
3185 c = '('; /* open paren */
3186 for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) {
3187 *s++ = c; /* write prefix */
3188 if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++);
3189 switch (spg->function) {
3190 case SORTDATE:
3191 for (t = "DATE"; *t; *s++ = *t++);
3192 break;
3193 case SORTARRIVAL:
3194 for (t = "ARRIVAL"; *t; *s++ = *t++);
3195 break;
3196 case SORTFROM:
3197 for (t = "FROM"; *t; *s++ = *t++);
3198 break;
3199 case SORTSUBJECT:
3200 for (t = "SUBJECT"; *t; *s++ = *t++);
3201 break;
3202 case SORTTO:
3203 for (t = "TO"; *t; *s++ = *t++);
3204 break;
3205 case SORTCC:
3206 for (t = "CC"; *t; *s++ = *t++);
3207 break;
3208 case SORTSIZE:
3209 for (t = "SIZE"; *t; *s++ = *t++);
3210 break;
3211 default:
3212 fatal ("Unknown sort program function in imap_send()!");
3214 c = ' '; /* prefix character for subsequent items */
3216 *s++ = ')'; /* close list */
3217 break;
3219 case BODYTEXT: /* body section */
3220 for (t = "BODY["; *t; *s++ = *t++);
3221 for (t = (char *) arg->text; *t; *s++ = *t++);
3222 break;
3223 case BODYPEEK: /* body section */
3224 for (t = "BODY.PEEK["; *t; *s++ = *t++);
3225 for (t = (char *) arg->text; *t; *s++ = *t++);
3226 break;
3227 case BODYCLOSE: /* close bracket and possible length */
3228 s[-1] = ']'; /* no leading space */
3229 for (t = (char *) arg->text; *t; *s++ = *t++);
3230 break;
3231 case SEQUENCE: /* sequence */
3232 if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND)
3233 while (*t) *s++ = *t++; /* easy case */
3234 else {
3235 mail_unlock (stream); /* unlock stream */
3236 a = arg->text; /* save original sequence pointer */
3237 arg->type = ATOM; /* make recursive call be faster */
3238 do { /* break up into multiple commands */
3239 if (i <= MAXCOMMAND) {/* final part? */
3240 reply = imap_send (stream,cmd,args);
3241 i = 0; /* and mark as done */
3243 else { /* still needs to be split further */
3244 if (!(t = strchr (t + MAXCOMMAND - 30,',')) ||
3245 ((t - (char *) arg->text) > MAXCOMMAND))
3246 fatal ("impossible over-long sequence");
3247 *t = '\0'; /* tie off sequence at point of split*/
3248 /* recurse to do this part */
3249 reply = imap_send (stream,cmd,args);
3250 *t++ = ','; /* restore the comma in case something cares */
3251 /* punt if error */
3252 if (!imap_OK (stream,reply)) break;
3253 /* calculate size of remaining sequence */
3254 i -= (t - (char *) arg->text);
3255 /* point to new remaining sequence */
3256 arg->text = (void *) t;
3258 } while (i);
3259 arg->type = SEQUENCE; /* restore in case something cares */
3260 arg->text = a;
3261 return reply; /* return result */
3263 break;
3264 case LISTMAILBOX: /* astring with wildcards */
3265 st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
3266 if ((reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND)) != NULL)
3267 return reply;
3268 break;
3270 case MULTIAPPEND: /* append multiple messages */
3271 /* get package pointer */
3272 map = (APPENDDATA *) arg->text;
3273 if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)||
3274 !map->message) {
3275 STRING es;
3276 INIT (&es,mail_string,"",0);
3277 return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3278 reply : imap_fake (stream,tag,"Server zero-length literal error");
3280 case MULTIAPPENDREDO: /* redo multiappend */
3281 /* get package pointer */
3282 map = (APPENDDATA *) arg->text;
3283 do { /* make sure date valid if given */
3284 char datetmp[MAILTMPLEN];
3285 MESSAGECACHE elt;
3286 STRING es;
3287 if (!map->date || mail_parse_date (&elt,map->date)) {
3288 if ((t = map->flags) != NULL) { /* flags given? */
3289 if (*t != '(') {
3290 *s++ = '('; /* wrap parens around string */
3291 while (*t) *s++ = *t++;
3292 *s++ = ')'; /* wrap parens around string */
3294 else while (*t) *s++ = *t++;
3295 *s++ = ' '; /* delimit with space */
3297 if (map->date) { /* date given? */
3298 st.size = strlen ((char *) (st.data = (unsigned char *)
3299 mail_date (datetmp,&elt)));
3300 if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,
3301 CMDBASE+MAXCOMMAND)) != NULL) return reply;
3302 *s++ = ' '; /* delimit with space */
3304 if ((reply = imap_send_literal (stream,tag,&s,map->message)) != NULL)
3305 return reply;
3306 /* get next message */
3307 if ((*map->af) (stream,map->data,&map->flags,&map->date,
3308 &map->message)) {
3309 /* have a message, delete next in command */
3310 if (map->message) *s++ = ' ';
3311 continue; /* loop back for next message */
3314 /* bad date or need to abort */
3315 INIT (&es,mail_string,"",0);
3316 return (reply = imap_send_literal (stream,tag,&s,&es)) ?
3317 reply : imap_fake (stream,tag,"Server zero-length literal error");
3318 break; /* exit the loop */
3319 } while (map->message);
3320 break;
3322 case SNLIST: /* list of string/number pairs */
3323 list = (STRINGLIST *) arg->text;
3324 c = '('; /* open paren */
3325 do { /* for each list item */
3326 *s++ = c; /* write prefix character */
3327 if (list) { /* sigh, QUOTA has bizarre syntax! */
3328 for (t = (char *) list->text.data; *t; *s++ = *t++);
3329 sprintf (s," %lu",list->text.size);
3330 s += strlen (s);
3331 c = ' '; /* prefix character for subsequent strings */
3334 while ((list = list->next) != NULL);
3335 *s++ = ')'; /* close list */
3336 break;
3337 default:
3338 fatal ("Unknown argument type in imap_send()!");
3341 /* send the command */
3342 reply = imap_sout (stream,tag,CMDBASE,&s);
3343 mail_unlock (stream); /* unlock stream */
3344 return reply;
3347 /* IMAP send atom-string
3348 * Accepts: MAIL stream
3349 * reply tag
3350 * pointer to current position pointer of output bigbuf
3351 * atom-string to output
3352 * flag if list_wildcards allowed
3353 * maximum to write as atom or qstring
3354 * Returns: error reply or NIL if success
3357 IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
3358 SIZEDTEXT *as,long wildok,char *limit)
3360 unsigned long j;
3361 char c;
3362 STRING st;
3363 /* default to atom unless empty or loser */
3364 int qflag = (as->size && !LOCAL->loser) ? NIL : T;
3365 /* in case needed */
3366 INIT (&st,mail_string,(void *) as->data,as->size);
3367 /* always write literal if no space */
3368 if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st);
3369 for (j = 0; j < as->size; j++) switch (c = as->data[j]) {
3370 default: /* all other characters */
3371 if (!(c & 0x80)) { /* must not be 8bit */
3372 if (c <= ' ') qflag = T; /* must quote if a CTL */
3373 break;
3375 case '\0': /* not a CHAR */
3376 case '\012': case '\015': /* not a TEXT-CHAR */
3377 case '"': case '\\': /* quoted-specials (IMAP2 required this) */
3378 return imap_send_literal (stream,tag,s,&st);
3379 case '*': case '%': /* list_wildcards */
3380 if (wildok) break; /* allowed if doing the wild thing */
3381 /* atom_specials */
3382 case '(': case ')': case '{': case ' ': case 0x7f:
3383 #if 0
3384 case '"': case '\\': /* quoted-specials (could work in IMAP4) */
3385 #endif
3386 qflag = T; /* must use quoted string format */
3387 break;
3389 if (qflag) *(*s)++ = '"'; /* write open quote */
3390 for (j = 0; j < as->size; j++) *(*s)++ = as->data[j];
3391 if (qflag) *(*s)++ = '"'; /* write close quote */
3392 return NIL;
3395 /* IMAP send literal
3396 * Accepts: MAIL stream
3397 * reply tag
3398 * pointer to current position pointer of output bigbuf
3399 * literal to output as stringstruct
3400 * Returns: error reply or NIL if success
3403 IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
3404 STRING *st)
3406 IMAPPARSEDREPLY *reply;
3407 unsigned long i = SIZE (st);
3408 unsigned long j;
3409 sprintf (*s,"{%lu}",i); /* write literal count */
3410 *s += strlen (*s); /* size of literal count */
3411 /* send the command */
3412 reply = imap_sout (stream,tag,CMDBASE,s);
3413 if (strcmp (reply->tag,"+")) {/* prompt for more data? */
3414 mail_unlock (stream); /* no, give up */
3415 return reply;
3417 while (i) { /* dump the text */
3418 if (st->cursize) { /* if text to do in this chunk */
3419 /* RFC 3501 technically forbids NULs in literals. Normally, the
3420 * delivering MTA would take care of MIME converting the message text
3421 * so that it is NUL-free. If it doesn't, then we have the choice of
3422 * either violating IMAP by sending NULs, corrupting the data, or going
3423 * to lots of work to do MIME conversion in the IMAP server.
3425 * No current stringstruct driver objects to having its buffer patched.
3426 * If this ever changes, it will be necessary to change this kludge.
3428 /* patch NULs to C1 control */
3429 for (j = 0; j < st->cursize; ++j)
3430 if (!st->curpos[j]) st->curpos[j] = 0x80;
3431 if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) {
3432 mail_unlock (stream);
3433 return imap_fake (stream,tag,"[CLOSED] IMAP connection broken (data)");
3435 i -= st->cursize; /* note that we wrote out this much */
3436 st->curpos += (st->cursize - 1);
3437 st->cursize = 0;
3439 (*st->dtb->next) (st); /* advance to next buffer's worth */
3441 return NIL; /* success */
3444 #define ADD_STRING(X, Y) { \
3445 if(remain > 0){ \
3446 sprintf (u, (X), (Y)); \
3447 len = strlen(u); \
3448 if(len < remain){ \
3449 strncpy(t, u, remain); \
3450 t[remain-1] = '\0'; \
3451 remain -= len; \
3452 t += strlen (t); \
3458 long imap_search_x_gm_ext1 (MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long flags)
3460 char *cmd = (flags & SE_UID) ? "UID SEARCH X-GM-RAW" : "SEARCH X-GM-RAW";
3461 char *t, s[MAILTMPLEN+1], u[MAILTMPLEN];
3462 IMAPARG *args[4],apgm;
3463 IMAPPARSEDREPLY *reply;
3464 unsigned long i,j,k;
3465 MESSAGECACHE *elt;
3466 size_t remain = sizeof(s), len;
3468 u[0] = s[0] = '\0';
3469 t = s;
3470 args[1] = args[2] = args[3] = NIL;
3472 if(pgm->x_gm_ext1)
3473 ADD_STRING(" %s", pgm->x_gm_ext1->text.data);
3475 #if 0 /* maybe later */
3476 if (pgm->larger)
3477 ADD_STRING(" larger:%lu", pgm->larger);
3479 if (pgm->smaller)
3480 ADD_STRING(" smaller:%lu", pgm->smaller);
3482 if (pgm->deleted)
3483 ADD_STRING(" %s", "in:trash");
3485 if (pgm->undeleted)
3486 ADD_STRING(" %s", "-in:trash");
3488 if (pgm->draft)
3489 ADD_STRING(" %s", "in:drafts");
3491 if (pgm->undraft)
3492 ADD_STRING(" %s", "-in:drafts");
3494 if (pgm->flagged)
3495 ADD_STRING(" %s", "is:starred");
3497 if (pgm->unflagged)
3498 ADD_STRING(" %s", "-is:starred");
3500 if (pgm->seen)
3501 ADD_STRING(" %s", "-is:unread");
3503 if (pgm->unseen)
3504 ADD_STRING(" %s", "is:unread");
3506 if (pgm->keyword){
3507 STRINGLIST *sl;
3508 for(sl = pgm->keyword; remain > 0 && sl; sl = sl->next)
3509 ADD_STRING(" label:%s", sl->text.data);
3512 if (pgm->unkeyword){
3513 STRINGLIST *sl;
3514 for(sl = pgm->unkeyword; remain > 0 && sl; sl = sl->next)
3515 ADD_STRING(" -label:%s", sl->text.data);
3518 if (pgm->sentbefore){
3519 unsigned short date = pgm->sentbefore;
3520 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3521 (date >> 5) & 0xf, date & 0x1f);
3522 ADD_STRING(" before:%s", v);
3525 if (pgm->sentsince){
3526 unsigned short date = pgm->sentsince;
3527 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3528 (date >> 5) & 0xf, date & 0x1f);
3529 ADD_STRING(" older:%s", v);
3532 if (pgm->before){
3533 unsigned short date = pgm->before;
3534 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3535 (date >> 5) & 0xf, date & 0x1f);
3536 ADD_STRING(" before:%s", v);
3539 if (pgm->since){
3540 unsigned short date = pgm->since;
3541 sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
3542 (date >> 5) & 0xf, date & 0x1f);
3543 ADD_STRING(" before:%s", v);
3546 if (pgm->older){
3547 sprintf(v, "%dd", pgm->older/86400);
3548 ADD_STRING(" older_than:%s", v);
3551 if (pgm->younger){
3552 sprintf(v, "%dd", pgm->younger/86400);
3553 ADD_STRING(" newer_than:%s", v);
3556 if(pgm->bcc){
3557 STRINGLIST *sl;
3558 ADD_STRING("%s", pgm->bcc->next ? " {" : " ");
3559 for(sl = pgm->bcc; remain > 0 && sl; sl = sl->next){
3560 ADD_STRING("bcc:%s", sl->text.data);
3561 ADD_STRING("%s", sl->next ? " " : "}");
3565 if(pgm->cc){
3566 STRINGLIST *sl;
3567 ADD_STRING("%s", pgm->cc->next ? " {" : " ");
3568 for(sl = pgm->cc; remain > 0 && sl; sl = sl->next){
3569 ADD_STRING("cc:%s", sl->text.data);
3570 ADD_STRING("%s", sl->next ? " " : "}");
3574 if(pgm->from){
3575 STRINGLIST *sl;
3576 ADD_STRING("%s", pgm->from->next ? " {" : " ");
3577 for(sl = pgm->from; remain > 0 && sl; sl = sl->next){
3578 ADD_STRING("from:%s", sl->text.data);
3579 ADD_STRING("%s", sl->next ? " " : (pgm->from->next ? "}" : ""));
3583 if(pgm->to){
3584 STRINGLIST *sl;
3585 ADD_STRING("%s", pgm->to->next ? " {" : " ");
3586 for(sl = pgm->to; remain > 0 && sl; sl = sl->next){
3587 ADD_STRING("to:%s", sl->text.data);
3588 ADD_STRING("%s", sl->next ? " " : (pgm->to->next ? "}" : ""));
3592 if(pgm->subject){
3593 STRINGLIST *sl;
3594 ADD_STRING("%s", pgm->subject->next ? " {" : " ");
3595 for(sl = pgm->subject; remain > 0 && sl; sl = sl->next){
3596 ADD_STRING("subject:(%s)", sl->text.data);
3597 ADD_STRING("%s", sl->next ? " " : (pgm->subject->next ? "}" : ""));
3601 if(pgm->body){
3602 STRINGLIST *sl;
3603 ADD_STRING("%s", pgm->body->next ? " {" : " ");
3604 for(sl = pgm->body; remain > 0 && sl; sl = sl->next){
3605 ADD_STRING(" %s", sl->text.data);
3606 ADD_STRING("%s", sl->next ? " " : (pgm->body->next ? "}" : ""));
3610 if (mail_valid_net_parse (stream->mailbox,&mb)){
3611 p = strchr(mb.mailbox, '/');
3612 ADD_STRING(" in:%s", p ? p+1 : "inbox");
3615 #endif /* maybe later */
3617 s[0] = '\"';
3618 strcat(t, "\"");
3620 apgm.type = ATOM; apgm.text = (void *) s;
3621 args[0] = &apgm;
3622 args[1] = NIL;
3623 LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
3624 reply = imap_send (stream,cmd,args);
3625 LOCAL->uidsearch = NIL;
3626 /* do locally if server won't grok */
3627 if (!strcmp (reply->key,"BAD")) {
3628 if ((flags & SE_NOLOCAL) ||
3629 !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
3630 return NIL;
3632 else if (!imap_OK (stream,reply)) {
3633 mm_log (reply->text,ERROR);
3634 return NIL;
3637 /* can never pre-fetch with a short cache */
3638 if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
3639 !stream->scache) { /* only if prefetching permitted */
3640 t = LOCAL->tmp; /* build sequence in temporary buffer */
3641 *t = '\0'; /* initially nothing */
3642 /* search through mailbox */
3643 for (i = 1; k && (i <= stream->nmsgs); ++i)
3644 /* for searched messages with no envelope */
3645 if ((elt = mail_elt (stream,i)) && elt->searched &&
3646 !mail_elt (stream,i)->private.msg.env) {
3647 /* prepend with comma if not first time */
3648 if (LOCAL->tmp[0]) *t++ = ',';
3649 sprintf (t,"%lu",j = i);/* output message number */
3650 t += strlen (t); /* point at end of string */
3651 k--; /* count one up */
3652 /* search for possible end of range */
3653 while (k && (i < stream->nmsgs) &&
3654 (elt = mail_elt (stream,i+1))->searched &&
3655 !elt->private.msg.env) i++,k--;
3656 if (i != j) { /* if a range */
3657 sprintf (t,":%lu",i); /* output delimiter and end of range */
3658 t += strlen (t); /* point at end of string */
3660 if ((t - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
3662 if (LOCAL->tmp[0]) { /* anything to pre-fetch? */
3663 /* pre-fetch envelopes for the first imap_prefetch number of messages */
3664 if (!imap_OK (stream,reply =
3665 imap_fetch (stream,t = cpystr (LOCAL->tmp),FT_NEEDENV +
3666 ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
3667 ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
3668 mm_log (reply->text,ERROR);
3669 fs_give ((void **) &t); /* flush copy of sequence */
3672 return LONGT;
3675 /* IMAP send search program
3676 * Accepts: MAIL stream
3677 * reply tag
3678 * base pointer if trimming needed
3679 * pointer to current position pointer of output bigbuf
3680 * search program to output
3681 * pointer to limit guideline
3682 * Returns: error reply or NIL if success
3686 IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
3687 char **s,SEARCHPGM *pgm,char *limit)
3689 IMAPPARSEDREPLY *reply;
3690 SEARCHHEADER *hdr;
3691 SEARCHOR *pgo;
3692 SEARCHPGMLIST *pgl;
3693 char *t;
3694 /* trim if called recursively */
3695 if (base) *s = imap_send_spgm_trim (base,*s,NIL);
3696 base = *s; /* this is the new base */
3697 /* default searchpgm */
3698 for (t = "ALL"; *t; *(*s)++ = *t++);
3699 if (!pgm) return NIL; /* done if NIL searchpgm */
3700 if ((pgm->msgno && /* message sequences */
3701 (pgm->msgno->next || /* trim away first:last */
3702 (pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) &&
3703 (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) ||
3704 (pgm->uid &&
3705 (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit))))
3706 return reply;
3707 /* message sizes */
3708 if (pgm->larger) {
3709 sprintf (*s," LARGER %lu",pgm->larger);
3710 *s += strlen (*s);
3712 if (pgm->smaller) {
3713 sprintf (*s," SMALLER %lu",pgm->smaller);
3714 *s += strlen (*s);
3717 /* message flags */
3718 if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++);
3719 if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++);
3720 if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++);
3721 if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++);
3722 if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++);
3723 if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++);
3724 if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++);
3725 if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++);
3726 if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++);
3727 if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++);
3728 if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++);
3729 if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++);
3730 if ((pgm->keyword && /* keywords */
3731 (reply = imap_send_slist (stream,tag,base,s," KEYWORD ",pgm->keyword,
3732 limit))) ||
3733 (pgm->unkeyword &&
3734 (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ",
3735 pgm->unkeyword,limit))))
3736 return reply;
3737 /* sent date ranges */
3738 if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
3739 if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
3740 if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
3741 /* internal date ranges */
3742 if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
3743 if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
3744 if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
3745 if (pgm->older) {
3746 sprintf (*s," OLDER %lu",pgm->older);
3747 *s += strlen (*s);
3749 if (pgm->younger) {
3750 sprintf (*s," YOUNGER %lu",pgm->younger);
3751 *s += strlen (*s);
3753 /* search texts */
3754 if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ",
3755 pgm->bcc,limit))) ||
3756 (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc,
3757 limit))) ||
3758 (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ",
3759 pgm->from,limit))) ||
3760 (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to,
3761 limit))))
3762 return reply;
3763 if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ",
3764 pgm->subject,limit))) ||
3765 (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ",
3766 pgm->body,limit))) ||
3767 (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ",
3768 pgm->text,limit))))
3769 return reply;
3771 /* Note that these criteria are not supported by IMAP and have to be
3772 emulated */
3773 if ((pgm->return_path &&
3774 (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ",
3775 pgm->return_path,limit))) ||
3776 (pgm->sender &&
3777 (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ",
3778 pgm->sender,limit))) ||
3779 (pgm->reply_to &&
3780 (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ",
3781 pgm->reply_to,limit))) ||
3782 (pgm->in_reply_to &&
3783 (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ",
3784 pgm->in_reply_to,limit))) ||
3785 (pgm->message_id &&
3786 (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ",
3787 pgm->message_id,limit))) ||
3788 (pgm->newsgroups &&
3789 (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ",
3790 pgm->newsgroups,limit))) ||
3791 (pgm->followup_to &&
3792 (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ",
3793 pgm->followup_to,limit))) ||
3794 (pgm->references &&
3795 (reply = imap_send_slist (stream,tag,base,s," HEADER References ",
3796 pgm->references,limit)))) return reply;
3798 /* all other headers */
3799 if ((hdr = pgm->header) != NULL) do {
3800 *s = imap_send_spgm_trim (base,*s," HEADER ");
3801 if ((reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) != NULL)
3802 return reply;
3803 *(*s)++ = ' ';
3804 if ((reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) != NULL)
3805 return reply;
3806 } while ((hdr = hdr->next) != NULL);
3807 for (pgo = pgm->or; pgo; pgo = pgo->next) {
3808 *s = imap_send_spgm_trim (base,*s," OR (");
3809 if ((reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit)) != NULL)
3810 return reply;
3811 for (t = ") ("; *t; *(*s)++ = *t++);
3812 if ((reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) != NULL)
3813 return reply;
3814 *(*s)++ = ')';
3816 for (pgl = pgm->not; pgl; pgl = pgl->next) {
3817 *s = imap_send_spgm_trim (base,*s," NOT (");
3818 if ((reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit)) != NULL)
3819 return reply;
3820 *(*s)++ = ')';
3822 /* trim if needed */
3823 *s = imap_send_spgm_trim (base,*s,NIL);
3824 return NIL; /* search program written OK */
3828 /* Write new text and trim extraneous "ALL" from searchpgm
3829 * Accepts: pointer to start of searchpgm or NIL
3830 * current end pointer
3831 * new text to write or NIL
3832 * Returns: new end pointer, trimmed if needed
3835 char *imap_send_spgm_trim (char *base,char *s,char *text)
3837 char *t;
3838 /* write new text */
3839 if (text) for (t = text; *t; *s++ = *t++);
3840 /* need to trim? */
3841 if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') &&
3842 (base[2] == 'L') && (base[3] == ' ')) {
3843 memmove (base,t,s - t); /* yes, blat down remaining text */
3844 s -= 4; /* and reduce current pointer */
3846 return s; /* return new end pointer */
3849 /* IMAP send search set
3850 * Accepts: MAIL stream
3851 * current command tag
3852 * base pointer if trimming needed
3853 * pointer to current position pointer of output bigbuf
3854 * search set to output
3855 * message prefix
3856 * maximum output pointer
3857 * Returns: NIL if success, error reply if error
3860 IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
3861 char **s,SEARCHSET *set,char *prefix,
3862 char *limit)
3864 IMAPPARSEDREPLY *reply;
3865 STRING st;
3866 char c,*t;
3867 char *start = *s;
3868 /* trim and write prefix */
3869 *s = imap_send_spgm_trim (base,*s,prefix);
3870 /* run down search list */
3871 for (c = NIL; set && (*s < limit); set = set->next, c = ',') {
3872 if (c) *(*s)++ = c; /* write delimiter and first value */
3873 if (set->first == 0xffffffff) *(*s)++ = '*';
3874 else {
3875 sprintf (*s,"%lu",set->first);
3876 *s += strlen (*s);
3878 /* have a second value? */
3879 if (set->last && (set->first != set->last)) {
3880 *(*s)++ = ':'; /* write delimiter and second value */
3881 if (set->last == 0xffffffff) *(*s)++ = '*';
3882 else {
3883 sprintf (*s,"%lu",set->last);
3884 *s += strlen (*s);
3888 if (set) { /* insert "OR" in front of incomplete set */
3889 memmove (start + 3,start,*s - start);
3890 memcpy (start," OR",3);
3891 *s += 3; /* point to end of buffer */
3892 /* write glue that is equivalent to ALL */
3893 for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++);
3894 /* but broken by a literal */
3895 INIT (&st,mail_string,(void *) "FOO",3);
3896 if ((reply = imap_send_literal (stream,tag,s,&st)) != NULL) return reply;
3897 *(*s)++ = ')'; /* close glue */
3898 if ((reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) != NULL)
3899 return reply;
3900 *(*s)++ = ')'; /* close second OR argument */
3902 return NIL;
3905 /* IMAP send search list
3906 * Accepts: MAIL stream
3907 * reply tag
3908 * base pointer if trimming needed
3909 * pointer to current position pointer of output bigbuf
3910 * name of search list
3911 * search list to output
3912 * maximum output pointer
3913 * Returns: NIL if success, error reply if error
3916 IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
3917 char **s,char *name,STRINGLIST *list,
3918 char *limit)
3920 IMAPPARSEDREPLY *reply;
3921 do {
3922 *s = imap_send_spgm_trim (base,*s,name);
3923 base = NIL; /* no longer need trimming */
3924 reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit);
3926 while (!reply && (list = list->next));
3927 return reply;
3931 /* IMAP send search date
3932 * Accepts: pointer to current position pointer of output bigbuf
3933 * field name
3934 * search date to output
3937 void imap_send_sdate (char **s,char *name,unsigned short date)
3939 sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
3940 months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
3941 *s += strlen (*s);
3944 /* IMAP send buffered command to sender
3945 * Accepts: MAIL stream
3946 * reply tag
3947 * string
3948 * pointer to string tail pointer
3949 * Returns: reply
3952 IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s)
3954 IMAPPARSEDREPLY *reply;
3955 if (stream->debug) { /* output debugging telemetry */
3956 **s = '\0';
3957 mail_dlog (base,LOCAL->sensitive);
3959 *(*s)++ = '\015'; /* append CRLF */
3960 *(*s)++ = '\012';
3961 **s = '\0';
3962 reply = net_sout (LOCAL->netstream,base,*s - base) ?
3963 imap_reply (stream,tag) :
3964 imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)");
3965 *s = base; /* restart buffer */
3966 return reply;
3970 /* IMAP send null-terminated string to sender
3971 * Accepts: MAIL stream
3972 * string
3973 * Returns: T if success, else NIL
3976 long imap_soutr (MAILSTREAM *stream,char *string)
3978 long ret;
3979 unsigned long i;
3980 char *s;
3981 if (stream->debug) mm_dlog (string);
3982 sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1),
3983 "%s\015\012",string);
3984 ret = net_sout (LOCAL->netstream,s,i);
3985 fs_give ((void **) &s);
3986 return ret;
3989 /* IMAP get reply
3990 * Accepts: MAIL stream
3991 * tag to search or NIL if want a greeting
3992 * Returns: parsed reply, never NIL
3995 IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
3997 IMAPPARSEDREPLY *reply;
3998 while (LOCAL->netstream) { /* parse reply from server */
3999 if ((reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) != NULL) {
4000 /* continuation ready? */
4001 if (!strcmp (reply->tag,"+")) return reply;
4002 /* untagged data? */
4003 else if (!strcmp (reply->tag,"*")) {
4004 imap_parse_unsolicited (stream,reply);
4005 if (!tag) return reply; /* return if just wanted greeting */
4007 else { /* tagged data */
4008 if (tag && !compare_cstring (tag,reply->tag)) return reply;
4009 /* report bogon */
4010 sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
4011 (char *) reply->tag,(char *) reply->key,(char *) reply->text);
4012 mm_notify (stream,LOCAL->tmp,WARN);
4013 stream->unhealthy = T;
4017 return imap_fake (stream,tag,
4018 "[CLOSED] IMAP connection broken (server response)");
4021 /* IMAP parse reply
4022 * Accepts: MAIL stream
4023 * text of reply
4024 * Returns: parsed reply, or NIL if can't parse at least a tag and key
4028 IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
4030 char *r;
4031 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
4032 /* init fields in case error */
4033 LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL;
4034 if (!(LOCAL->reply.line = text)) {
4035 /* NIL text means the stream died */
4036 if (LOCAL->netstream) net_close (LOCAL->netstream);
4037 LOCAL->netstream = NIL;
4038 return NIL;
4040 if (stream->debug) mm_dlog (LOCAL->reply.line);
4041 if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) {
4042 mm_notify (stream,"IMAP server sent a blank line",WARN);
4043 stream->unhealthy = T;
4044 return NIL;
4046 /* non-continuation replies */
4047 if (strcmp (LOCAL->reply.tag,"+")) {
4048 /* parse key */
4049 if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) {
4050 /* determine what is missing */
4051 sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",
4052 (char *) LOCAL->reply.tag);
4053 mm_notify (stream,LOCAL->tmp,WARN);
4054 stream->unhealthy = T;
4055 return NIL; /* can't parse this text */
4057 ucase (LOCAL->reply.key); /* canonicalize key to upper */
4058 /* get text as well, allow empty text */
4059 if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
4060 LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
4062 else { /* special handling of continuation */
4063 LOCAL->reply.key = "BAD"; /* so it barfs if not expecting continuation */
4064 if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
4065 LOCAL->reply.text = "";
4067 return &LOCAL->reply; /* return parsed reply */
4070 /* IMAP fake reply when stream determined to be dead
4071 * Accepts: MAIL stream
4072 * tag
4073 * text of fake reply (must start with "[CLOSED]")
4074 * Returns: parsed reply
4077 IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
4079 mm_notify (stream,text,BYE); /* send bye alert */
4080 if (LOCAL->netstream) net_close (LOCAL->netstream);
4081 LOCAL->netstream = NIL; /* farewell, dear NET stream... */
4082 /* flush previous reply */
4083 if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
4084 /* build new fake reply */
4085 LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*");
4086 LOCAL->reply.key = "NO";
4087 LOCAL->reply.text = text;
4088 return &LOCAL->reply; /* return parsed reply */
4092 /* IMAP check for OK response in tagged reply
4093 * Accepts: MAIL stream
4094 * parsed reply
4095 * Returns: T if OK else NIL
4098 long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
4100 long ret = NIL;
4101 /* OK - operation succeeded */
4102 if (!strcmp (reply->key,"OK")) {
4103 imap_parse_response (stream,reply->text,NIL,NIL);
4104 ret = T;
4106 /* NO - operation failed */
4107 else if (!strcmp (reply->key,"NO"))
4108 imap_parse_response (stream,reply->text,WARN,NIL);
4109 else { /* BAD - operation rejected */
4110 if (!strcmp (reply->key,"BAD")) {
4111 imap_parse_response (stream,reply->text,ERROR,NIL);
4112 sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text);
4114 /* bad protocol received */
4115 else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
4116 (char *) reply->key,(char *) reply->text);
4117 mm_log (LOCAL->tmp,ERROR); /* either way, this is not good */
4119 return ret;
4122 /* IMAP parse and act upon unsolicited reply
4123 * Accepts: MAIL stream
4124 * parsed reply
4127 void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
4129 unsigned long i = 0;
4130 unsigned long j,msgno;
4131 unsigned char *s,*t;
4132 char *r;
4133 /* see if key is a number */
4134 if (isdigit (*reply->key)) {
4135 msgno = strtoul (reply->key,(char **) &s,10);
4136 if (*s) { /* better be nothing after number */
4137 sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4138 (char *) reply->key);
4139 mm_notify (stream,LOCAL->tmp,WARN);
4140 stream->unhealthy = T;
4141 return;
4143 if (!reply->text) { /* better be some data */
4144 mm_notify (stream,"Missing message data",WARN);
4145 stream->unhealthy = T;
4146 return;
4148 /* get message data type, canonicalize upper */
4149 s = ucase (strtok_r (reply->text," ",&r));
4150 t = strtok_r (NIL,"\n",&r); /* and locate the text after it */
4151 /* now take the action */
4152 /* change in size of mailbox */
4153 if (LOCAL->authed && !strcmp (s,"EXISTS") && (msgno >= stream->nmsgs))
4154 mail_exists (stream,msgno);
4155 else if (LOCAL->authed && !strcmp (s,"RECENT") && (msgno <= stream->nmsgs))
4156 mail_recent (stream,msgno);
4157 else if (LOCAL->authed && !strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) {
4158 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4159 MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
4160 if (elt) imap_gc_body (elt->private.msg.body);
4161 /* notify upper level */
4162 mail_expunged (stream,msgno);
4165 else if (LOCAL->authed && t && (!strcmp (s,"FETCH") || !strcmp (s,"STORE")) &&
4166 msgno && (msgno <= stream->nmsgs)) {
4167 char *prop;
4168 GETS_DATA md;
4169 ENVELOPE **e;
4170 MESSAGECACHE *elt = mail_elt (stream,msgno);
4171 ENVELOPE *env = NIL;
4172 imapenvelope_t ie =
4173 (imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL);
4174 ++t; /* skip past open parenthesis */
4175 /* parse Lisp-form property list */
4176 while ((prop = (strtok_r (t," )",&r))) && (t = strtok_r (NIL,"\n",&r))) {
4177 INIT_GETS (md,stream,elt->msgno,NIL,0,0);
4178 e = NIL; /* not pointing at any envelope yet */
4179 /* canonicalize property, parse it */
4180 if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t);
4181 else if (!strcmp (prop,"INTERNALDATE") &&
4182 (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) {
4183 if (!mail_parse_date (elt,s)) {
4184 sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s);
4185 mm_notify (stream,LOCAL->tmp,WARN);
4186 stream->unhealthy = T;
4187 /* slam in default so we don't try again */
4188 mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000");
4190 fs_give ((void **) &s);
4192 /* unique identifier */
4193 else if (!strcmp (prop,"UID")) {
4194 LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10);
4195 LOCAL->lastuid.msgno = elt->msgno;
4197 else if (!strcmp (prop,"ENVELOPE")) {
4198 if (stream->scache) { /* short cache, flush old stuff */
4199 mail_free_body (&stream->body);
4200 stream->msgno = elt->msgno;
4201 e = &stream->env; /* get pointer to envelope */
4203 else e = &elt->private.msg.env;
4204 imap_parse_envelope (stream,e,&t,reply);
4206 else if (!strncmp (prop,"BODY",4)) {
4207 if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) {
4208 BODY **body;
4209 if (stream->scache){/* short cache, flush old stuff */
4210 if (stream->msgno != msgno) {
4211 mail_free_envelope (&stream->env);
4212 sprintf (LOCAL->tmp,"Body received for %lu but current is %lu",
4213 msgno,stream->msgno);
4214 stream->msgno = msgno;
4216 /* get pointer to body */
4217 body = &stream->body;
4219 else body = &elt->private.msg.body;
4220 /* flush any prior body */
4221 mail_free_body (body);
4222 /* instantiate and parse a new body */
4223 imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply);
4226 else if (prop[4] == '[') {
4227 STRINGLIST *stl = NIL;
4228 SIZEDTEXT text;
4229 /* will want to return envelope data */
4230 if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") ||
4231 !strcmp (md.what,"0]"))
4232 e = stream->scache ? &stream->env : &elt->private.msg.env;
4233 LOCAL->tmp[0] ='\0';/* no errors yet */
4234 /* found end of section? */
4235 if (!(s = strchr (md.what,']'))) {
4236 /* skip leading nesting */
4237 for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++);
4238 /* better be one of these */
4239 if (strncmp (s,"HEADER.FIELDS",13) &&
4240 (!s[13] || strcmp (s+13,".NOT")))
4241 sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what);
4242 /* get list of headers */
4243 else if (!(stl = imap_parse_stringlist (stream,&t,reply)))
4244 sprintf (LOCAL->tmp,"Bogus header field list: %.80s",
4245 (char *) t);
4246 else if (*t != ']')
4247 sprintf (LOCAL->tmp,"Unterminated header section: %.80s",
4248 (char *) t);
4249 /* point after the text */
4250 else if ((t = strchr (s = t,' ')) != NULL) *t++ = '\0';
4252 if (s && !LOCAL->tmp[0]) {
4253 *s++ = '\0'; /* tie off section specifier */
4254 if (*s == '<') { /* partial specifier? */
4255 md.first = strtoul (s+1,(char **) &s,10) + 1;
4256 if (*s++ != '>') /* make sure properly terminated */
4257 sprintf (LOCAL->tmp,"Unterminated partial data: %.80s",
4258 (char *) s-1);
4260 if (!LOCAL->tmp[0] && *s)
4261 sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s);
4263 if (LOCAL->tmp[0]) { /* got any errors? */
4264 mm_notify (stream,LOCAL->tmp,WARN);
4265 stream->unhealthy = T;
4266 mail_free_stringlist (&stl);
4268 else { /* parse text from server */
4269 text.data = (unsigned char *)
4270 imap_parse_string (stream,&t,reply,
4271 ((md.what[0] && (md.what[0] != 'H')) ||
4272 md.first || md.last) ? &md : NIL,
4273 &text.size,NIL);
4274 /* all done if partial */
4275 if (md.first || md.last) mail_free_stringlist (&stl);
4276 /* otherwise register it in the cache */
4277 else imap_cache (stream,msgno,md.what,stl,&text);
4279 fs_give ((void **) &md.what);
4281 else {
4282 sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop);
4283 mm_notify (stream,LOCAL->tmp,WARN);
4284 stream->unhealthy = T;
4288 /* one of the RFC822 props? */
4289 else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){
4290 SIZEDTEXT text;
4291 if (!prop[6]) { /* cache full message */
4292 md.what = "";
4293 text.data = (unsigned char *)
4294 imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
4295 imap_cache (stream,msgno,md.what,NIL,&text);
4297 else if (!strcmp (prop+7,"SIZE"))
4298 elt->rfc822_size = strtoul (t,(char **) &t,10);
4299 /* legacy properties */
4300 else if (!strcmp (prop+7,"HEADER")) {
4301 text.data = (unsigned char *)
4302 imap_parse_string (stream,&t,reply,NIL,&text.size,NIL);
4303 imap_cache (stream,msgno,"HEADER",NIL,&text);
4304 e = stream->scache ? &stream->env : &elt->private.msg.env;
4306 else if (!strcmp (prop+7,"TEXT")) {
4307 md.what = "TEXT";
4308 text.data = (unsigned char *)
4309 imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
4310 imap_cache (stream,msgno,md.what,NIL,&text);
4312 else {
4313 sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop);
4314 mm_notify (stream,LOCAL->tmp,WARN);
4315 stream->unhealthy = T;
4318 else {
4319 sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
4320 mm_notify (stream,LOCAL->tmp,WARN);
4321 stream->unhealthy = T;
4323 if (e && *e) env = *e; /* note envelope if we got one */
4325 if (prop) {
4326 sprintf (LOCAL->tmp,"Missing data for property: %.80s",prop);
4327 mm_notify (stream,LOCAL->tmp,WARN);
4328 stream->unhealthy = T;
4330 /* do callback if requested */
4331 if (ie && env) (*ie) (stream,msgno,env);
4333 /* obsolete response to COPY */
4334 else if (strcmp (s,"COPY") || !LOCAL->authed) {
4335 sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s);
4336 mm_notify (stream,LOCAL->tmp,WARN);
4337 stream->unhealthy = T;
4341 else if (LOCAL->authed && !strcmp (reply->key,"FLAGS") && reply->text &&
4342 (*reply->text == '(') &&
4343 (s = strtok_r (reply->text+1," )",&r)))
4344 do if (*s != '\\') {
4345 for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] &&
4346 compare_cstring (s,stream->user_flags[i]); i++);
4347 if (i > NUSERFLAGS) {
4348 sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s",
4349 (char *) s);
4350 mm_notify (stream,LOCAL->tmp,WARN);
4352 else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s);
4354 while ((s = strtok_r (NIL," )",&r)) != NULL);
4355 else if (LOCAL->authed && !strcmp (reply->key,"SEARCH")) {
4356 /* only do something if have text */
4357 if (reply->text && (t = strtok_r (reply->text," ",&r))) do
4358 if ((i = strtoul (t,NIL,10)) != 0L) {
4359 /* UIDs always passed to main program */
4360 if (LOCAL->uidsearch) mm_searched (stream,i);
4361 /* should be a msgno then */
4362 else if ((i <= stream->nmsgs) &&
4363 (!LOCAL->filter || mail_elt (stream,i)->private.filter)) {
4364 mail_elt (stream,i)->searched = T;
4365 if (!stream->silent) mm_searched (stream,i);
4367 } while ((t = strtok_r (NIL," ",&r)) != NULL);
4369 else if (LOCAL->authed && !strcmp (reply->key,"SORT")) {
4370 sortresults_t sr = (sortresults_t)
4371 mail_parameters (NIL,GET_SORTRESULTS,NIL);
4372 LOCAL->sortsize = 0; /* initialize sort data */
4373 if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
4374 LOCAL->sortdata = (unsigned long *)
4375 fs_get ((stream->nmsgs + 1) * sizeof (unsigned long));
4376 /* only do something if have text */
4377 if (reply->text && (t = strtok_r (reply->text," ",&r))) {
4378 do if ((i = atol (t)) && (LOCAL->filter ?
4379 mail_elt (stream,i)->searched : T))
4380 LOCAL->sortdata[LOCAL->sortsize++] = i;
4381 while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs));
4383 LOCAL->sortdata[LOCAL->sortsize] = 0;
4384 /* also return via callback if requested */
4385 if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize);
4387 else if (LOCAL->authed && !strcmp (reply->key,"THREAD")) {
4388 threadresults_t tr = (threadresults_t)
4389 mail_parameters (NIL,GET_THREADRESULTS,NIL);
4390 if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
4391 if ((s = reply->text) != NULL) {
4392 LOCAL->threaddata = imap_parse_thread (stream,&s);
4393 if (tr) (*tr) (stream,LOCAL->threaddata);
4394 if (s && *s) {
4395 sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s);
4396 mm_notify (stream,LOCAL->tmp,WARN);
4397 stream->unhealthy = T;
4402 else if (LOCAL->authed && !strcmp (reply->key,"STATUS") && reply->text) {
4403 MAILSTATUS status;
4404 unsigned char *txt = reply->text;
4405 if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt &&
4406 (*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) &&
4407 (s - txt)) {
4408 *s = '\0'; /* tie off status data */
4409 /* initialize data block */
4410 status.flags = status.messages = status.recent = status.unseen =
4411 status.uidnext = status.uidvalidity = 0;
4412 while (*txt && (s = strchr (txt,' '))) {
4413 *s++ = '\0'; /* tie off status attribute name */
4414 /* get attribute value */
4415 i = strtoul (s,(char **) &s,10);
4416 if (!compare_cstring (txt,"MESSAGES")) {
4417 status.flags |= SA_MESSAGES;
4418 status.messages = i;
4420 else if (!compare_cstring (txt,"RECENT")) {
4421 status.flags |= SA_RECENT;
4422 status.recent = i;
4424 else if (!compare_cstring (txt,"UNSEEN")) {
4425 status.flags |= SA_UNSEEN;
4426 status.unseen = i;
4428 else if (!compare_cstring (txt,"UIDNEXT")) {
4429 status.flags |= SA_UIDNEXT;
4430 status.uidnext = i;
4432 else if (!compare_cstring (txt,"UIDVALIDITY")) {
4433 status.flags |= SA_UIDVALIDITY;
4434 status.uidvalidity = i;
4436 /* next attribute */
4437 txt = (*s == ' ') ? s + 1 : s;
4439 if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) <
4440 IMAPTMPLEN) {
4441 strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t);
4442 /* pass status to main program */
4443 mm_status (stream,LOCAL->tmp,&status);
4446 if (t) fs_give ((void **) &t);
4449 else if (LOCAL->authed
4450 && (!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
4451 reply->text && (*reply->text == '(') &&
4452 (s = strchr (reply->text,')')) && (s[1] == ' ')) {
4453 char delimiter = '\0';
4454 *s++ = '\0'; /* tie off attribute list */
4455 /* parse attribute list */
4456 if ((t = strtok_r (reply->text+1," ",&r)) != NULL) do {
4457 if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS;
4458 else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT;
4459 else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED;
4460 else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED;
4461 else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN;
4462 else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN;
4463 else if (!compare_cstring (t,"\\All")) i |= LATT_ALL;
4464 else if (!compare_cstring (t,"\\Archive")) i |= LATT_ARCHIVE;
4465 else if (!compare_cstring (t,"\\Drafts")) i |= LATT_DRAFTS;
4466 else if (!compare_cstring (t,"\\Flagged")) i |= LATT_FLAGGED;
4467 else if (!compare_cstring (t,"\\Junk")) i |= LATT_JUNK;
4468 else if (!compare_cstring (t,"\\Sent")) i |= LATT_SENT;
4469 else if (!compare_cstring (t,"\\Trash")) i |= LATT_TRASH;
4470 /* ignore extension flags */
4472 while ((t = strtok_r (NIL," ",&r)) != NULL);
4473 switch (*++s) { /* process delimiter */
4474 case 'N': /* NIL */
4475 case 'n':
4476 s += 4; /* skip over NIL<space> */
4477 break;
4478 case '"': /* have a delimiter */
4479 delimiter = (*++s == '\\') ? *++s : *s;
4480 s += 3; /* skip over <delimiter><quote><space> */
4482 /* parse the mailbox name */
4483 if ((t = imap_parse_astring (stream,&s,reply,&j)) != NULL) {
4484 /* prepend prefix if requested */
4485 if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN))
4486 sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t);
4487 else s = t; /* otherwise just mailbox name */
4488 /* pass data to main program */
4489 if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i);
4490 else mm_list (stream,delimiter,s,i);
4491 fs_give ((void **) &t); /* flush mailbox name */
4494 else if (LOCAL->authed && !strcmp (reply->key,"NAMESPACE")) {
4495 if (LOCAL->namespace) {
4496 mail_free_namespace (&LOCAL->namespace[0]);
4497 mail_free_namespace (&LOCAL->namespace[1]);
4498 mail_free_namespace (&LOCAL->namespace[2]);
4500 else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *));
4501 if ((s = reply->text) != NULL) { /* parse namespace results */
4502 LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply);
4503 LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply);
4504 LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply);
4505 if (s && *s) {
4506 sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s);
4507 mm_notify (stream,LOCAL->tmp,WARN);
4508 stream->unhealthy = T;
4511 else {
4512 mm_notify (stream,"Missing namespace list",WARN);
4513 stream->unhealthy = T;
4517 else if (LOCAL->authed && !strcmp (reply->key,"ACL") && (s = reply->text) &&
4518 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4519 getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL);
4520 if (s && (*s++ == ' ')) {
4521 ACLLIST *al = mail_newacllist ();
4522 ACLLIST *ac = al;
4523 do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) &&
4524 s && (*s++ == ' '))
4525 ac->rights = imap_parse_astring (stream,&s,reply,NIL);
4526 while (ac->rights && s && (*s == ' ') && s++ &&
4527 (ac = ac->next = mail_newacllist ()));
4528 if (!ac->rights || (s && *s)) {
4529 sprintf (LOCAL->tmp,"Invalid ACL identifier/rights for %.80s",
4530 (char *) t);
4531 mm_notify (stream,LOCAL->tmp,WARN);
4532 stream->unhealthy = T;
4534 else if (ar) (*ar) (stream,t,al);
4535 mail_free_acllist (&al); /* clean up */
4537 /* no optional rights */
4538 else if (ar) (*ar) (stream,t,NIL);
4539 fs_give ((void **) &t); /* free mailbox name */
4542 else if (LOCAL->authed && !strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) &&
4543 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4544 listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL);
4545 char *id,*r;
4546 if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){
4547 if (s && (*s++ == ' ') &&
4548 (r = imap_parse_astring (stream,&s,reply,NIL))) {
4549 if (s && (*s++ == ' ')) {
4550 STRINGLIST *rl = mail_newstringlist ();
4551 STRINGLIST *rc = rl;
4552 do rc->text.data = (unsigned char *)
4553 imap_parse_astring (stream,&s,reply,&rc->text.size);
4554 while (rc->text.data && s && (*s == ' ') && s++ &&
4555 (rc = rc->next = mail_newstringlist ()));
4556 if (!rc->text.data || (s && *s)) {
4557 sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s",
4558 (char *) t);
4559 mm_notify (stream,LOCAL->tmp,WARN);
4560 stream->unhealthy = T;
4562 else if (lr) (*lr) (stream,t,id,r,rl);
4563 /* clean up */
4564 mail_free_stringlist (&rl);
4566 /* no optional rights */
4567 else if (lr) (*lr) (stream,t,id,r,NIL);
4568 fs_give ((void **) &r); /* free rights */
4570 else {
4571 sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t);
4572 mm_notify (stream,LOCAL->tmp,WARN);
4573 stream->unhealthy = T;
4575 fs_give ((void **) &id); /* free identifier */
4577 else {
4578 sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifier for %.80s",(char *) t);
4579 mm_notify (stream,LOCAL->tmp,WARN);
4580 stream->unhealthy = T;
4582 fs_give ((void **) &t); /* free mailbox name */
4585 else if (LOCAL->authed
4586 && !strcmp (reply->key,"MYRIGHTS") && (s = reply->text) &&
4587 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4588 myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL);
4589 char *r;
4590 if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) {
4591 if (s && *s) {
4592 sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t);
4593 mm_notify (stream,LOCAL->tmp,WARN);
4594 stream->unhealthy = T;
4596 else if (mr) (*mr) (stream,t,r);
4597 fs_give ((void **) &r); /* free rights */
4599 else {
4600 sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t);
4601 mm_notify (stream,LOCAL->tmp,WARN);
4602 stream->unhealthy = T;
4604 fs_give ((void **) &t); /* free mailbox name */
4607 /* this response has a bizarre syntax! */
4608 else if (LOCAL->authed && !strcmp (reply->key,"QUOTA") && (s = reply->text) &&
4609 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4610 /* in case error */
4611 sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t);
4612 if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) {
4613 quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL);
4614 QUOTALIST *ql = NIL;
4615 QUOTALIST *qc;
4616 /* parse non-empty quota resource list */
4617 if (*s != ')') for (ql = qc = mail_newquotalist (); T;
4618 qc = qc->next = mail_newquotalist ()) {
4619 if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s &&
4620 (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) {
4621 if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10);
4622 else if ((t = strchr (s,' ')) != NULL) t = s;
4623 if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
4624 if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
4625 else if ((t = strpbrk (s," )")) != NULL) t = s;
4626 /* another resource follows? */
4627 if (*s == ' ') continue;
4628 /* end of resource list? */
4629 if ((*s == ')') && !s[1]) {
4630 if (qt) (*qt) (stream,t,ql);
4631 break; /* all done */
4635 /* something bad happened */
4636 mm_notify (stream,LOCAL->tmp,WARN);
4637 stream->unhealthy = T;
4638 break; /* parse failed */
4640 /* all done with quota resource list now */
4641 if (ql) mail_free_quotalist (&ql);
4643 else {
4644 mm_notify (stream,LOCAL->tmp,WARN);
4645 stream->unhealthy = T;
4647 fs_give ((void **) &t); /* free root name */
4649 else if (LOCAL->authed && !strcmp (reply->key,"ID") && (s = reply->text)){
4650 if(compare_cstring (s,"NIL")) LOCAL->id = imap_parse_idlist(s);
4652 else if (LOCAL->authed && !strcmp (reply->key,"QUOTAROOT") && (s = reply->text) &&
4653 (t = imap_parse_astring (stream,&s,reply,NIL))) {
4654 sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t);
4655 if (s && (*s++ == ' ')) {
4656 quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL);
4657 STRINGLIST *rl = mail_newstringlist ();
4658 STRINGLIST *rc = rl;
4659 do rc->text.data = (unsigned char *)
4660 imap_parse_astring (stream,&s,reply,&rc->text.size);
4661 while (rc->text.data && *s && (*s++ == ' ') &&
4662 (rc = rc->next = mail_newstringlist ()));
4663 if (!rc->text.data || (s && *s)) {
4664 mm_notify (stream,LOCAL->tmp,WARN);
4665 stream->unhealthy = T;
4667 else if (qr) (*qr) (stream,t,rl);
4668 /* clean up */
4669 mail_free_stringlist (&rl);
4671 else {
4672 mm_notify (stream,LOCAL->tmp,WARN);
4673 stream->unhealthy = T;
4675 fs_give ((void **) &t);
4678 else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
4679 imap_parse_response (stream,reply->text,NIL,T);
4680 else if (!strcmp (reply->key,"NO"))
4681 imap_parse_response (stream,reply->text,WARN,T);
4682 else if (!strcmp (reply->key,"BAD"))
4683 imap_parse_response (stream,reply->text,ERROR,T);
4684 else if (!strcmp (reply->key,"BYE")) {
4685 LOCAL->byeseen = T; /* note that a BYE seen */
4686 imap_parse_response (stream,reply->text,BYE,T);
4688 else if (!strcmp (reply->key,"CAPABILITY") && reply->text)
4689 imap_parse_capabilities (stream,reply->text);
4690 else if (LOCAL->authed && !strcmp (reply->key,"MAILBOX") && reply->text) {
4691 if (LOCAL->prefix &&
4692 ((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN))
4693 sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text);
4694 else t = reply->text;
4695 mm_list (stream,NIL,t,NIL);
4697 else {
4698 sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
4699 (char *) reply->key);
4700 mm_notify (stream,LOCAL->tmp,WARN);
4701 stream->unhealthy = T;
4705 /* Parse human-readable response text
4706 * Accepts: mail stream
4707 * text
4708 * error level for mm_notify()
4709 * non-NIL if want mm_notify() event even if no response code
4712 void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy)
4714 char *s,*t,*r;
4715 size_t i;
4716 unsigned long j;
4717 MESSAGECACHE *elt;
4718 copyuid_t cu;
4719 appenduid_t au;
4720 SEARCHSET *source = NIL;
4721 SEARCHSET *dest = NIL;
4722 if (text && (*text == '[') && (t = strchr (s = text + 1,']')) &&
4723 ((i = t - s) < IMAPTMPLEN)) {
4724 LOCAL->tmp[i] = '\0'; /* make mungable copy of text code */
4725 if ((s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) != NULL) *s++ = '\0';
4726 if (s) { /* have argument? */
4727 ntfy = NIL; /* suppress mm_notify if normal SELECT data */
4728 if (!compare_cstring (t,"CAPABILITY")) imap_parse_capabilities(stream,s);
4729 else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') &&
4730 (t[i-1] == ')')) {
4731 t[i-1] = '\0'; /* tie off flags */
4732 stream->perm_seen = stream->perm_deleted = stream->perm_answered =
4733 stream->perm_draft = stream->kwd_create = NIL;
4734 stream->perm_user_flags = NIL;
4735 if ((s = strtok_r (s+1," ",&r)) != NULL) do {
4736 if (*s == '\\') { /* system flags */
4737 if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T;
4738 else if (!compare_cstring (s,"\\Deleted"))
4739 stream->perm_deleted = T;
4740 else if (!compare_cstring (s,"\\Flagged"))
4741 stream->perm_flagged = T;
4742 else if (!compare_cstring (s,"\\Answered"))
4743 stream->perm_answered = T;
4744 else if (!compare_cstring (s,"\\Draft")) stream->perm_draft = T;
4745 else if (!strcmp (s,"\\*")) stream->kwd_create = T;
4747 else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
4749 while ((s = strtok_r (NIL," ",&r)) != NULL);
4752 else if (!compare_cstring (t,"UIDVALIDITY") && (j = strtoul (s,NIL,10))){
4753 /* do this in separate if because of ntfy */
4754 if (j != stream->uid_validity) {
4755 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
4756 stream->uid_validity = j;
4757 /* purge any UIDs in cache */
4758 for (j = 1; j <= stream->nmsgs; j++)
4759 if ((elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT)) != NULL)
4760 elt->private.uid = 0;
4763 else if (!compare_cstring (t,"UIDNEXT"))
4764 stream->uid_last = strtoul (s,NIL,10) - 1;
4765 else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) &&
4766 !compare_cstring (t,"COPYUID") &&
4767 (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) &&
4768 isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4769 (source = mail_parse_set (s,&s)) && (*s++ == ' ') &&
4770 (dest = mail_parse_set (s,&s)) && !*s)
4771 (*cu) (stream,LOCAL->appendmailbox,j,source,dest);
4772 else if (j && !compare_cstring (t,"APPENDUID") &&
4773 (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) &&
4774 isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
4775 (dest = mail_parse_set (s,&s)) && !*s)
4776 (*au) (LOCAL->appendmailbox,j,dest);
4777 else { /* all other response code events */
4778 ntfy = T; /* must mm_notify() */
4779 if (!compare_cstring (t,"REFERRAL"))
4780 LOCAL->referral = cpystr (t + 9);
4782 mail_free_searchset (&source);
4783 mail_free_searchset (&dest);
4785 else { /* no arguments */
4786 if (!compare_cstring (t,"UIDNOTSTICKY")) {
4787 ntfy = NIL;
4788 stream->uid_nosticky = T;
4790 else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T;
4791 else if (!compare_cstring (t,"READ-WRITE"))
4792 stream->rdonly = NIL;
4793 else if (!compare_cstring (t,"PARSE") && !errflg)
4794 errflg = PARSE;
4797 /* give event to main program */
4798 if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg);
4801 /* Parse a namespace
4802 * Accepts: mail stream
4803 * current text pointer
4804 * parsed reply
4805 * Returns: namespace list, text pointer updated
4808 NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
4809 IMAPPARSEDREPLY *reply)
4811 NAMESPACE *ret = NIL;
4812 NAMESPACE *nam = NIL;
4813 NAMESPACE *prev = NIL;
4814 PARAMETER *par = NIL;
4815 if (*txtptr) { /* only if argument given */
4816 /* ignore leading space */
4817 while (**txtptr == ' ') ++*txtptr;
4818 switch (**txtptr) {
4819 case 'N': /* if NIL */
4820 case 'n':
4821 ++*txtptr; /* bump past "N" */
4822 ++*txtptr; /* bump past "I" */
4823 ++*txtptr; /* bump past "L" */
4824 break;
4825 case '(':
4826 ++*txtptr; /* skip past open paren */
4827 while (**txtptr == '(') {
4828 ++*txtptr; /* skip past open paren */
4829 prev = nam; /* note previous if any */
4830 nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0,
4831 sizeof (NAMESPACE));
4832 if (!ret) ret = nam; /* if first time note first namespace */
4833 /* if previous link new block to it */
4834 if (prev) prev->next = nam;
4835 nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL);
4836 /* ignore whitespace */
4837 while (**txtptr == ' ') ++*txtptr;
4838 switch (**txtptr) { /* parse delimiter */
4839 case 'N':
4840 case 'n':
4841 *txtptr += 3; /* bump past "NIL" */
4842 break;
4843 case '"':
4844 if (*++*txtptr == '\\') nam->delimiter = *++*txtptr;
4845 else nam->delimiter = **txtptr;
4846 *txtptr += 2; /* bump past character and closing quote */
4847 break;
4848 default:
4849 sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s",
4850 (char *) *txtptr);
4851 mm_notify (stream,LOCAL->tmp,WARN);
4852 stream->unhealthy = T;
4853 *txtptr = NIL; /* stop parse */
4854 return ret;
4857 while (**txtptr == ' '){/* append new parameter to tail */
4858 if (nam->param) par = par->next = mail_newbody_parameter ();
4859 else nam->param = par = mail_newbody_parameter ();
4860 if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL,
4861 NIL,NIL))) {
4862 mm_notify (stream,"Missing namespace extension attribute",WARN);
4863 stream->unhealthy = T;
4864 par->attribute = cpystr ("UNKNOWN");
4866 /* skip space */
4867 while (**txtptr == ' ') ++*txtptr;
4868 if (**txtptr == '(') {/* have value list? */
4869 char *att = par->attribute;
4870 ++*txtptr; /* yes */
4871 do { /* parse each value */
4872 if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,
4873 NIL,LONGT))) {
4874 sprintf (LOCAL->tmp,
4875 "Missing value for namespace attribute %.80s",att);
4876 mm_notify (stream,LOCAL->tmp,WARN);
4877 stream->unhealthy = T;
4878 par->value = cpystr ("UNKNOWN");
4880 /* is there another value? */
4881 if (**txtptr == ' ') par = par->next = mail_newbody_parameter ();
4882 } while (!par->value);
4884 else {
4885 sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s",
4886 par->attribute);
4887 mm_notify (stream,LOCAL->tmp,WARN);
4888 stream->unhealthy = T;
4889 par->value = cpystr ("UNKNOWN");
4892 if (**txtptr == ')') ++*txtptr;
4893 else { /* missing trailing paren */
4894 sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s",
4895 (char *) *txtptr);
4896 mm_notify (stream,LOCAL->tmp,WARN);
4897 stream->unhealthy = T;
4898 return ret;
4901 if (**txtptr == ')') { /* expected trailing paren? */
4902 ++*txtptr; /* got it! */
4903 break;
4905 default:
4906 sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr);
4907 mm_notify (stream,LOCAL->tmp,WARN);
4908 stream->unhealthy = T;
4909 *txtptr = NIL; /* stop parse now */
4910 break;
4913 return ret;
4916 /* Parse a thread node list
4917 * Accepts: mail stream
4918 * current text pointer
4919 * Returns: thread node list, text pointer updated
4922 THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr)
4924 char *s;
4925 THREADNODE *ret = NIL; /* returned tree */
4926 THREADNODE *last = NIL; /* last branch in this tree */
4927 THREADNODE *parent = NIL; /* parent of current node */
4928 THREADNODE *cur; /* current node */
4929 while (**txtptr == '(') { /* see a thread? */
4930 ++*txtptr; /* skip past open paren */
4931 while (**txtptr != ')') { /* parse thread */
4932 if (**txtptr == '(') { /* thread branch */
4933 cur = imap_parse_thread (stream,txtptr);
4934 /* add to parent */
4935 if (parent) parent = parent->next = cur;
4936 else { /* no parent, create dummy */
4937 if (last) last = last->branch = mail_newthreadnode (NIL);
4938 /* new tree */
4939 else ret = last = mail_newthreadnode (NIL);
4940 /* add to dummy parent */
4941 last->next = parent = cur;
4944 /* threaded message number */
4945 else if (isdigit (*(s = *txtptr)) &&
4946 ((cur = mail_newthreadnode (NIL))->num =
4947 strtoul (*txtptr,(char **) txtptr,10))) {
4948 if (LOCAL->filter && !mail_elt (stream,cur->num)->searched)
4949 cur->num = NIL; /* make dummy if filtering and not searched */
4950 /* add to parent */
4951 if (parent) parent = parent->next = cur;
4952 /* no parent, start new thread */
4953 else if (last) last = last->branch = parent = cur;
4954 /* create new tree */
4955 else ret = last = parent = cur;
4957 else { /* anything else is a bogon */
4958 char tmp[MAILTMPLEN];
4959 sprintf (tmp,"Bogus thread member: %.80s",s);
4960 mm_notify (stream,tmp,WARN);
4961 stream->unhealthy = T;
4962 return ret;
4964 /* skip past any space */
4965 if (**txtptr == ' ') ++*txtptr;
4967 ++*txtptr; /* skip past end of thread */
4968 parent = NIL; /* close this thread */
4970 return ret; /* return parsed thread */
4973 /* Parse RFC822 message header
4974 * Accepts: MAIL stream
4975 * envelope to parse into
4976 * header as sized text
4977 * stringlist if partial header
4980 void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
4981 STRINGLIST *stl)
4983 ENVELOPE *nenv;
4984 /* parse what we can from this header */
4985 rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL,
4986 net_host (LOCAL->netstream),stream->dtb->flags);
4987 if (*env) { /* need to merge this header into envelope? */
4988 if (!(*env)->newsgroups) { /* need Newsgroups? */
4989 (*env)->newsgroups = nenv->newsgroups;
4990 (*env)->ngpathexists = nenv->ngpathexists;
4991 nenv->newsgroups = NIL;
4993 if (!(*env)->followup_to) { /* need Followup-To? */
4994 (*env)->followup_to = nenv->followup_to;
4995 nenv->followup_to = NIL;
4997 if (!(*env)->references) { /* need References? */
4998 (*env)->references = nenv->references;
4999 nenv->references = NIL;
5001 if (!(*env)->sparep) { /* need spare pointer? */
5002 (*env)->sparep = nenv->sparep;
5003 nenv->sparep = NIL;
5005 mail_free_envelope (&nenv);
5006 (*env)->imapenvonly = NIL; /* have complete envelope now */
5008 /* otherwise set it to this envelope */
5009 else (*env = nenv)->incomplete = stl ? T : NIL;
5012 /* IMAP parse envelope
5013 * Accepts: MAIL stream
5014 * pointer to envelope pointer
5015 * current text pointer
5016 * parsed reply
5018 * Updates text pointer
5021 void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
5022 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5024 ENVELOPE *oenv = *env;
5025 char c = **txtptr; /* grab first character */
5026 /* ignore leading spaces */
5027 while (c == ' ') c = *++*txtptr;
5028 if (c) ++*txtptr; /* skip past first character */
5029 switch (c) { /* dispatch on first character */
5030 case '(': /* if envelope S-expression */
5031 *env = mail_newenvelope (); /* parse the new envelope */
5032 (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5033 (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5034 (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
5035 (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
5036 (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
5037 (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
5038 (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
5039 (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
5040 (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5041 LONGT);
5042 (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5043 if (oenv) { /* need to merge old envelope? */
5044 (*env)->newsgroups = oenv->newsgroups;
5045 oenv->newsgroups = NIL;
5046 (*env)->ngpathexists = oenv->ngpathexists;
5047 (*env)->followup_to = oenv->followup_to;
5048 oenv->followup_to = NIL;
5049 (*env)->references = oenv->references;
5050 oenv->references = NIL;
5051 mail_free_envelope(&oenv);/* free old envelope */
5053 /* have IMAP envelope components only */
5054 else (*env)->imapenvonly = T;
5055 if (**txtptr != ')') {
5056 sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr);
5057 mm_notify (stream,LOCAL->tmp,WARN);
5058 stream->unhealthy = T;
5060 else ++*txtptr; /* skip past delimiter */
5061 break;
5062 case 'N': /* if NIL */
5063 case 'n':
5064 ++*txtptr; /* bump past "I" */
5065 ++*txtptr; /* bump past "L" */
5066 break;
5067 default:
5068 sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr);
5069 mm_notify (stream,LOCAL->tmp,WARN);
5070 stream->unhealthy = T;
5071 break;
5075 /* IMAP parse address list
5076 * Accepts: MAIL stream
5077 * current text pointer
5078 * parsed reply
5079 * Returns: address list, NIL on failure
5081 * Updates text pointer
5084 ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
5085 IMAPPARSEDREPLY *reply)
5087 ADDRESS *adr = NIL;
5088 char c = **txtptr; /* sniff at first character */
5089 /* ignore leading spaces */
5090 while (c == ' ') c = *++*txtptr;
5091 if (c) ++*txtptr; /* skip past open paren */
5092 switch (c) {
5093 case '(': /* if envelope S-expression */
5094 adr = imap_parse_address (stream,txtptr,reply);
5095 if (**txtptr != ')') {
5096 sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",
5097 (char *) *txtptr);
5098 mm_notify (stream,LOCAL->tmp,WARN);
5099 stream->unhealthy = T;
5101 else ++*txtptr; /* skip past delimiter */
5102 break;
5103 case 'N': /* if NIL */
5104 case 'n':
5105 ++*txtptr; /* bump past "I" */
5106 ++*txtptr; /* bump past "L" */
5107 break;
5108 default:
5109 sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
5110 mm_notify (stream,LOCAL->tmp,WARN);
5111 stream->unhealthy = T;
5112 break;
5114 return adr;
5117 /* IMAP parse address
5118 * Accepts: MAIL stream
5119 * current text pointer
5120 * parsed reply
5121 * Returns: address, NIL on failure
5123 * Updates text pointer
5126 ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
5127 IMAPPARSEDREPLY *reply)
5129 long ingroup = 0;
5130 ADDRESS *adr = NIL;
5131 ADDRESS *ret = NIL;
5132 ADDRESS *prev = NIL;
5133 char c = **txtptr; /* sniff at first address character */
5134 switch (c) {
5135 case '(': /* if envelope S-expression */
5136 while (c == '(') { /* recursion dies on small stack machines */
5137 ++*txtptr; /* skip past open paren */
5138 if (adr) prev = adr; /* note previous if any */
5139 adr = mail_newaddr (); /* instantiate address and parse its fields */
5140 adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5141 adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5142 adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5143 adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5144 if (**txtptr != ')') { /* handle trailing paren */
5145 sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr);
5146 mm_notify (stream,LOCAL->tmp,WARN);
5147 stream->unhealthy = T;
5149 else ++*txtptr; /* skip past close paren */
5150 c = **txtptr; /* set up for while test */
5151 /* ignore leading spaces in front of next */
5152 while (c == ' ') c = *++*txtptr;
5154 if (!adr->mailbox) { /* end of group? */
5155 /* decrement group if all looks well */
5156 if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup;
5157 else {
5158 if (ingroup) { /* in a group? */
5159 sprintf (LOCAL->tmp,/* yes, must be bad syntax */
5160 "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
5161 adr->personal ? adr->personal : "",
5162 adr->adl ? adr->adl : "",
5163 adr->host ? adr->host : "");
5164 mm_notify (stream,LOCAL->tmp,WARN);
5166 else mm_notify (stream,"End of group encountered when not in group",
5167 WARN);
5168 stream->unhealthy = T;
5169 mail_free_address (&adr);
5170 adr = prev;
5171 prev = NIL;
5174 else if (!adr->host) { /* start of group? */
5175 if (adr->personal || adr->adl) {
5176 sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s",
5177 adr->personal ? adr->personal : "",
5178 adr->adl ? adr->adl : "");
5179 mm_notify (stream,LOCAL->tmp,WARN);
5180 stream->unhealthy = T;
5181 mail_free_address (&adr);
5182 adr = prev;
5183 prev = NIL;
5185 else ++ingroup; /* in a group now */
5187 if (adr) { /* good address */
5188 if (!ret) ret = adr; /* if first time note first adr */
5189 /* if previous link new block to it */
5190 if (prev) prev->next = adr;
5191 /* flush bogus personal name */
5192 if (LOCAL->loser && adr->personal && strchr (adr->personal,'@'))
5193 fs_give ((void **) &adr->personal);
5196 break;
5197 case 'N': /* if NIL */
5198 case 'n':
5199 *txtptr += 3; /* bump past NIL */
5200 break;
5201 default:
5202 sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
5203 mm_notify (stream,LOCAL->tmp,WARN);
5204 stream->unhealthy = T;
5205 break;
5207 return ret;
5210 /* IMAP parse flags
5211 * Accepts: current message cache
5212 * current text pointer
5214 * Updates text pointer
5217 void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
5218 unsigned char **txtptr)
5220 char *flag;
5221 char c = '\0';
5222 struct { /* old flags */
5223 unsigned int valid : 1;
5224 unsigned int seen : 1;
5225 unsigned int deleted : 1;
5226 unsigned int flagged : 1;
5227 unsigned int answered : 1;
5228 unsigned int draft : 1;
5229 unsigned long user_flags;
5230 } old;
5231 old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted;
5232 old.flagged = elt->flagged; old.answered = elt->answered;
5233 old.draft = elt->draft; old.user_flags = elt->user_flags;
5234 elt->valid = T; /* mark have valid flags now */
5235 elt->user_flags = NIL; /* zap old flag values */
5236 elt->seen = elt->deleted = elt->flagged = elt->answered = elt->draft =
5237 elt->recent = NIL;
5238 do { /* parse list of flags */
5239 /* point at a flag */
5240 while (*(flag = ++*txtptr) == ' ');
5241 /* scan for end of flag */
5242 while (**txtptr && (**txtptr != ' ') && (**txtptr != ')')) ++*txtptr;
5243 c = **txtptr; /* save delimiter */
5244 **txtptr = '\0'; /* tie off flag */
5245 if (!*flag) break; /* null flag */
5246 /* if starts with \ must be sys flag */
5247 else if (*flag == '\\') {
5248 if (!compare_cstring (flag,"\\Seen")) elt->seen = T;
5249 else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T;
5250 else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T;
5251 else if (!compare_cstring (flag,"\\Answered")) elt->answered = T;
5252 else if (!compare_cstring (flag,"\\Recent")) elt->recent = T;
5253 else if (!compare_cstring (flag,"\\Draft")) elt->draft = T;
5255 /* otherwise user flag */
5256 else elt->user_flags |= imap_parse_user_flag (stream,flag);
5257 } while (c && (c != ')'));
5258 if (c) ++*txtptr; /* bump past delimiter */
5259 else {
5260 mm_notify (stream,"Unterminated flags list",WARN);
5261 stream->unhealthy = T;
5263 if (!old.valid || (old.seen != elt->seen) ||
5264 (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
5265 (old.answered != elt->answered) || (old.draft != elt->draft) ||
5266 (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno);
5270 /* IMAP parse user flag
5271 * Accepts: MAIL stream
5272 * flag name
5273 * Returns: flag bit position
5276 unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
5278 long i;
5279 /* sniff through all user flags */
5280 for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
5281 if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i);
5282 return (unsigned long) 0; /* not found */
5285 /* IMAP parse atom-string
5286 * Accepts: MAIL stream
5287 * current text pointer
5288 * parsed reply
5289 * returned string length
5290 * Returns: string
5292 * Updates text pointer
5295 unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
5296 IMAPPARSEDREPLY *reply,unsigned long *len)
5298 unsigned long i;
5299 unsigned char c,*s,*ret;
5300 /* ignore leading spaces */
5301 for (c = **txtptr; c == ' '; c = *++*txtptr);
5302 switch (c) {
5303 case '"': /* quoted string? */
5304 case '{': /* literal? */
5305 ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL);
5306 break;
5307 default: /* must be atom */
5308 for (c = *(s = *txtptr); /* find end of atom */
5309 c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') &&
5310 (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80);
5311 c = *++*txtptr);
5312 if ((i = *txtptr - s) != 0L) { /* atom ends at atom_special */
5313 if (len) *len = i; /* return length of atom */
5314 ret = strncpy ((char *) fs_get (i + 1),s,i);
5315 ret[i] = '\0'; /* tie off string */
5317 else { /* no atom found */
5318 sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr);
5319 mm_notify (stream,LOCAL->tmp,WARN);
5320 stream->unhealthy = T;
5321 if (len) *len = 0;
5322 ret = NIL;
5324 break;
5326 return ret;
5329 /* IMAP parse string
5330 * Accepts: MAIL stream
5331 * current text pointer
5332 * parsed reply
5333 * mailgets data
5334 * returned string length
5335 * filter newline flag
5336 * Returns: string
5338 * Updates text pointer
5341 unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
5342 IMAPPARSEDREPLY *reply,GETS_DATA *md,
5343 unsigned long *len,long flags)
5345 char *st;
5346 char *string = NIL;
5347 unsigned long i,j,k;
5348 int bogon = NIL;
5349 unsigned char c = **txtptr; /* sniff at first character */
5350 mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
5351 readprogress_t rp =
5352 (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
5353 /* ignore leading spaces */
5354 while (c == ' ') c = *++*txtptr;
5355 if (c) st = ++*txtptr; /* remember start of string */
5356 switch (c) {
5357 case '"': /* if quoted string */
5358 i = 0; /* initial byte count */
5359 /* search for end of string */
5360 for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) {
5361 /* backslash quotes next character */
5362 if (c == '\\') c = *++*txtptr;
5363 /* CHAR8 not permitted in quoted string */
5364 if (!bogon && (bogon = (c & 0x80))) {
5365 sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x",
5366 (unsigned int) c);
5367 mm_notify (stream,LOCAL->tmp,WARN);
5368 stream->unhealthy = T;
5370 else if (!c) { /* NUL not permitted either */
5371 mm_notify (stream,"Unterminated quoted string",WARN);
5372 stream->unhealthy = T;
5373 if (len) *len = 0; /* punt, since may be at end of string */
5374 return NIL;
5377 ++*txtptr; /* bump past delimiter */
5378 string = (char *) fs_get ((size_t) i + 1);
5379 for (j = 0; j < i; j++) { /* copy the string */
5380 if (*st == '\\') ++st; /* quoted character */
5381 string[j] = *st++;
5383 string[j] = '\0'; /* tie off string */
5384 if (len) *len = i; /* set return value too */
5385 if (md && mg) { /* have special routine to slurp string? */
5386 STRING bs;
5387 if (md->first) { /* partial fetch? */
5388 md->first--; /* restore origin octet */
5389 md->last = i; /* number of octets that we got */
5391 INIT (&bs,mail_string,string,i);
5392 (*mg) (mail_read,&bs,i,md);
5394 break;
5396 case 'N': /* if NIL */
5397 case 'n':
5398 ++*txtptr; /* bump past "I" */
5399 ++*txtptr; /* bump past "L" */
5400 if (len) *len = 0;
5401 break;
5402 case '{': /* if literal string */
5403 if (!isdigit (**txtptr)) {
5404 sprintf (LOCAL->tmp,"Invalid server literal length %.80s",*txtptr);
5405 mm_notify (stream,LOCAL->tmp,WARN);
5406 stream->unhealthy = T; /* read and discard */
5407 i = 0;
5409 /* get size of string */
5410 else if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) {
5411 sprintf (LOCAL->tmp,"Absurd server literal length %lu",i);
5412 mm_notify (stream,LOCAL->tmp,WARN);
5413 stream->unhealthy = T; /* read and discard */
5414 for (j = IMAPTMPLEN - 1; i; i -= j) {
5415 if (j > i) j = i;
5416 net_getbuffer (LOCAL->netstream,j,LOCAL->tmp);
5419 if (len) *len = i; /* set return value */
5420 if (md && mg) { /* have special routine to slurp string? */
5421 if (md->first) { /* partial fetch? */
5422 md->first--; /* restore origin octet */
5423 md->last = i; /* number of octets that we got */
5425 else md->flags |= MG_COPY;/* otherwise flag need to copy */
5426 string = (*mg) (net_getbuffer,LOCAL->netstream,i,md);
5428 else { /* must slurp into free storage */
5429 string = (char *) fs_get ((size_t) i + 1);
5430 *string = '\0'; /* init in case getbuffer fails */
5431 /* get the literal */
5432 if (rp) for (k = 0; (j = min ((long) MAILTMPLEN,(long) i)) != 0L; i -= j) {
5433 net_getbuffer (LOCAL->netstream,j,string + k);
5434 (*rp) (md,k += j);
5436 else net_getbuffer (LOCAL->netstream,i,string);
5438 fs_give ((void **) &reply->line);
5439 if (flags && string) /* need to filter newlines? */
5440 for (st = string; (st = strpbrk (st,"\015\012\011")) != NULL; *st++ = ' ');
5441 /* get new reply text line */
5442 if (!(reply->line = net_getline (LOCAL->netstream)))
5443 reply->line = cpystr ("");
5444 if (stream->debug) mm_dlog (reply->line);
5445 *txtptr = reply->line; /* set text pointer to point at it */
5446 break;
5447 default:
5448 sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,(char *) *txtptr);
5449 mm_notify (stream,LOCAL->tmp,WARN);
5450 stream->unhealthy = T;
5451 if (len) *len = 0;
5452 break;
5454 return (unsigned char *) string;
5457 /* Register text in IMAP cache
5458 * Accepts: MAIL stream
5459 * message number
5460 * IMAP segment specifier
5461 * header string list (if a HEADER section specifier)
5462 * sized text to register
5463 * Returns: non-zero if cache non-empty
5466 long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg,
5467 STRINGLIST *stl,SIZEDTEXT *text)
5469 char *t,tmp[MAILTMPLEN];
5470 unsigned long i;
5471 BODY *b;
5472 SIZEDTEXT *ret;
5473 STRINGLIST *stc;
5474 MESSAGECACHE *elt = mail_elt (stream,msgno);
5475 /* top-level header never does mailgets */
5476 if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") ||
5477 !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) {
5478 ret = &elt->private.msg.header.text;
5479 if (text) { /* don't do this if no text */
5480 if (ret->data) fs_give ((void **) &ret->data);
5481 mail_free_stringlist (&elt->private.msg.lines);
5482 elt->private.msg.lines = stl;
5483 /* prevent cache reuse of .NOT */
5484 if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.'))
5485 for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5486 if (stream->scache) { /* short caching puts it in the stream */
5487 if (stream->msgno != msgno) {
5488 /* flush old stuff */
5489 mail_free_envelope (&stream->env);
5490 mail_free_body (&stream->body);
5491 stream->msgno = msgno;
5493 imap_parse_header (stream,&stream->env,text,stl);
5495 /* regular caching */
5496 else imap_parse_header (stream,&elt->private.msg.env,text,stl);
5499 /* top level text */
5500 else if (!strcmp (seg,"TEXT")) {
5501 ret = &elt->private.msg.text.text;
5502 if (text && ret->data) fs_give ((void **) &ret->data);
5504 else if (!*seg) { /* full message */
5505 ret = &elt->private.msg.full.text;
5506 if (text && ret->data) fs_give ((void **) &ret->data);
5509 else { /* nested, find non-contents specifier */
5510 for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++);
5511 if (*t) *t++ = '\0'; /* tie off section from data specifier */
5512 if (!(b = mail_body (stream,msgno,seg))) {
5513 sprintf (tmp,"Unknown section number: %.80s",seg);
5514 mm_notify (stream,tmp,WARN);
5515 stream->unhealthy = T;
5516 return NIL;
5518 if (*t) { /* if a non-numberic subpart */
5519 if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) &&
5520 (!strcmp (t,"HEADER") || !strcmp (t,"0") ||
5521 !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) {
5522 ret = &b->nested.msg->header.text;
5523 if (text) {
5524 if (ret->data) fs_give ((void **) &ret->data);
5525 mail_free_stringlist (&b->nested.msg->lines);
5526 b->nested.msg->lines = stl;
5527 /* prevent cache reuse of .NOT */
5528 if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.'))
5529 for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
5530 imap_parse_header (stream,&b->nested.msg->env,text,stl);
5533 else if (i && !strcmp (t,"TEXT")) {
5534 ret = &b->nested.msg->text.text;
5535 if (text && ret->data) fs_give ((void **) &ret->data);
5537 /* otherwise it must be MIME */
5538 else if (!strcmp (t,"MIME")) {
5539 ret = &b->mime.text;
5540 if (text && ret->data) fs_give ((void **) &ret->data);
5542 else {
5543 sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t);
5544 mm_notify (stream,tmp,WARN);
5545 stream->unhealthy = T;
5546 return NIL;
5549 else { /* ordinary contents */
5550 ret = &b->contents.text;
5551 if (text && ret->data) fs_give ((void **) &ret->data);
5554 if (text) { /* update cache if requested */
5555 ret->data = text->data;
5556 ret->size = text->size;
5558 return ret->data ? LONGT : NIL;
5561 /* IMAP parse body structure
5562 * Accepts: MAIL stream
5563 * body structure to write into
5564 * current text pointer
5565 * parsed reply
5567 * Updates text pointer
5570 void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
5571 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5573 int i;
5574 char *s;
5575 PART *part = NIL;
5576 char c = **txtptr; /* grab first character */
5577 /* ignore leading spaces */
5578 while (c == ' ') c = *++*txtptr;
5579 if (c) ++*txtptr; /* skip past first character */
5580 switch (c) { /* dispatch on first character */
5581 case '(': /* body structure list */
5582 if (**txtptr == '(') { /* multipart body? */
5583 body->type= TYPEMULTIPART;/* yes, set its type */
5584 do { /* instantiate new body part */
5585 if (part) part = part->next = mail_newbody_part ();
5586 else body->nested.part = part = mail_newbody_part ();
5587 /* parse it */
5588 imap_parse_body_structure (stream,&part->body,txtptr,reply);
5589 } while (**txtptr == '(');/* for each body part */
5590 if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL)
5591 ucase (body->subtype);
5592 else {
5593 mm_notify (stream,"Missing multipart subtype",WARN);
5594 stream->unhealthy = T;
5595 body->subtype = cpystr (rfc822_default_subtype (body->type));
5597 if (**txtptr == ' ') /* multipart parameters */
5598 body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5599 if (**txtptr == ' ') { /* disposition */
5600 imap_parse_disposition (stream,body,txtptr,reply);
5601 if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5603 if (**txtptr == ' ') { /* language */
5604 body->language = imap_parse_language (stream,txtptr,reply);
5605 if (LOCAL->cap.extlevel < BODYEXTLANG)
5606 LOCAL->cap.extlevel = BODYEXTLANG;
5608 if (**txtptr == ' ') { /* location */
5609 body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5610 if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5612 while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5613 if (**txtptr != ')') { /* validate ending */
5614 sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",
5615 (char *) *txtptr);
5616 mm_notify (stream,LOCAL->tmp,WARN);
5617 stream->unhealthy = T;
5619 else ++*txtptr; /* skip past delimiter */
5622 else { /* not multipart, parse type name */
5623 if (**txtptr == ')') { /* empty body? */
5624 ++*txtptr; /* bump past it */
5625 break; /* and punt */
5627 body->type = TYPEOTHER; /* assume unknown type */
5628 body->encoding = ENCOTHER;/* and unknown encoding */
5629 /* parse type */
5630 if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) {
5631 ucase (s); /* application always gets uppercase form */
5632 for (i = 0; /* look in existing table */
5633 (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++);
5634 if (i <= TYPEMAX) { /* only if found a slot */
5635 body->type = i; /* set body type */
5636 if (!body_types[i]) { /* assign empty slot */
5637 body_types[i] = s;
5638 s = NIL; /* don't free this string */
5641 if (s) fs_give ((void **) &s);
5643 if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL)
5644 ucase (body->subtype); /* parse subtype */
5645 else {
5646 mm_notify (stream,"Missing body subtype",WARN);
5647 stream->unhealthy = T;
5648 body->subtype = cpystr (rfc822_default_subtype (body->type));
5650 body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
5651 body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5652 body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5653 LONGT);
5654 if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) {
5655 ucase (s); /* application always gets uppercase form */
5656 for (i = 0; /* search for body encoding */
5657 (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]);
5658 i++);
5659 if (i > ENCMAX) body->encoding = ENCOTHER;
5660 else { /* only if found a slot */
5661 body->encoding = i; /* set body encoding */
5662 /* assign empty slot */
5663 if (!body_encodings[i]) {
5664 body_encodings[i] = s;
5665 s = NIL; /* don't free this string */
5668 if (s) fs_give ((void **) &s);
5670 \f /* parse size of contents in bytes */
5671 body->size.bytes = strtoul (*txtptr,(char **) txtptr,10);
5672 switch (body->type) { /* possible extra stuff */
5673 case TYPEMESSAGE: /* message envelope and body */
5674 /* non MESSAGE/RFC822 is basic type */
5675 if (strcmp (body->subtype,"RFC822")) break;
5676 { /* make certain server sends an envelope */
5677 ENVELOPE *env = NIL;
5678 imap_parse_envelope (stream,&env,txtptr,reply);
5679 if (!env) {
5680 mm_notify (stream,"Missing body message envelope",WARN);
5681 stream->unhealthy = T;
5682 fs_give ((void **) &body->subtype);
5683 body->subtype = cpystr ("RFC822_MISSING_ENVELOPE");
5684 break;
5686 (body->nested.msg = mail_newmsg ())->env = env;
5688 body->nested.msg->body = mail_newbody ();
5689 imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply);
5690 /* drop into text case */
5691 case TYPETEXT: /* size in lines */
5692 body->size.lines = strtoul (*txtptr,(char **) txtptr,10);
5693 break;
5694 default: /* otherwise nothing special */
5695 break;
5698 if (**txtptr == ' ') { /* extension data - md5 */
5699 body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5700 if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5;
5702 if (**txtptr == ' ') { /* disposition */
5703 imap_parse_disposition (stream,body,txtptr,reply);
5704 if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
5706 if (**txtptr == ' ') { /* language */
5707 body->language = imap_parse_language (stream,txtptr,reply);
5708 if (LOCAL->cap.extlevel < BODYEXTLANG)
5709 LOCAL->cap.extlevel = BODYEXTLANG;
5711 if (**txtptr == ' ') { /* location */
5712 body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
5713 if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
5715 while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
5716 if (**txtptr != ')') { /* validate ending */
5717 sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",
5718 (char *) *txtptr);
5719 mm_notify (stream,LOCAL->tmp,WARN);
5720 stream->unhealthy = T;
5722 else ++*txtptr; /* skip past delimiter */
5724 break;
5725 case 'N': /* if NIL */
5726 case 'n':
5727 ++*txtptr; /* bump past "I" */
5728 ++*txtptr; /* bump past "L" */
5729 break;
5730 default: /* otherwise quite bogus */
5731 sprintf (LOCAL->tmp,"Bogus body structure: %.80s",(char *) *txtptr);
5732 mm_notify (stream,LOCAL->tmp,WARN);
5733 stream->unhealthy = T;
5734 break;
5738 /* IMAP parse body parameter
5739 * Accepts: MAIL stream
5740 * current text pointer
5741 * parsed reply
5742 * Returns: body parameter
5743 * Updates text pointer
5746 PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
5747 unsigned char **txtptr,
5748 IMAPPARSEDREPLY *reply)
5750 PARAMETER *ret = NIL;
5751 PARAMETER *par = NIL;
5752 char c,*s;
5753 /* ignore leading spaces */
5754 while ((c = *(*txtptr)++) == ' ');
5755 if (c == '(') do { /* parse parameter list */
5756 /* append new parameter to tail */
5757 if (ret) par = par->next = mail_newbody_parameter ();
5758 else ret = par = mail_newbody_parameter ();
5759 if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL,
5760 LONGT))) {
5761 mm_notify (stream,"Missing parameter attribute",WARN);
5762 stream->unhealthy = T;
5763 par->attribute = cpystr ("UNKNOWN");
5765 if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){
5766 sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
5767 mm_notify (stream,LOCAL->tmp,WARN);
5768 stream->unhealthy = T;
5769 par->value = cpystr ("UNKNOWN");
5771 switch (c = **txtptr) { /* see what comes after */
5772 case ' ': /* flush whitespace */
5773 while ((c = *++*txtptr) == ' ');
5774 break;
5775 case ')': /* end of attribute/value pairs */
5776 ++*txtptr; /* skip past closing paren */
5777 break;
5778 case '\0':
5779 mm_notify (stream,"Unterminated parameter list", WARN);
5780 stream->unhealthy = T;
5781 break;
5782 default:
5783 sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
5784 mm_notify (stream,LOCAL->tmp,WARN);
5785 stream->unhealthy = T;
5786 break;
5788 } while (c && (c != ')'));
5789 /* empty parameter, must be NIL */
5790 else if (((c == 'N') || (c == 'n')) &&
5791 ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
5792 ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
5793 else {
5794 sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,
5795 (char *) (*txtptr) - 1);
5796 mm_notify (stream,LOCAL->tmp,WARN);
5797 stream->unhealthy = T;
5799 return ret;
5802 /* IMAP parse body disposition
5803 * Accepts: MAIL stream
5804 * body structure to write into
5805 * current text pointer
5806 * parsed reply
5809 void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
5810 unsigned char **txtptr,IMAPPARSEDREPLY *reply)
5812 switch (*++*txtptr) {
5813 case '(':
5814 ++*txtptr; /* skip open paren */
5815 body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL,
5816 LONGT);
5817 body->disposition.parameter =
5818 imap_parse_body_parameter (stream,txtptr,reply);
5819 if (**txtptr != ')') { /* validate ending */
5820 sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",
5821 (char *) *txtptr);
5822 mm_notify (stream,LOCAL->tmp,WARN);
5823 stream->unhealthy = T;
5825 else ++*txtptr; /* skip past delimiter */
5826 break;
5827 case 'N': /* if NIL */
5828 case 'n':
5829 ++*txtptr; /* bump past "N" */
5830 ++*txtptr; /* bump past "I" */
5831 ++*txtptr; /* bump past "L" */
5832 break;
5833 default:
5834 sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",(char *) *txtptr);
5835 mm_notify (stream,LOCAL->tmp,WARN);
5836 stream->unhealthy = T;
5837 /* try to skip to next space */
5838 while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')'));
5839 break;
5843 /* IMAP parse body language
5844 * Accepts: MAIL stream
5845 * current text pointer
5846 * parsed reply
5847 * Returns: string list or NIL if empty or error
5850 STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
5851 IMAPPARSEDREPLY *reply)
5853 unsigned long i;
5854 char *s;
5855 STRINGLIST *ret = NIL;
5856 /* language is a list */
5857 if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
5858 else if ((s = imap_parse_string (stream,txtptr,reply,NIL,&i,LONGT)) != NULL) {
5859 (ret = mail_newstringlist ())->text.data = (unsigned char *) s;
5860 ret->text.size = i;
5862 return ret;
5865 /* IMAP parse string list
5866 * Accepts: MAIL stream
5867 * current text pointer
5868 * parsed reply
5869 * Returns: string list or NIL if empty or error
5872 STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
5873 IMAPPARSEDREPLY *reply)
5875 STRINGLIST *stl = NIL;
5876 STRINGLIST *stc = NIL;
5877 unsigned char *t = *txtptr;
5878 /* parse the list */
5879 if (*t++ == '(') while (*t != ')') {
5880 if (stl) stc = stc->next = mail_newstringlist ();
5881 else stc = stl = mail_newstringlist ();
5882 /* parse astring */
5883 if (!(stc->text.data =
5884 imap_parse_astring (stream,&t,reply,&stc->text.size))) {
5885 sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t);
5886 mm_notify (stream,LOCAL->tmp,WARN);
5887 stream->unhealthy = T;
5888 mail_free_stringlist (&stl);
5889 break;
5891 else if (*t == ' ') ++t; /* another token follows */
5893 if (stl) *txtptr = ++t; /* update return string */
5894 return stl;
5897 /* IMAP parse unknown body extension data
5898 * Accepts: MAIL stream
5899 * current text pointer
5900 * parsed reply
5902 * Updates text pointer
5905 void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
5906 IMAPPARSEDREPLY *reply)
5908 unsigned long i,j;
5909 switch (*++*txtptr) { /* action depends upon first character */
5910 case '(':
5911 while (**txtptr && (**txtptr != ')'))
5912 imap_parse_extension (stream,txtptr,reply);
5913 if (**txtptr) ++*txtptr; /* bump past closing parenthesis */
5914 break;
5915 case '"': /* if quoted string */
5916 while ((*++*txtptr != '"') && **txtptr) if (**txtptr == '\\') ++*txtptr;
5917 if (**txtptr) ++*txtptr; /* bump past closing quote */
5918 break;
5919 case 'N': /* if NIL */
5920 case 'n':
5921 ++*txtptr; /* bump past "N" */
5922 ++*txtptr; /* bump past "I" */
5923 ++*txtptr; /* bump past "L" */
5924 break;
5925 case '{': /* get size of literal */
5926 ++*txtptr; /* bump past open squiggle */
5927 if ((i = strtoul (*txtptr,(char **) txtptr,10)) != 0L) do
5928 net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
5929 LOCAL->tmp);
5930 while (i -= j);
5931 /* get new reply text line */
5932 if (!(reply->line = net_getline (LOCAL->netstream)))
5933 reply->line = cpystr ("");
5934 if (stream->debug) mm_dlog (reply->line);
5935 *txtptr = reply->line; /* set text pointer to point at it */
5936 break;
5937 case '0': case '1': case '2': case '3': case '4':
5938 case '5': case '6': case '7': case '8': case '9':
5939 strtoul (*txtptr,(char **) txtptr,10);
5940 break;
5941 default:
5942 sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr);
5943 mm_notify (stream,LOCAL->tmp,WARN);
5944 stream->unhealthy = T;
5945 /* try to skip to next space */
5946 while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')'));
5947 break;
5951 /* IMAP parse capabilities
5952 * Accepts: MAIL stream
5953 * capability list
5956 void imap_parse_capabilities (MAILSTREAM *stream,char *t)
5958 char *s,*r;
5959 unsigned long i;
5960 THREADER *thr,*th;
5961 if (!LOCAL->gotcapability) { /* need to save previous capabilities? */
5962 /* no, flush threaders */
5963 if ((thr = LOCAL->cap.threader) != NULL) while ((th = thr) != NULL) {
5964 fs_give ((void **) &th->name);
5965 thr = th->next;
5966 fs_give ((void **) &th);
5968 /* zap capabilities */
5969 memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
5970 LOCAL->gotcapability = T; /* flag that capabilities arrived */
5972 for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) {
5973 if (!compare_cstring (t,"IMAP4"))
5974 LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5975 else if (!compare_cstring (t,"IMAP4rev1"))
5976 LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5977 else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T;
5978 else if (!compare_cstring (t,"IMAP2bis"))
5979 LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
5980 else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T;
5981 else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T;
5982 else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T;
5983 else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T;
5984 else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T;
5985 else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T;
5986 else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T;
5987 else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T;
5988 else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T;
5989 else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T;
5990 else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T;
5991 else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T;
5992 else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T;
5993 else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T;
5994 else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T;
5995 else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T;
5996 else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T;
5997 else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T;
5998 else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T;
5999 else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T;
6000 else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T;
6001 else if (!compare_cstring (t,"X-GM-EXT-1")) LOCAL->cap.x_gm_ext1 = T;
6002 else if (((t[0] == 'S') || (t[0] == 's')) &&
6003 ((t[1] == 'O') || (t[1] == 'o')) &&
6004 ((t[2] == 'R') || (t[2] == 'r')) &&
6005 ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T;
6006 /* capability with value? */
6007 else if ((s = strchr (t,'=')) != NULL) {
6008 *s++ = '\0'; /* separate token from value */
6009 if (!compare_cstring (t,"THREAD") && !LOCAL->loser) {
6010 THREADER *thread = (THREADER *) fs_get (sizeof (THREADER));
6011 thread->name = cpystr (s);
6012 thread->dispatch = NIL;
6013 thread->next = LOCAL->cap.threader;
6014 LOCAL->cap.threader = thread;
6016 else if (!compare_cstring (t,"AUTH")) {
6017 if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) &&
6018 (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i);
6019 else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T;
6022 /* ignore other capabilities */
6024 /* disable LOGIN if PLAIN also advertised */
6025 if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) &&
6026 (LOCAL->cap.auth & (1 << i)) &&
6027 (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS))
6028 LOCAL->cap.auth &= ~(1 << i);
6031 /* IMAP load cache
6032 * Accepts: MAIL stream
6033 * sequence
6034 * flags
6035 * Returns: parsed reply from fetch
6038 IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags)
6040 int i = 2;
6041 char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ?
6042 "UID FETCH" : "FETCH";
6043 IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl;
6044 if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
6045 flags & FT_UID);
6046 args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence;
6047 args[1] = &aarg; aarg.type = ATOM;
6048 aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
6049 ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel];
6050 axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
6051 ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer;
6052 abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
6053 atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
6054 if (LEVELIMAP4 (stream)) { /* include UID if IMAP4 or IMAP4rev1 */
6055 aarg.text = (void *) "(UID";
6056 if (flags & FT_NEEDENV) { /* if need envelopes */
6057 args[i++] = &aenv; /* include envelope */
6058 /* extra header poop if IMAP4rev1 */
6059 if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
6060 args[i++] = &ahhr; /* header header */
6061 if (axtr.text) args[i++] = &axtr;
6062 args[i++] = &ahtr; /* header trailer */
6064 /* fetch body if requested */
6065 if (flags & FT_NEEDBODY) args[i++] = &abdy;
6067 args[i++] = &atrl; /* fetch trailer */
6069 /* easy if IMAP2 */
6070 else aarg.text = (void *) (flags & FT_NEEDENV) ?
6071 ((flags & FT_NEEDBODY) ?
6072 "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
6073 "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
6074 args[i] = NIL; /* tie off command */
6075 return imap_send (stream,cmd,args);
6078 /* Reform sequence for losing server that doesn't handle ranges right
6079 * Accepts: MAIL stream
6080 * sequence
6081 * non-zero if UID
6082 * Returns: sequence
6085 char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags)
6087 unsigned long i,j,star;
6088 char *s,*t,*tl,*rs;
6089 /* can't win if empty */
6090 if (!stream->nmsgs) return sequence;
6091 /* get highest possible range value */
6092 star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs;
6093 /* flush old reformed sequence */
6094 if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
6095 rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence));
6096 for (s = sequence; (t = strpbrk (s,",:")) != NULL; ) switch (*t++) {
6097 case ',': /* single message */
6098 strncpy (rs,s,i = t - s); /* copy string up to that point */
6099 rs += i; /* advance destination pointer */
6100 s += i; /* and source */
6101 break;
6102 case ':': /* message range */
6103 i = (*s == '*') ? star : strtoul (s,NIL,10);
6104 if (*t == '*') { /* range ends with star */
6105 j = star;
6106 tl = t+1;
6108 else { /* numeric range end */
6109 j = strtoul (t,(char **) &tl,10);
6110 if (!tl) tl = t + strlen (t);
6112 if (i <= j) { /* if first less than second */
6113 if (*tl) tl++; /* skip past end of range if present */
6114 strncpy (rs,s,i = tl - s);/* copy string up to that point */
6115 rs += i; /* advance destination and source pointers */
6116 s += i;
6118 else { /* here's the workaround for losing servers */
6119 strncpy (rs,t,i = tl - t);/* swap the order */
6120 rs[i] = ':'; /* delimit */
6121 strncpy (rs+i+1,s,j = (t-1) - s);
6122 rs += i + 1 + j; /* advance destination pointer */
6123 if (*tl) *rs++ = *tl++; /* write trailing delimiter if present */
6124 s = tl; /* advance source pointer */
6127 if (*s) strcpy (rs,s); /* write remainder of sequence */
6128 else *rs = '\0'; /* tie off string */
6129 return LOCAL->reform;
6132 /* IMAP return host name
6133 * Accepts: MAIL stream
6134 * Returns: host name
6137 char *imap_host (MAILSTREAM *stream)
6139 if (stream->dtb != &imapdriver)
6140 fatal ("imap_host called on non-IMAP stream!");
6141 /* return host name on stream if open */
6142 return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) :
6143 ".NO-IMAP-CONNECTION.";
6147 /* IMAP return IMAP capability structure
6148 * Accepts: MAIL stream
6149 * Returns: IMAP capability structure
6152 IMAPCAP *imap_cap (MAILSTREAM *stream)
6154 if (stream->dtb != &imapdriver)
6155 fatal ("imap_cap called on non-IMAP stream!");
6156 return &LOCAL->cap; /* return capability structure */