2 * Copyright 2016-2022 Eduardo Chappa
4 * Last Edited: Jun 18, 2020 Eduardo Chappa <alpine.chappa@yandex.com>
7 /* ========================================================================
8 * Copyright 2008-2011 Mark Crispin
9 * ========================================================================
12 * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
14 * Author: Mark Crispin
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.
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 */
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 */
100 unsigned long uid
; /* last UID returned */
101 unsigned long msgno
; /* last msgno returned */
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 */
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 */
127 /* imap_send() argument types */
135 #define SEARCHPROGRAM 6
136 #define SORTPROGRAM 7
141 #define LISTMAILBOX 12
142 #define MULTIAPPEND 13
144 #define MULTIAPPENDREDO 15
149 typedef struct append_data
{
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
,
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
,
188 long imap_msgdata (MAILSTREAM
*stream
,unsigned long msgno
,char *section
,
189 unsigned long first
,unsigned long last
,STRINGLIST
*lines
,
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
,
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
,
231 IMAPPARSEDREPLY
*imap_send_slist (MAILSTREAM
*stream
,char *tag
,char *base
,
232 char **s
,char *name
,STRINGLIST
*list
,
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
,
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 */
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
;
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
) {
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;
379 ((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->cap
.threader
;
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
;
386 case SET_MAXLOGINTRIALS
:
387 imap_maxlogintrials
= (long) value
;
389 case GET_MAXLOGINTRIALS
:
390 value
= (void *) imap_maxlogintrials
;
393 imap_lookahead
= (long) value
;
396 value
= (void *) imap_lookahead
;
398 case SET_UIDLOOKAHEAD
:
399 imap_uidlookahead
= (long) value
;
401 case GET_UIDLOOKAHEAD
:
402 value
= (void *) imap_uidlookahead
;
406 imap_defaultport
= (long) value
;
409 value
= (void *) imap_defaultport
;
411 case SET_SSLIMAPPORT
:
412 imap_sslport
= (long) value
;
414 case GET_SSLIMAPPORT
:
415 value
= (void *) imap_sslport
;
418 imap_prefetch
= (long) value
;
421 value
= (void *) imap_prefetch
;
423 case SET_CLOSEONERROR
:
424 imap_closeonerror
= (long) value
;
426 case GET_CLOSEONERROR
:
427 value
= (void *) imap_closeonerror
;
429 case SET_IMAPENVELOPE
:
430 imap_envelope
= (imapenvelope_t
) value
;
432 case GET_IMAPENVELOPE
:
433 value
= (void *) imap_envelope
;
435 case SET_IMAPREFERRAL
:
436 imap_referral
= (imapreferral_t
) value
;
438 case GET_IMAPREFERRAL
:
439 value
= (void *) imap_referral
;
441 case SET_IMAPEXTRAHEADERS
:
442 imap_extrahdrs
= (char *) value
;
444 case GET_IMAPEXTRAHEADERS
:
445 value
= (void *) imap_extrahdrs
;
448 imap_tryssl
= (long) value
;
451 value
= (void *) imap_tryssl
;
453 case SET_FETCHLOOKAHEADLIMIT
:
454 imap_fetchlookaheadlimit
= (long) value
;
456 case GET_FETCHLOOKAHEADLIMIT
:
457 value
= (void *) imap_fetchlookaheadlimit
;
460 case SET_IDLETIMEOUT
:
461 fatal ("SET_IDLETIMEOUT not permitted");
462 case GET_IDLETIMEOUT
:
463 value
= (void *) IDLETIMEOUT
;
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
;
471 value
= NIL
; /* error case */
477 /* IMAP scan mailboxes
478 * Accepts: mail stream
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
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
508 void imap_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
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
538 void imap_list_work (MAILSTREAM
*stream
,char *cmd
,char *ref
,char *pat
,
541 MAILSTREAM
*st
= stream
;
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 */
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
);
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
);
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
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
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
;
693 char mbx
[MAILTMPLEN
],mbx2
[MAILTMPLEN
];
694 IMAPARG
*args
[3],ambx
,amb2
;
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
) {
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;
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
);
730 * Accepts: mail stream
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
];
743 MAILSTREAM
*tstream
= NIL
;
744 /* use given stream if (rev1 or halfopen) and
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
))))
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? */
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");
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
)) &&
771 (mbx
= (*ir
) (stream
,LOCAL
->referral
,REFSTATUS
)))
772 ret
= imap_status (NIL
,mbx
,flags
| (stream
->debug
? SA_DEBUG
: NIL
));
776 else if (imap_OK (stream
,imap_send (stream
,"EXAMINE",args
))) {
778 status
.flags
= flags
& ~ (SA_UIDNEXT
| SA_UIDVALIDITY
);
779 status
.messages
= stream
->nmsgs
;
780 status
.recent
= stream
->recent
;
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 */
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
;
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
];
824 IMAPPARSEDREPLY
*reply
= NIL
;
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\"",
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
876 if (stream
->anonymous
|| mb
.port
|| mb
.sslflag
|| mb
.tlsflag
)
877 reply
= (LOCAL
->netstream
= net_open (&mb
,NIL
,defprt
,ssld
,"*imaps",
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.
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
)) &&
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)) {
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
);
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
);
924 && stls
&& LOCAL
->cap
.starttls
&& !mb
.sslflag
&& !mb
.notlsflag
&& mb
.tlsflag
){
925 mm_log("STARTTLS not allowed on PREAUTH state. Closing Connection", ERROR
);
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
);
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
);
957 else if (mb
.tlsflag
) { /* user specified /starttls but can't do it */
958 mm_log ("Unable to negotiate TLS with this server",ERROR
);
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 */
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\"",
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
);
1049 if (!stream
->halfopen
) { /* wants to open a mailbox? */
1052 ambx
.type
= ASTRING
;
1053 ambx
.text
= (void *) mb
.mailbox
;
1054 args
[0] = &ambx
; args
[1] = NIL
;
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
);
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
1096 * Returns: parsed reply if success, else NIL
1099 IMAPPARSEDREPLY
*imap_rimap (MAILSTREAM
*stream
,char *service
,NETMBX
*mb
,
1100 char *usr
,char *tmp
)
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 */
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
) &&
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 */
1133 /* IMAP log in as anonymous
1134 * Accepts: stream to authenticate
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
) {
1146 char *broken
= "[CLOSED] IMAP connection broken (anonymous auth)";
1147 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
1149 sprintf (tmp
,"%s AUTHENTICATE ANONYMOUS",tag
);
1150 if (!imap_soutr (stream
,tmp
)) {
1151 mm_log (broken
,ERROR
);
1154 if (imap_challenge (stream
,&i
)) imap_response (stream
,NIL
,s
,strlen (s
));
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
,"*");
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
);
1179 /* IMAP authenticate
1180 * Accepts: stream to authenticate
1181 * parsed network mailbox structure
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
;
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
))
1205 if (lsterr
) { /* previous authenticator failed? */
1206 sprintf (tmp
,"Retrying using %s authentication after %.80s",
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
);
1218 fs_give ((void **) &lsterr
);
1219 delete_password(mb
, usr
);
1221 LOCAL
->saslcancel
= NIL
;
1222 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
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
);
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 */
1253 if (!trial
) { /* if main program requested cancellation */
1254 mm_log ("IMAP Authentication cancelled",ERROR
);
1255 delete_password(mb
, usr
);
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
);
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 */
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
;
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
;
1311 if(app_pwd
) fs_give((void **) &app_pwd
);
1313 mm_login (mb
,usr
, &app_pwd
,trial
++);
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 */
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
);
1341 /* Get challenge to authenticator in binary
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
];
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
);
1370 /* Send authenticator response in BASE64
1371 * Accepts: MAIL stream
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
;
1382 if (response
) { /* make CRLFless BASE64 string */
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
;
1391 for (s
= v
,j
= 0; j
< i
; j
++) if (s
[j
] > ' ') *u
++ = s
[j
];
1392 fs_give((void **) &v
);
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
);
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 */
1415 * Accepts: MAIL stream
1419 void imap_close (MAILSTREAM
*stream
,long options
)
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
);
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
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
);
1476 * Accepts: MAIL stream
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
,
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
)
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 */
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
);
1554 /* IMAP fetch structure
1555 * Accepts: MAIL stream
1556 * message # to fetch
1557 * pointer to return body
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
,
1567 unsigned long i
,j
,k
,x
;
1568 char *s
,seq
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
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 */
1603 elt
= mail_elt (stream
,msgno
);/* get cache pointer */
1604 if (stream
->scache
) { /* short caching? */
1605 env
= &stream
->env
; /* use temporaries on the stream */
1607 if (msgno
!= stream
->msgno
){/* flush old poop if a different message */
1608 mail_free_envelope (env
);
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? */
1623 for (k
= imap_fetchlookaheadlimit
;
1624 k
&& set
&& (((s
+= strlen (s
)) - seq
) < (MAXCOMMAND
- 30));
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
) &&
1655 ((msg
= &(mail_elt (stream
,i
)->private.msg
))->env
&&
1656 (!body
|| msg
->body
)));
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
);
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
=
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
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
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
,
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
) &&
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 */
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
;
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
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" :
1826 else if (!section
[0]) /* BODY[] becomes RFC822 */
1827 aatt
.text
= (void *) ((flags
& FT_PEEK
) ? "RFC822.PEEK" : "RFC822");
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)
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)
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 */
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
;
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")) {
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.
1932 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1935 return NIL
; /* can't do anything close either */
1938 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1940 mm_notify (stream
,tmp
,WARN
);
1943 sprintf(tmp
,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
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")) {
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
,
1986 (t
= (char *) text
.data
)[text
.size
] = '\0';
1987 imap_cache (stream
,msgno
,"1",NIL
,&text
);
1993 * Accepts: MAIL stream
1998 unsigned long imap_uid (MAILSTREAM
*stream
,unsigned long msgno
)
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
;
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
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
];
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
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
,
2094 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
2095 ascm
.type
= ATOM
; ascm
.text
= (void *)
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
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
;
2120 IMAPPARSEDREPLY
*reply
;
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
))
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
;
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
))
2201 else if (!imap_OK (stream
,reply
)) {
2202 mm_log (reply
->text
,ERROR
);
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 */
2245 /* IMAP sort messages
2246 * Accepts: mail stream
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 */
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
) {
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 */
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
);
2387 /* IMAP thread messages
2388 * Accepts: mail stream
2393 * Returns: thread node tree or NIL if error
2396 THREADNODE
*imap_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
2397 SEARCHPGM
*spg
,long flags
)
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
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
);
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
)
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
2510 * Returns: T if success, NIL if failure
2513 long imap_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
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
)) {
2530 char *t
= (char *) fs_get (IMAPTMPLEN
);
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
);
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
);
2560 /* IMAP copy message(s)
2561 * Accepts: MAIL stream
2563 * destination mailbox
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";
2573 IMAPPARSEDREPLY
*reply
;
2574 IMAPARG
*args
[3],aseq
,ambx
;
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
,
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
);
2602 /* IMAP mail append message from stringstruct
2603 * Accepts: MAIL stream
2604 * destination mailbox
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
;
2616 char tmp
[MAILTMPLEN
];
2617 long debug
= stream
? stream
->debug
: NIL
;
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
;
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
) &&
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
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
)
2680 IMAPARG
*args
[3],ambx
,amap
;
2681 IMAPPARSEDREPLY
*reply
;
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
);
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
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
)
2733 IMAPARG
*args
[5],ambx
,aflg
,adat
,amsg
;
2734 IMAPPARSEDREPLY
*reply
;
2735 char tmp
[MAILTMPLEN
];
2737 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2738 args
[i
= 0] = &ambx
;
2740 aflg
.type
= FLAGS
; aflg
.text
= (void *) flags
;
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
));
2757 amsg
.type
= LITERAL
; amsg
.text
= (void *) message
;
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
);
2769 /* IMAP garbage collect stream
2770 * Accepts: Mail stream
2771 * garbage collection flags
2774 void imap_gc (MAILSTREAM
*stream
,long gcflags
)
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
)
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;
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
)
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
);
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
;
2851 * Accepts: mail stream
2853 * authentication identifier
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
);
2870 * Accepts: mail stream
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
);
2887 * Accepts: mail stream
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
);
2901 * Accepts: mail stream
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
);
2918 * Accepts: mail stream
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
2936 * Returns: T on success, NIL on failure
2939 long imap_acl_work (MAILSTREAM
*stream
,char *command
,IMAPARG
*args
[])
2942 if (LEVELACL (stream
)) { /* send command */
2943 IMAPPARSEDREPLY
*reply
;
2944 if (imap_OK (stream
,reply
= imap_send (stream
,command
,args
)))
2946 else mm_log (reply
->text
,ERROR
);
2948 else mm_log ("ACL not available on this IMAP server",ERROR
);
2953 * Accepts: mail stream
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
)
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
)))
2970 else mm_log (reply
->text
,ERROR
);
2972 else mm_log ("Quota not available on this IMAP server",ERROR
);
2976 IDLIST
*imap_parse_idlist (char *text
)
2980 char tmp
[MAILTMPLEN
];
2982 if(text
== NULL
) return NULL
;
2983 for(s
= text
; *s
== ' '; s
++); /* move past spaces */
2987 for(t
= s
; *t
&& *t
!= '"'; t
++);
2989 ret
= fs_get(sizeof(IDLIST
));
2991 ret
->name
= cpystr(s
);
2993 for(s
= t
+1; *s
== ' '; s
++); /* move past spaces */
2995 for(t
= s
; *t
&& *t
!= '"'; t
++);
2998 ret
->value
= cpystr(s
);
3000 ret
->next
= imap_parse_idlist(t
);
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 */
3020 long imap_setid (MAILSTREAM
*stream
, IDLIST
*idlist
)
3023 if (LEVELID (stream
)) { /* send "ID (params)" */
3024 IMAPPARSEDREPLY
*reply
;
3025 IMAPARG
*args
[2],aqrt
;
3030 if(idlist
== NULL
) return ret
;
3031 for (list
= idlist
; list
!= NULL
; list
= list
->next
)
3032 len
+= strlen(list
->name
) + strlen(list
->value
) + 6;
3034 len
+= 1L; /* in case there is only one field */
3035 qroot
= fs_get(len
+1);
3036 memset((void *)&qroot
[0], 0, len
+1);
3038 for (list
= idlist
; list
!= NULL
; list
= list
->next
){
3039 sprintf(p
, " \"%s\" \"%s\"", list
->name
, list
->value
);
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
)))
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
);
3057 * Accepts: mail stream
3059 * Returns: T on success with data returned via callback, NIL on failure
3062 long imap_getquota (MAILSTREAM
*stream
,char *qroot
)
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
)))
3072 else mm_log (reply
->text
,ERROR
);
3074 else mm_log ("Quota not available on this IMAP server",ERROR
);
3079 /* IMAP get quota root
3080 * Accepts: mail stream
3082 * Returns: T on success with data returned via callback, NIL on failure
3085 long imap_getquotaroot (MAILSTREAM
*stream
,char *mailbox
)
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
)))
3095 else mm_log (reply
->text
,ERROR
);
3097 else mm_log ("Quota not available on this IMAP server",ERROR
);
3101 /* Internal routines */
3104 /* IMAP send command
3105 * Accepts: MAIL stream
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
;
3121 sendcommand_t sc
= (sendcommand_t
) mail_parameters (NIL
,GET_SENDCOMMAND
,NIL
);
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
++);
3146 case NUMBER
: /* number */
3147 sprintf (s
,"%lu",(unsigned long) arg
->text
);
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
++;
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
)
3163 case LITERAL
: /* literal, as a stringstruct */
3164 if ((reply
= imap_send_literal (stream
,tag
,&s
,arg
->text
)) != NULL
) return reply
;
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 */
3179 case SEARCHPROGRAM
: /* search program */
3180 if ((reply
= imap_send_spgm (stream
,tag
,CMDBASE
,&s
,arg
->text
,
3181 CMDBASE
+MAXCOMMAND
)) != NULL
)
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
) {
3191 for (t
= "DATE"; *t
; *s
++ = *t
++);
3194 for (t
= "ARRIVAL"; *t
; *s
++ = *t
++);
3197 for (t
= "FROM"; *t
; *s
++ = *t
++);
3200 for (t
= "SUBJECT"; *t
; *s
++ = *t
++);
3203 for (t
= "TO"; *t
; *s
++ = *t
++);
3206 for (t
= "CC"; *t
; *s
++ = *t
++);
3209 for (t
= "SIZE"; *t
; *s
++ = *t
++);
3212 fatal ("Unknown sort program function in imap_send()!");
3214 c
= ' '; /* prefix character for subsequent items */
3216 *s
++ = ')'; /* close list */
3219 case BODYTEXT
: /* body section */
3220 for (t
= "BODY["; *t
; *s
++ = *t
++);
3221 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3223 case BODYPEEK
: /* body section */
3224 for (t
= "BODY.PEEK["; *t
; *s
++ = *t
++);
3225 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3227 case BODYCLOSE
: /* close bracket and possible length */
3228 s
[-1] = ']'; /* no leading space */
3229 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3231 case SEQUENCE
: /* sequence */
3232 if ((i
= strlen (t
= (char *) arg
->text
)) <= (size_t) MAXCOMMAND
)
3233 while (*t
) *s
++ = *t
++; /* easy case */
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 */
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
;
3259 arg
->type
= SEQUENCE
; /* restore in case something cares */
3261 return reply
; /* return result */
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
)
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
)||
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
];
3287 if (!map
->date
|| mail_parse_date (&elt
,map
->date
)) {
3288 if ((t
= map
->flags
) != NULL
) { /* flags given? */
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
)
3306 /* get next message */
3307 if ((*map
->af
) (stream
,map
->data
,&map
->flags
,&map
->date
,
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
);
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
);
3331 c
= ' '; /* prefix character for subsequent strings */
3334 while ((list
= list
->next
) != NULL
);
3335 *s
++ = ')'; /* close list */
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 */
3347 /* IMAP send atom-string
3348 * Accepts: MAIL stream
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
)
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 */
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 */
3382 case '(': case ')': case '{': case ' ': case 0x7f:
3384 case '"': case '\\': /* quoted-specials (could work in IMAP4) */
3386 qflag
= T
; /* must use quoted string format */
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 */
3395 /* IMAP send literal
3396 * Accepts: MAIL stream
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
,
3406 IMAPPARSEDREPLY
*reply
;
3407 unsigned long i
= SIZE (st
);
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 */
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);
3439 (*st
->dtb
->next
) (st
); /* advance to next buffer's worth */
3441 return NIL
; /* success */
3444 #define ADD_STRING(X, Y) { \
3446 sprintf (u, (X), (Y)); \
3449 strncpy(t, u, remain); \
3450 t[remain-1] = '\0'; \
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
;
3466 size_t remain
= sizeof(s
), len
;
3470 args
[1] = args
[2] = args
[3] = NIL
;
3473 ADD_STRING(" %s", pgm
->x_gm_ext1
->text
.data
);
3475 #if 0 /* maybe later */
3477 ADD_STRING(" larger:%lu", pgm
->larger
);
3480 ADD_STRING(" smaller:%lu", pgm
->smaller
);
3483 ADD_STRING(" %s", "in:trash");
3486 ADD_STRING(" %s", "-in:trash");
3489 ADD_STRING(" %s", "in:drafts");
3492 ADD_STRING(" %s", "-in:drafts");
3495 ADD_STRING(" %s", "is:starred");
3498 ADD_STRING(" %s", "-is:starred");
3501 ADD_STRING(" %s", "-is:unread");
3504 ADD_STRING(" %s", "is:unread");
3508 for(sl
= pgm
->keyword
; remain
> 0 && sl
; sl
= sl
->next
)
3509 ADD_STRING(" label:%s", sl
->text
.data
);
3512 if (pgm
->unkeyword
){
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
);
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
);
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
);
3547 sprintf(v
, "%dd", pgm
->older
/86400);
3548 ADD_STRING(" older_than:%s", v
);
3552 sprintf(v
, "%dd", pgm
->younger
/86400);
3553 ADD_STRING(" newer_than:%s", v
);
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
? " " : "}");
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
? " " : "}");
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
? "}" : ""));
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
? "}" : ""));
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
? "}" : ""));
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 */
3620 apgm
.type
= ATOM
; apgm
.text
= (void *) s
;
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
))
3632 else if (!imap_OK (stream
,reply
)) {
3633 mm_log (reply
->text
,ERROR
);
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 */
3675 /* IMAP send search program
3676 * Accepts: MAIL stream
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
;
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
))) ||
3705 (reply
= imap_send_sset (stream
,tag
,base
,s
,pgm
->uid
," UID ",limit
))))
3709 sprintf (*s
," LARGER %lu",pgm
->larger
);
3713 sprintf (*s
," SMALLER %lu",pgm
->smaller
);
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
,
3734 (reply
= imap_send_slist (stream
,tag
,base
,s
," UNKEYWORD ",
3735 pgm
->unkeyword
,limit
))))
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
);
3746 sprintf (*s
," OLDER %lu",pgm
->older
);
3750 sprintf (*s
," YOUNGER %lu",pgm
->younger
);
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
,
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
,
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 ",
3771 /* Note that these criteria are not supported by IMAP and have to be
3773 if ((pgm
->return_path
&&
3774 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Return-Path ",
3775 pgm
->return_path
,limit
))) ||
3777 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Sender ",
3778 pgm
->sender
,limit
))) ||
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
))) ||
3786 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Message-ID ",
3787 pgm
->message_id
,limit
))) ||
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
))) ||
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
)
3804 if ((reply
= imap_send_astring (stream
,tag
,s
,&hdr
->text
,NIL
,limit
)) != NULL
)
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
)
3811 for (t
= ") ("; *t
; *(*s
)++ = *t
++);
3812 if ((reply
= imap_send_spgm (stream
,tag
,base
,s
,pgo
->second
,limit
)) != NULL
)
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
)
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
)
3838 /* write new text */
3839 if (text
) for (t
= text
; *t
; *s
++ = *t
++);
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
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
,
3864 IMAPPARSEDREPLY
*reply
;
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
)++ = '*';
3875 sprintf (*s
,"%lu",set
->first
);
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
)++ = '*';
3883 sprintf (*s
,"%lu",set
->last
);
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
)
3900 *(*s
)++ = ')'; /* close second OR argument */
3905 /* IMAP send search list
3906 * Accepts: MAIL stream
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
,
3920 IMAPPARSEDREPLY
*reply
;
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
));
3931 /* IMAP send search date
3932 * Accepts: pointer to current position pointer of output bigbuf
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));
3944 /* IMAP send buffered command to sender
3945 * Accepts: MAIL stream
3948 * pointer to string tail pointer
3952 IMAPPARSEDREPLY
*imap_sout (MAILSTREAM
*stream
,char *tag
,char *base
,char **s
)
3954 IMAPPARSEDREPLY
*reply
;
3955 if (stream
->debug
) { /* output debugging telemetry */
3957 mail_dlog (base
,LOCAL
->sensitive
);
3959 *(*s
)++ = '\015'; /* append CRLF */
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 */
3970 /* IMAP send null-terminated string to sender
3971 * Accepts: MAIL stream
3973 * Returns: T if success, else NIL
3976 long imap_soutr (MAILSTREAM
*stream
,char *string
)
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
);
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
;
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)");
4022 * Accepts: MAIL stream
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
)
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
;
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
;
4046 /* non-continuation replies */
4047 if (strcmp (LOCAL
->reply
.tag
,"+")) {
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
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
4095 * Returns: T if OK else NIL
4098 long imap_OK (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
4101 /* OK - operation succeeded */
4102 if (!strcmp (reply
->key
,"OK")) {
4103 imap_parse_response (stream
,reply
->text
,NIL
,NIL
);
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 */
4122 /* IMAP parse and act upon unsolicited reply
4123 * Accepts: MAIL stream
4127 void imap_parse_unsolicited (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
4129 unsigned long i
= 0;
4130 unsigned long j
,msgno
;
4131 unsigned char *s
,*t
;
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
;
4143 if (!reply
->text
) { /* better be some data */
4144 mm_notify (stream
,"Missing message data",WARN
);
4145 stream
->unhealthy
= T
;
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
)) {
4170 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
4171 ENVELOPE
*env
= NIL
;
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")) {
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
;
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",
4247 sprintf (LOCAL
->tmp
,"Unterminated header section: %.80s",
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",
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
,
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
);
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] == '.'))){
4291 if (!prop
[6]) { /* cache full message */
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")) {
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
);
4313 sprintf (LOCAL
->tmp
,"Unknown RFC822 message property: %.80s",prop
);
4314 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4315 stream
->unhealthy
= T
;
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 */
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",
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
);
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
) {
4404 unsigned char *txt
= reply
->text
;
4405 if ((t
= imap_parse_astring (stream
,&txt
,reply
,&j
)) && txt
&&
4406 (*txt
++ == ' ') && (*txt
++ == '(') && (s
= strchr (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
;
4424 else if (!compare_cstring (txt
,"UNSEEN")) {
4425 status
.flags
|= SA_UNSEEN
;
4428 else if (!compare_cstring (txt
,"UIDNEXT")) {
4429 status
.flags
|= SA_UIDNEXT
;
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
) <
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 */
4476 s
+= 4; /* skip over NIL<space> */
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
);
4506 sprintf (LOCAL
->tmp
,"Junk after namespace list: %.80s",(char *) s
);
4507 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4508 stream
->unhealthy
= T
;
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 ();
4523 do if ((ac
->identifier
= imap_parse_astring (stream
,&s
,reply
,NIL
)) &&
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",
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
);
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",
4559 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4560 stream
->unhealthy
= T
;
4562 else if (lr
) (*lr
) (stream
,t
,id
,r
,rl
);
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 */
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 */
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
);
4590 if (s
&& (*s
++ == ' ') && (r
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
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 */
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
))) {
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
;
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
);
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
);
4669 mail_free_stringlist (&rl
);
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
);
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
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
)
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
== '(') &&
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")) {
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
)
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
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
;
4819 case 'N': /* if NIL */
4821 ++*txtptr
; /* bump past "N" */
4822 ++*txtptr
; /* bump past "I" */
4823 ++*txtptr
; /* bump past "L" */
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 */
4841 *txtptr
+= 3; /* bump past "NIL" */
4844 if (*++*txtptr
== '\\') nam
->delimiter
= *++*txtptr
;
4845 else nam
->delimiter
= **txtptr
;
4846 *txtptr
+= 2; /* bump past character and closing quote */
4849 sprintf (LOCAL
->tmp
,"Missing delimiter in namespace: %.80s",
4851 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4852 stream
->unhealthy
= T
;
4853 *txtptr
= NIL
; /* stop parse */
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
,
4862 mm_notify (stream
,"Missing namespace extension attribute",WARN
);
4863 stream
->unhealthy
= T
;
4864 par
->attribute
= cpystr ("UNKNOWN");
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
,
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
);
4885 sprintf (LOCAL
->tmp
,"Missing values for namespace attribute %.80s",
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",
4896 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4897 stream
->unhealthy
= T
;
4901 if (**txtptr
== ')') { /* expected trailing paren? */
4902 ++*txtptr
; /* got it! */
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 */
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
)
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
);
4935 if (parent
) parent
= parent
->next
= cur
;
4936 else { /* no parent, create dummy */
4937 if (last
) last
= last
->branch
= mail_newthreadnode (NIL
);
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 */
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
;
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
,
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
;
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
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
,
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 */
5062 case 'N': /* if NIL */
5064 ++*txtptr
; /* bump past "I" */
5065 ++*txtptr
; /* bump past "L" */
5068 sprintf (LOCAL
->tmp
,"Not an envelope: %.80s",(char *) *txtptr
);
5069 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5070 stream
->unhealthy
= T
;
5075 /* IMAP parse address list
5076 * Accepts: MAIL stream
5077 * current text pointer
5079 * Returns: address list, NIL on failure
5081 * Updates text pointer
5084 ADDRESS
*imap_parse_adrlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
5085 IMAPPARSEDREPLY
*reply
)
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 */
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",
5098 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5099 stream
->unhealthy
= T
;
5101 else ++*txtptr
; /* skip past delimiter */
5103 case 'N': /* if NIL */
5105 ++*txtptr
; /* bump past "I" */
5106 ++*txtptr
; /* bump past "L" */
5109 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
5110 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5111 stream
->unhealthy
= T
;
5117 /* IMAP parse address
5118 * Accepts: MAIL stream
5119 * current text pointer
5121 * Returns: address, NIL on failure
5123 * Updates text pointer
5126 ADDRESS
*imap_parse_address (MAILSTREAM
*stream
,unsigned char **txtptr
,
5127 IMAPPARSEDREPLY
*reply
)
5132 ADDRESS
*prev
= NIL
;
5133 char c
= **txtptr
; /* sniff at first address character */
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
;
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",
5168 stream
->unhealthy
= T
;
5169 mail_free_address (&adr
);
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
);
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
);
5197 case 'N': /* if NIL */
5199 *txtptr
+= 3; /* bump past NIL */
5202 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
5203 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5204 stream
->unhealthy
= T
;
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
)
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
;
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
=
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 */
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
5273 * Returns: flag bit position
5276 unsigned long imap_parse_user_flag (MAILSTREAM
*stream
,char *flag
)
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
5289 * returned string length
5292 * Updates text pointer
5295 unsigned char *imap_parse_astring (MAILSTREAM
*stream
,unsigned char **txtptr
,
5296 IMAPPARSEDREPLY
*reply
,unsigned long *len
)
5299 unsigned char c
,*s
,*ret
;
5300 /* ignore leading spaces */
5301 for (c
= **txtptr
; c
== ' '; c
= *++*txtptr
);
5303 case '"': /* quoted string? */
5304 case '{': /* literal? */
5305 ret
= imap_parse_string (stream
,txtptr
,reply
,NIL
,len
,NIL
);
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);
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
;
5329 /* IMAP parse string
5330 * Accepts: MAIL stream
5331 * current text pointer
5334 * returned string length
5335 * filter newline flag
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
)
5347 unsigned long i
,j
,k
;
5349 unsigned char c
= **txtptr
; /* sniff at first character */
5350 mailgets_t mg
= (mailgets_t
) mail_parameters (NIL
,GET_GETS
,NIL
);
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 */
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",
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 */
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 */
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? */
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
);
5396 case 'N': /* if NIL */
5398 ++*txtptr
; /* bump past "I" */
5399 ++*txtptr
; /* bump past "L" */
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 */
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
) {
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
);
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 */
5448 sprintf (LOCAL
->tmp
,"Not a string: %c%.80s",c
,(char *) *txtptr
);
5449 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5450 stream
->unhealthy
= T
;
5454 return (unsigned char *) string
;
5457 /* Register text in IMAP cache
5458 * Accepts: MAIL stream
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
];
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
;
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
;
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
);
5543 sprintf (tmp
,"Unknown section specifier: %.80s.%.80s",seg
,t
);
5544 mm_notify (stream
,tmp
,WARN
);
5545 stream
->unhealthy
= T
;
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
5567 * Updates text pointer
5570 void imap_parse_body_structure (MAILSTREAM
*stream
,BODY
*body
,
5571 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
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 ();
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
);
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",
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 */
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 */
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 */
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
,
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
]);
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
);
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");
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);
5694 default: /* otherwise nothing special */
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",
5719 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5720 stream
->unhealthy
= T
;
5722 else ++*txtptr
; /* skip past delimiter */
5725 case 'N': /* if NIL */
5727 ++*txtptr
; /* bump past "I" */
5728 ++*txtptr
; /* bump past "L" */
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
;
5738 /* IMAP parse body parameter
5739 * Accepts: MAIL stream
5740 * current text pointer
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
;
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
,
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
) == ' ');
5775 case ')': /* end of attribute/value pairs */
5776 ++*txtptr
; /* skip past closing paren */
5779 mm_notify (stream
,"Unterminated parameter list", WARN
);
5780 stream
->unhealthy
= T
;
5783 sprintf (LOCAL
->tmp
,"Junk at end of parameter: %.80s",(char *) *txtptr
);
5784 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5785 stream
->unhealthy
= T
;
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;
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
;
5802 /* IMAP parse body disposition
5803 * Accepts: MAIL stream
5804 * body structure to write into
5805 * current text pointer
5809 void imap_parse_disposition (MAILSTREAM
*stream
,BODY
*body
,
5810 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
5812 switch (*++*txtptr
) {
5814 ++*txtptr
; /* skip open paren */
5815 body
->disposition
.type
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
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",
5822 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5823 stream
->unhealthy
= T
;
5825 else ++*txtptr
; /* skip past delimiter */
5827 case 'N': /* if NIL */
5829 ++*txtptr
; /* bump past "N" */
5830 ++*txtptr
; /* bump past "I" */
5831 ++*txtptr
; /* bump past "L" */
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
!= ')'));
5843 /* IMAP parse body language
5844 * Accepts: MAIL stream
5845 * current text pointer
5847 * Returns: string list or NIL if empty or error
5850 STRINGLIST
*imap_parse_language (MAILSTREAM
*stream
,unsigned char **txtptr
,
5851 IMAPPARSEDREPLY
*reply
)
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
;
5865 /* IMAP parse string list
5866 * Accepts: MAIL stream
5867 * current text pointer
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 ();
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
);
5891 else if (*t
== ' ') ++t
; /* another token follows */
5893 if (stl
) *txtptr
= ++t
; /* update return string */
5897 /* IMAP parse unknown body extension data
5898 * Accepts: MAIL stream
5899 * current text pointer
5902 * Updates text pointer
5905 void imap_parse_extension (MAILSTREAM
*stream
,unsigned char **txtptr
,
5906 IMAPPARSEDREPLY
*reply
)
5909 switch (*++*txtptr
) { /* action depends upon first character */
5911 while (**txtptr
&& (**txtptr
!= ')'))
5912 imap_parse_extension (stream
,txtptr
,reply
);
5913 if (**txtptr
) ++*txtptr
; /* bump past closing parenthesis */
5915 case '"': /* if quoted string */
5916 while ((*++*txtptr
!= '"') && **txtptr
) if (**txtptr
== '\\') ++*txtptr
;
5917 if (**txtptr
) ++*txtptr
; /* bump past closing quote */
5919 case 'N': /* if NIL */
5921 ++*txtptr
; /* bump past "N" */
5922 ++*txtptr
; /* bump past "I" */
5923 ++*txtptr
; /* bump past "L" */
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),
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 */
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);
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
!= ')'));
5951 /* IMAP parse capabilities
5952 * Accepts: MAIL stream
5956 void imap_parse_capabilities (MAILSTREAM
*stream
,char *t
)
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
);
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
);
6032 * Accepts: MAIL stream
6035 * Returns: parsed reply from fetch
6038 IMAPPARSEDREPLY
*imap_fetch (MAILSTREAM
*stream
,char *sequence
,long flags
)
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
,
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 */
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
6085 char *imap_reform_sequence (MAILSTREAM
*stream
,char *sequence
,long flags
)
6087 unsigned long i
,j
,star
;
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 */
6102 case ':': /* message range */
6103 i
= (*s
== '*') ? star
: strtoul (s
,NIL
,10);
6104 if (*t
== '*') { /* range ends with star */
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 */
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 */