1 /* ========================================================================
2 * Copyright 2008-2011 Mark Crispin
3 * ========================================================================
7 * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
12 * Last Edited: 3 October 2011
14 * Previous versions of this file were
16 * Copyright 1988-2008 University of Washington
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
22 * http://www.apache.org/licenses/LICENSE-2.0
24 * This original version of this file is
25 * Copyright 1988 Stanford University
26 * and was developed in the Symbolic Systems Resources Group of the Knowledge
27 * Systems Laboratory at Stanford University in 1987-88, and was funded by the
28 * Biomedical Research Technology Program of the National Institutes of Health
29 * under grant number RR-00785.
41 #define IMAPLOOKAHEAD 20 /* envelope lookahead */
42 #define IMAPUIDLOOKAHEAD 1000 /* UID lookahead */
43 #define IMAPTCPPORT (long) 143 /* assigned TCP contact port */
44 #define IMAPSSLPORT (long) 993 /* assigned SSL TCP contact port */
45 #define MAXCOMMAND 1000 /* RFC 2683 guideline for cmd line length */
46 #define IDLETIMEOUT (long) 30 /* defined in RFC 3501 */
47 #define MAXSERVERLIT 0x7ffffffe /* maximum server literal size
48 * must be smaller than 4294967295
52 /* Parsed reply message from imap_reply */
54 typedef struct imap_parsed_reply
{
55 unsigned char *line
; /* original reply string pointer */
56 unsigned char *tag
; /* command tag this reply is for */
57 unsigned char *key
; /* reply keyword */
58 unsigned char *text
; /* subsequent text */
62 #define IMAPTMPLEN 16*MAILTMPLEN
65 /* IMAP4 I/O stream local data */
67 typedef struct imap_local
{
68 NETSTREAM
*netstream
; /* TCP I/O stream */
69 IMAPPARSEDREPLY reply
; /* last parsed reply */
70 MAILSTATUS
*stat
; /* status to fill in */
71 IMAPCAP cap
; /* server capabilities */
72 char *appendmailbox
; /* mailbox being appended to */
73 unsigned int uidsearch
: 1; /* UID searching */
74 unsigned int byeseen
: 1; /* saw a BYE response */
75 /* got implicit capabilities */
76 unsigned int gotcapability
: 1;
77 unsigned int sensitive
: 1; /* sensitive data in progress */
78 unsigned int tlsflag
: 1; /* TLS session */
79 unsigned int tlssslv23
: 1; /* TLS using SSLv23 client method */
80 unsigned int notlsflag
: 1; /* TLS not used in session */
81 unsigned int sslflag
: 1; /* SSL session */
82 unsigned int tls1
: 1; /* using TLSv1 over SSL */
83 unsigned int tls1_1
: 1; /* using TLSv1_1 over SSL */
84 unsigned int tls1_2
: 1; /* using TLSv1_2 over SSL */
85 unsigned int dtls1
: 1; /* using DTLSv1 over SSL */
86 unsigned int novalidate
: 1; /* certificate not validated */
87 unsigned int filter
: 1; /* filter SEARCH/SORT/THREAD results */
88 unsigned int loser
: 1; /* server is a loser */
89 unsigned int saslcancel
: 1; /* SASL cancelled by protocol */
90 long authflags
; /* required flags for authenticators */
91 unsigned long sortsize
; /* sort return data size */
92 unsigned long *sortdata
; /* sort return data */
94 unsigned long uid
; /* last UID returned */
95 unsigned long msgno
; /* last msgno returned */
97 NAMESPACE
**namespace; /* namespace return data */
98 THREADNODE
*threaddata
; /* thread return data */
99 char *referral
; /* last referral */
100 char *prefix
; /* find prefix */
101 char *user
; /* logged-in user */
102 char *reform
; /* reformed sequence */
103 char tmp
[IMAPTMPLEN
]; /* temporary buffer */
104 SEARCHSET
*lookahead
; /* fetch lookahead */
108 /* Convenient access to local data */
110 #define LOCAL ((IMAPLOCAL *) stream->local)
112 /* Arguments to imap_send() */
114 typedef struct imap_argument
{
115 int type
; /* argument type */
116 void *text
; /* argument text */
120 /* imap_send() argument types */
128 #define SEARCHPROGRAM 6
129 #define SORTPROGRAM 7
134 #define LISTMAILBOX 12
135 #define MULTIAPPEND 13
137 #define MULTIAPPENDREDO 15
142 typedef struct append_data
{
150 /* Function prototypes */
152 DRIVER
*imap_valid (char *name
);
153 void *imap_parameters (long function
,void *value
);
154 void imap_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
155 void imap_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
156 void imap_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
157 void imap_list_work (MAILSTREAM
*stream
,char *cmd
,char *ref
,char *pat
,
159 long imap_subscribe (MAILSTREAM
*stream
,char *mailbox
);
160 long imap_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
161 long imap_create (MAILSTREAM
*stream
,char *mailbox
);
162 long imap_delete (MAILSTREAM
*stream
,char *mailbox
);
163 long imap_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
164 long imap_manage (MAILSTREAM
*stream
,char *mailbox
,char *command
,char *arg2
);
165 long imap_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
166 MAILSTREAM
*imap_open (MAILSTREAM
*stream
);
167 IMAPPARSEDREPLY
*imap_rimap (MAILSTREAM
*stream
,char *service
,NETMBX
*mb
,
168 char *usr
,char *tmp
);
169 long imap_anon (MAILSTREAM
*stream
,char *tmp
);
170 long imap_auth (MAILSTREAM
*stream
,NETMBX
*mb
,char *tmp
,char *usr
);
171 long imap_login (MAILSTREAM
*stream
,NETMBX
*mb
,char *pwd
,char *usr
);
172 void *imap_challenge (void *stream
,unsigned long *len
);
173 long imap_response (void *stream
,char *s
,unsigned long size
);
174 void imap_close (MAILSTREAM
*stream
,long options
);
175 void imap_fast (MAILSTREAM
*stream
,char *sequence
,long flags
);
176 void imap_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
177 long imap_overview (MAILSTREAM
*stream
,overview_t ofn
);
178 ENVELOPE
*imap_structure (MAILSTREAM
*stream
,unsigned long msgno
,BODY
**body
,
180 long imap_msgdata (MAILSTREAM
*stream
,unsigned long msgno
,char *section
,
181 unsigned long first
,unsigned long last
,STRINGLIST
*lines
,
183 unsigned long imap_uid (MAILSTREAM
*stream
,unsigned long msgno
);
184 unsigned long imap_msgno (MAILSTREAM
*stream
,unsigned long uid
);
185 void imap_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
186 long imap_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
);
187 unsigned long *imap_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
188 SORTPGM
*pgm
,long flags
);
189 THREADNODE
*imap_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
190 SEARCHPGM
*spg
,long flags
);
191 THREADNODE
*imap_thread_work (MAILSTREAM
*stream
,char *type
,char *charset
,
192 SEARCHPGM
*spg
,long flags
);
193 long imap_ping (MAILSTREAM
*stream
);
194 void imap_check (MAILSTREAM
*stream
);
195 long imap_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
196 long imap_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
197 long imap_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
198 long imap_append_referral (char *mailbox
,char *tmp
,append_t af
,void *data
,
199 char *flags
,char *date
,STRING
*message
,
200 APPENDDATA
*map
,long options
);
201 IMAPPARSEDREPLY
*imap_append_single (MAILSTREAM
*stream
,char *mailbox
,
202 char *flags
,char *date
,STRING
*message
);
204 void imap_gc (MAILSTREAM
*stream
,long gcflags
);
205 void imap_gc_body (BODY
*body
);
206 void imap_capability (MAILSTREAM
*stream
);
207 long imap_acl_work (MAILSTREAM
*stream
,char *command
,IMAPARG
*args
[]);
209 IMAPPARSEDREPLY
*imap_send (MAILSTREAM
*stream
,char *cmd
,IMAPARG
*args
[]);
210 IMAPPARSEDREPLY
*imap_sout (MAILSTREAM
*stream
,char *tag
,char *base
,char **s
);
211 long imap_soutr (MAILSTREAM
*stream
,char *string
);
212 IMAPPARSEDREPLY
*imap_send_astring (MAILSTREAM
*stream
,char *tag
,char **s
,
213 SIZEDTEXT
*as
,long wildok
,char *limit
);
214 IMAPPARSEDREPLY
*imap_send_literal (MAILSTREAM
*stream
,char *tag
,char **s
,
216 IMAPPARSEDREPLY
*imap_send_spgm (MAILSTREAM
*stream
,char *tag
,char *base
,
217 char **s
,SEARCHPGM
*pgm
,char *limit
);
218 char *imap_send_spgm_trim (char *base
,char *s
,char *text
);
219 IMAPPARSEDREPLY
*imap_send_sset (MAILSTREAM
*stream
,char *tag
,char *base
,
220 char **s
,SEARCHSET
*set
,char *prefix
,
222 IMAPPARSEDREPLY
*imap_send_slist (MAILSTREAM
*stream
,char *tag
,char *base
,
223 char **s
,char *name
,STRINGLIST
*list
,
225 void imap_send_sdate (char **s
,char *name
,unsigned short date
);
226 IMAPPARSEDREPLY
*imap_reply (MAILSTREAM
*stream
,char *tag
);
227 IMAPPARSEDREPLY
*imap_parse_reply (MAILSTREAM
*stream
,char *text
);
228 IMAPPARSEDREPLY
*imap_fake (MAILSTREAM
*stream
,char *tag
,char *text
);
229 long imap_OK (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
);
230 void imap_parse_unsolicited (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
);
231 void imap_parse_response (MAILSTREAM
*stream
,char *text
,long errflg
,long ntfy
);
232 NAMESPACE
*imap_parse_namespace (MAILSTREAM
*stream
,unsigned char **txtptr
,
233 IMAPPARSEDREPLY
*reply
);
234 THREADNODE
*imap_parse_thread (MAILSTREAM
*stream
,unsigned char **txtptr
);
235 void imap_parse_header (MAILSTREAM
*stream
,ENVELOPE
**env
,SIZEDTEXT
*hdr
,
237 void imap_parse_envelope (MAILSTREAM
*stream
,ENVELOPE
**env
,
238 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
239 ADDRESS
*imap_parse_adrlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
240 IMAPPARSEDREPLY
*reply
);
241 ADDRESS
*imap_parse_address (MAILSTREAM
*stream
,unsigned char **txtptr
,
242 IMAPPARSEDREPLY
*reply
);
243 void imap_parse_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
,
244 unsigned char **txtptr
);
245 unsigned long imap_parse_user_flag (MAILSTREAM
*stream
,char *flag
);
246 unsigned char *imap_parse_astring (MAILSTREAM
*stream
,unsigned char **txtptr
,
247 IMAPPARSEDREPLY
*reply
,unsigned long *len
);
248 unsigned char *imap_parse_string (MAILSTREAM
*stream
,unsigned char **txtptr
,
249 IMAPPARSEDREPLY
*reply
,GETS_DATA
*md
,
250 unsigned long *len
,long flags
);
251 void imap_parse_body (GETS_DATA
*md
,char *seg
,unsigned char **txtptr
,
252 IMAPPARSEDREPLY
*reply
);
253 void imap_parse_body_structure (MAILSTREAM
*stream
,BODY
*body
,
254 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
255 PARAMETER
*imap_parse_body_parameter (MAILSTREAM
*stream
,
256 unsigned char **txtptr
,
257 IMAPPARSEDREPLY
*reply
);
258 void imap_parse_disposition (MAILSTREAM
*stream
,BODY
*body
,
259 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
260 STRINGLIST
*imap_parse_language (MAILSTREAM
*stream
,unsigned char **txtptr
,
261 IMAPPARSEDREPLY
*reply
);
262 STRINGLIST
*imap_parse_stringlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
263 IMAPPARSEDREPLY
*reply
);
264 void imap_parse_extension (MAILSTREAM
*stream
,unsigned char **txtptr
,
265 IMAPPARSEDREPLY
*reply
);
266 void imap_parse_capabilities (MAILSTREAM
*stream
,char *t
);
267 IMAPPARSEDREPLY
*imap_fetch (MAILSTREAM
*stream
,char *sequence
,long flags
);
268 char *imap_reform_sequence (MAILSTREAM
*stream
,char *sequence
,long flags
);
270 /* Driver dispatch used by MAIL */
272 DRIVER imapdriver
= {
273 "imap", /* driver name */
275 DR_MAIL
|DR_NEWS
|DR_NAMESPACE
|DR_CRLF
|DR_RECYCLE
|DR_HALFOPEN
,
276 (DRIVER
*) NIL
, /* next driver */
277 imap_valid
, /* mailbox is valid for us */
278 imap_parameters
, /* manipulate parameters */
279 imap_scan
, /* scan mailboxes */
280 imap_list
, /* find mailboxes */
281 imap_lsub
, /* find subscribed mailboxes */
282 imap_subscribe
, /* subscribe to mailbox */
283 imap_unsubscribe
, /* unsubscribe from mailbox */
284 imap_create
, /* create mailbox */
285 imap_delete
, /* delete mailbox */
286 imap_rename
, /* rename mailbox */
287 imap_status
, /* status of mailbox */
288 imap_open
, /* open mailbox */
289 imap_close
, /* close mailbox */
290 imap_fast
, /* fetch message "fast" attributes */
291 imap_flags
, /* fetch message flags */
292 imap_overview
, /* fetch overview */
293 imap_structure
, /* fetch message envelopes */
294 NIL
, /* fetch message header */
295 NIL
, /* fetch message body */
296 imap_msgdata
, /* fetch partial message */
297 imap_uid
, /* unique identifier */
298 imap_msgno
, /* message number */
299 imap_flag
, /* modify flags */
300 NIL
, /* per-message modify flags */
301 imap_search
, /* search for message based on criteria */
302 imap_sort
, /* sort messages */
303 imap_thread
, /* thread messages */
304 imap_ping
, /* ping mailbox to see if still alive */
305 imap_check
, /* check for new messages */
306 imap_expunge
, /* expunge deleted messages */
307 imap_copy
, /* copy messages to another mailbox */
308 imap_append
, /* append string message to mailbox */
309 imap_gc
/* garbage collect stream */
312 /* prototype stream */
313 MAILSTREAM imapproto
= {&imapdriver
};
315 /* driver parameters */
316 static unsigned long imap_maxlogintrials
= MAXLOGINTRIALS
;
317 static long imap_lookahead
= IMAPLOOKAHEAD
;
318 static long imap_uidlookahead
= IMAPUIDLOOKAHEAD
;
319 static long imap_fetchlookaheadlimit
= IMAPLOOKAHEAD
;
320 static long imap_defaultport
= 0;
321 static long imap_sslport
= 0;
322 static long imap_tryssl
= NIL
;
323 static long imap_prefetch
= IMAPLOOKAHEAD
;
324 static long imap_closeonerror
= NIL
;
325 static imapenvelope_t imap_envelope
= NIL
;
326 static imapreferral_t imap_referral
= NIL
;
327 static char *imap_extrahdrs
= NIL
;
330 static char *hdrheader
[] = {
331 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
332 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
333 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
334 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
335 "BODY.PEEK[HEADER.FIELDS (Newsgroups"
337 static char *hdrtrailer
="Followup-To References)]";
339 /* IMAP validate mailbox
340 * Accepts: mailbox name
341 * Returns: our driver if name is valid, NIL otherwise
344 DRIVER
*imap_valid (char *name
)
346 return mail_valid_net (name
,&imapdriver
,NIL
,NIL
);
350 /* IMAP manipulate driver parameters
351 * Accepts: function code
352 * function-dependent value
353 * Returns: function-dependent return value
356 void *imap_parameters (long function
,void *value
)
358 switch ((int) function
) {
360 if (((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->cap
.namespace &&
361 !((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->namespace)
362 imap_send (((MAILSTREAM
*) value
),"NAMESPACE",NIL
);
363 value
= (void *) &((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->namespace;
367 ((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->cap
.threader
;
369 case SET_FETCHLOOKAHEAD
: /* must use pointer from GET_FETCHLOOKAHEAD */
370 fatal ("SET_FETCHLOOKAHEAD not permitted");
371 case GET_FETCHLOOKAHEAD
:
372 value
= (void *) &((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->lookahead
;
374 case SET_MAXLOGINTRIALS
:
375 imap_maxlogintrials
= (long) value
;
377 case GET_MAXLOGINTRIALS
:
378 value
= (void *) imap_maxlogintrials
;
381 imap_lookahead
= (long) value
;
384 value
= (void *) imap_lookahead
;
386 case SET_UIDLOOKAHEAD
:
387 imap_uidlookahead
= (long) value
;
389 case GET_UIDLOOKAHEAD
:
390 value
= (void *) imap_uidlookahead
;
394 imap_defaultport
= (long) value
;
397 value
= (void *) imap_defaultport
;
399 case SET_SSLIMAPPORT
:
400 imap_sslport
= (long) value
;
402 case GET_SSLIMAPPORT
:
403 value
= (void *) imap_sslport
;
406 imap_prefetch
= (long) value
;
409 value
= (void *) imap_prefetch
;
411 case SET_CLOSEONERROR
:
412 imap_closeonerror
= (long) value
;
414 case GET_CLOSEONERROR
:
415 value
= (void *) imap_closeonerror
;
417 case SET_IMAPENVELOPE
:
418 imap_envelope
= (imapenvelope_t
) value
;
420 case GET_IMAPENVELOPE
:
421 value
= (void *) imap_envelope
;
423 case SET_IMAPREFERRAL
:
424 imap_referral
= (imapreferral_t
) value
;
426 case GET_IMAPREFERRAL
:
427 value
= (void *) imap_referral
;
429 case SET_IMAPEXTRAHEADERS
:
430 imap_extrahdrs
= (char *) value
;
432 case GET_IMAPEXTRAHEADERS
:
433 value
= (void *) imap_extrahdrs
;
436 imap_tryssl
= (long) value
;
439 value
= (void *) imap_tryssl
;
441 case SET_FETCHLOOKAHEADLIMIT
:
442 imap_fetchlookaheadlimit
= (long) value
;
444 case GET_FETCHLOOKAHEADLIMIT
:
445 value
= (void *) imap_fetchlookaheadlimit
;
448 case SET_IDLETIMEOUT
:
449 fatal ("SET_IDLETIMEOUT not permitted");
450 case GET_IDLETIMEOUT
:
451 value
= (void *) IDLETIMEOUT
;
454 value
= NIL
; /* error case */
460 /* IMAP scan mailboxes
461 * Accepts: mail stream
467 void imap_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
469 imap_list_work (stream
,"SCAN",ref
,pat
,contents
);
473 /* IMAP list mailboxes
474 * Accepts: mail stream
479 void imap_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
481 imap_list_work (stream
,"LIST",ref
,pat
,NIL
);
485 /* IMAP list subscribed mailboxes
486 * Accepts: mail stream
491 void imap_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
494 char *s
,mbx
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
495 /* do it on the server */
496 imap_list_work (stream
,"LSUB",ref
,pat
,NIL
);
497 if (*pat
== '{') { /* if remote pattern, must be IMAP */
498 if (!imap_valid (pat
)) return;
499 ref
= NIL
; /* good IMAP pattern, punt reference */
501 /* if remote reference, must be valid IMAP */
502 if (ref
&& (*ref
== '{') && !imap_valid (ref
)) return;
503 /* kludgy application of reference */
504 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
505 else strcpy (mbx
,pat
);
507 if ((s
= sm_read (tmp
,&sdb
)) != NULL
) do if (imap_valid (s
) && pmatch (s
,mbx
))
508 mm_lsub (stream
,NIL
,s
,NIL
);
509 /* until no more subscriptions */
510 while ((s
= sm_read (tmp
,&sdb
)) != NULL
);
513 /* IMAP find list of mailboxes
514 * Accepts: mail stream
521 void imap_list_work (MAILSTREAM
*stream
,char *cmd
,char *ref
,char *pat
,
524 MAILSTREAM
*st
= stream
;
526 char *s
,prefix
[MAILTMPLEN
],mbx
[MAILTMPLEN
];
527 IMAPARG
*args
[4],aref
,apat
,acont
;
528 if (ref
&& *ref
) { /* have a reference? */
529 if (!(imap_valid (ref
) && /* make sure valid IMAP name and open stream */
530 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
531 (stream
= mail_open (NIL
,ref
,OP_HALFOPEN
|OP_SILENT
))))) return;
532 /* calculate prefix length */
533 pl
= strchr (ref
,'}') + 1 - ref
;
534 strncpy (prefix
,ref
,pl
); /* build prefix */
535 prefix
[pl
] = '\0'; /* tie off prefix */
536 ref
+= pl
; /* update reference */
539 if (!(imap_valid (pat
) && /* make sure valid IMAP name and open stream */
540 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
541 (stream
= mail_open (NIL
,pat
,OP_HALFOPEN
|OP_SILENT
))))) return;
542 /* calculate prefix length */
543 pl
= strchr (pat
,'}') + 1 - pat
;
544 strncpy (prefix
,pat
,pl
); /* build prefix */
545 prefix
[pl
] = '\0'; /* tie off prefix */
546 pat
+= pl
; /* update reference */
548 LOCAL
->prefix
= prefix
; /* note prefix */
549 if (contents
) { /* want to do a scan? */
550 if (LEVELSCAN (stream
)) { /* make sure permitted */
551 args
[0] = &aref
; args
[1] = &apat
; args
[2] = &acont
; args
[3] = NIL
;
552 aref
.type
= ASTRING
; aref
.text
= (void *) (ref
? ref
: "");
553 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) pat
;
554 acont
.type
= ASTRING
; acont
.text
= (void *) contents
;
555 imap_send (stream
,cmd
,args
);
557 else mm_log ("Scan not valid on this IMAP server",ERROR
);
560 else if (LEVELIMAP4 (stream
)){/* easy if IMAP4 */
561 args
[0] = &aref
; args
[1] = &apat
; args
[2] = NIL
;
562 aref
.type
= ASTRING
; aref
.text
= (void *) (ref
? ref
: "");
563 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) pat
;
564 /* referrals armed? */
565 if (LOCAL
->cap
.mbx_ref
&& mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
)) {
566 /* yes, convert LIST -> RLIST */
567 if (!compare_cstring (cmd
,"LIST")) cmd
= "RLIST";
568 /* and convert LSUB -> RLSUB */
569 else if (!compare_cstring (cmd
,"LSUB")) cmd
= "RLSUB";
571 imap_send (stream
,cmd
,args
);
573 else if (LEVEL1176 (stream
)) {/* convert to IMAP2 format wildcard */
574 /* kludgy application of reference */
575 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
576 else strcpy (mbx
,pat
);
577 for (s
= mbx
; *s
; s
++) if (*s
== '%') *s
= '*';
578 args
[0] = &apat
; args
[1] = NIL
;
579 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) mbx
;
580 if (!(strstr (cmd
,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
581 strcmp (imap_send (stream
,"FIND ALL.MAILBOXES",args
)->key
,"BAD")) &&
582 !strcmp (imap_send (stream
,"FIND MAILBOXES",args
)->key
,"BAD"))
583 LOCAL
->cap
.rfc1176
= NIL
; /* must be RFC-1064 */
585 LOCAL
->prefix
= NIL
; /* no more prefix */
586 /* close temporary stream if we made one */
587 if (stream
!= st
) mail_close (stream
);
590 /* IMAP subscribe to mailbox
591 * Accepts: mail stream
592 * mailbox to add to subscription list
593 * Returns: T on success, NIL on failure
596 long imap_subscribe (MAILSTREAM
*stream
,char *mailbox
)
598 MAILSTREAM
*st
= stream
;
599 long ret
= ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
600 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
))) ?
601 imap_manage (stream
,mailbox
,LEVELIMAP4 (stream
) ?
602 "Subscribe" : "Subscribe Mailbox",NIL
) : NIL
;
603 /* toss out temporary stream */
604 if (st
!= stream
) mail_close (stream
);
609 /* IMAP unsubscribe to mailbox
610 * Accepts: mail stream
611 * mailbox to delete from manage list
612 * Returns: T on success, NIL on failure
615 long imap_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
617 MAILSTREAM
*st
= stream
;
618 long ret
= ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
619 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
))) ?
620 imap_manage (stream
,mailbox
,LEVELIMAP4 (stream
) ?
621 "Unsubscribe" : "Unsubscribe Mailbox",NIL
) : NIL
;
622 /* toss out temporary stream */
623 if (st
!= stream
) mail_close (stream
);
627 /* IMAP create mailbox
628 * Accepts: mail stream
629 * mailbox name to create
630 * Returns: T on success, NIL on failure
633 long imap_create (MAILSTREAM
*stream
,char *mailbox
)
635 return imap_manage (stream
,mailbox
,"Create",NIL
);
639 /* IMAP delete mailbox
640 * Accepts: mail stream
641 * mailbox name to delete
642 * Returns: T on success, NIL on failure
645 long imap_delete (MAILSTREAM
*stream
,char *mailbox
)
647 return imap_manage (stream
,mailbox
,"Delete",NIL
);
651 /* IMAP rename mailbox
652 * Accepts: mail stream
655 * Returns: T on success, NIL on failure
658 long imap_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
660 return imap_manage (stream
,old
,"Rename",newname
);
663 /* IMAP manage a mailbox
664 * Accepts: mail stream
665 * mailbox to manipulate
667 * optional second argument
668 * Returns: T on success, NIL on failure
671 long imap_manage (MAILSTREAM
*stream
,char *mailbox
,char *command
,char *arg2
)
673 MAILSTREAM
*st
= stream
;
674 IMAPPARSEDREPLY
*reply
;
676 char mbx
[MAILTMPLEN
],mbx2
[MAILTMPLEN
];
677 IMAPARG
*args
[3],ambx
,amb2
;
679 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
680 ambx
.type
= amb2
.type
= ASTRING
; ambx
.text
= (void *) mbx
;
681 amb2
.text
= (void *) mbx2
;
682 args
[0] = &ambx
; args
[1] = args
[2] = NIL
;
683 /* require valid names and open stream */
684 if (mail_valid_net (mailbox
,&imapdriver
,NIL
,mbx
) &&
685 (arg2
? mail_valid_net (arg2
,&imapdriver
,NIL
,mbx2
) : &imapdriver
) &&
686 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
687 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
)))) {
688 if (arg2
) args
[1] = &amb2
; /* second arg present? */
689 if (!(ret
= (imap_OK (stream
,reply
= imap_send (stream
,command
,args
)))) &&
690 ir
&& LOCAL
->referral
) {
692 switch (*command
) { /* which command was it? */
693 case 'S': code
= REFSUBSCRIBE
; break;
694 case 'U': code
= REFUNSUBSCRIBE
; break;
695 case 'C': code
= REFCREATE
; break;
696 case 'D': code
= REFDELETE
; break;
697 case 'R': code
= REFRENAME
; break;
699 fatal ("impossible referral command");
701 if ((code
>= 0) && (mailbox
= (*ir
) (stream
,LOCAL
->referral
,code
)))
702 ret
= imap_manage (NIL
,mailbox
,command
,(*command
== 'R') ?
703 (mailbox
+ strlen (mailbox
) + 1) : NIL
);
705 mm_log (reply
->text
,ret
? NIL
: ERROR
);
706 /* toss out temporary stream */
707 if (st
!= stream
) mail_close (stream
);
713 * Accepts: mail stream
716 * Returns: T on success, NIL on failure
719 long imap_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
721 IMAPARG
*args
[3],ambx
,aflg
;
722 char tmp
[MAILTMPLEN
];
726 MAILSTREAM
*tstream
= NIL
;
727 /* use given stream if (rev1 or halfopen) and
729 if (!((stream
&& (LEVELIMAP4rev1 (stream
) || stream
->halfopen
) &&
730 mail_usable_network_stream (stream
,mbx
)) ||
731 (stream
= tstream
= mail_open (NIL
,mbx
,OP_HALFOPEN
|OP_SILENT
))))
733 /* parse mailbox name */
734 mail_valid_net_parse (mbx
,&mb
);
735 args
[0] = &ambx
;args
[1] = NIL
;/* set up first argument as mailbox */
736 ambx
.type
= ASTRING
; ambx
.text
= (void *) mb
.mailbox
;
737 if (LEVELIMAP4rev1 (stream
)) {/* have STATUS command? */
739 aflg
.type
= FLAGS
; aflg
.text
= (void *) tmp
;
740 args
[1] = &aflg
; args
[2] = NIL
;
741 tmp
[0] = tmp
[1] = '\0'; /* build flag list */
742 if (flags
& SA_MESSAGES
) strcat (tmp
," MESSAGES");
743 if (flags
& SA_RECENT
) strcat (tmp
," RECENT");
744 if (flags
& SA_UNSEEN
) strcat (tmp
," UNSEEN");
745 if (flags
& SA_UIDNEXT
) strcat (tmp
," UIDNEXT");
746 if (flags
& SA_UIDVALIDITY
) strcat (tmp
," UIDVALIDITY");
749 /* send "STATUS mailbox flag" */
750 if (imap_OK (stream
,imap_send (stream
,"STATUS",args
))) ret
= T
;
751 else if ((ir
= (imapreferral_t
)
752 mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
)) &&
754 (mbx
= (*ir
) (stream
,LOCAL
->referral
,REFSTATUS
)))
755 ret
= imap_status (NIL
,mbx
,flags
| (stream
->debug
? SA_DEBUG
: NIL
));
759 else if (imap_OK (stream
,imap_send (stream
,"EXAMINE",args
))) {
761 status
.flags
= flags
& ~ (SA_UIDNEXT
| SA_UIDVALIDITY
);
762 status
.messages
= stream
->nmsgs
;
763 status
.recent
= stream
->recent
;
765 if (flags
& SA_UNSEEN
) { /* must search to get unseen messages */
766 /* clear search vector */
767 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mail_elt (stream
,i
)->searched
= NIL
;
768 if (imap_OK (stream
,imap_send (stream
,"SEARCH UNSEEN",NIL
)))
769 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
770 if (mail_elt (stream
,i
)->searched
) status
.unseen
++;
772 strcpy (strchr (strcpy (tmp
,stream
->mailbox
),'}') + 1,mb
.mailbox
);
773 /* pass status to main program */
774 mm_status (stream
,tmp
,&status
);
775 ret
= T
; /* note success */
777 if (tstream
) mail_close (tstream
);
778 return ret
; /* success */
782 * Accepts: stream to open
783 * Returns: stream to use on success, NIL on failure
786 MAILSTREAM
*imap_open (MAILSTREAM
*stream
)
789 char *s
,tmp
[MAILTMPLEN
],usr
[MAILTMPLEN
];
791 IMAPPARSEDREPLY
*reply
= NIL
;
793 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
794 /* return prototype for OP_PROTOTYPE call */
795 if (!stream
) return &imapproto
;
796 mail_valid_net_parse (stream
->mailbox
,&mb
);
797 usr
[0] = '\0'; /* initially no user name */
798 if (LOCAL
) { /* if stream opened earlier by us */
799 /* recycle if still alive */
800 if (LOCAL
->netstream
&& (!stream
->halfopen
|| LOCAL
->cap
.unselect
)) {
801 i
= stream
->silent
; /* temporarily mark silent */
802 stream
->silent
= T
; /* don't give mm_exists() events */
803 j
= imap_ping (stream
); /* learn if stream still alive */
804 stream
->silent
= i
; /* restore prior state */
805 if (j
) { /* was stream still alive? */
806 sprintf (tmp
,"Reusing connection to %s",net_host (LOCAL
->netstream
));
807 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",
809 if (!stream
->silent
) mm_log (tmp
,(long) NIL
);
810 /* unselect if now want halfopen */
811 if (stream
->halfopen
) imap_send (stream
,"UNSELECT",NIL
);
813 else imap_close (stream
,NIL
);
815 else imap_close (stream
,NIL
);
817 /* copy flags from name */
818 if (mb
.dbgflag
) stream
->debug
= T
;
819 if (mb
.readonlyflag
) stream
->rdonly
= T
;
820 if (mb
.anoflag
) stream
->anonymous
= T
;
821 if (mb
.secflag
) stream
->secure
= T
;
822 if (mb
.trysslflag
|| imap_tryssl
) stream
->tryssl
= T
;
824 if (!LOCAL
) { /* open new connection if no recycle */
825 NETDRIVER
*ssld
= (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
);
826 unsigned long defprt
= imap_defaultport
? imap_defaultport
: IMAPTCPPORT
;
827 unsigned long sslport
= imap_sslport
? imap_sslport
: IMAPSSLPORT
;
828 stream
->local
= /* instantiate localdata */
829 (void *) memset (fs_get (sizeof (IMAPLOCAL
)),0,sizeof (IMAPLOCAL
));
830 /* assume IMAP2bis server */
831 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
832 /* in case server is a loser */
833 if (mb
.loser
) LOCAL
->loser
= T
;
834 /* desirable authenticators */
835 LOCAL
->authflags
= (stream
->secure
? AU_SECURE
: NIL
) |
836 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
);
837 /* IMAP connection open logic is more complex than net_open() normally
838 * deals with, because of the simap and rimap hacks.
839 * If the session is anonymous, a specific port is given, or if /ssl or
840 * /tls is set, do net_open() since those conditions override everything
843 if (stream
->anonymous
|| mb
.port
|| mb
.sslflag
|| mb
.tlsflag
)
844 reply
= (LOCAL
->netstream
= net_open (&mb
,NIL
,defprt
,ssld
,"*imaps",
846 imap_reply (stream
,NIL
) : NIL
;
848 * No overriding conditions, so get the best connection that we can. In
849 * order, attempt to open via simap, tryssl, rimap, and finally TCP.
852 else if ((reply
= imap_rimap (stream
,"*imap",&mb
,usr
,tmp
)) != NULL
);
853 else if (ssld
&& /* try tryssl if enabled */
854 (stream
->tryssl
|| mail_parameters (NIL
,GET_TRYSSLFIRST
,NIL
)) &&
856 net_open_work (ssld
,mb
.host
,"*imaps",sslport
,mb
.port
,
857 (mb
.novalidate
? NET_NOVALIDATECERT
: 0) |
858 NET_SILENT
| NET_TRYSSL
))) {
859 if (net_sout (LOCAL
->netstream
,"",0)) {
861 reply
= imap_reply (stream
,NIL
);
863 else { /* flush fake SSL stream */
864 net_close (LOCAL
->netstream
);
865 LOCAL
->netstream
= NIL
;
868 /* try rimap first, then TCP */
869 else if (!(reply
= imap_rimap (stream
,"imap",&mb
,usr
,tmp
)) &&
870 (LOCAL
->netstream
= net_open (&mb
,NIL
,defprt
,NIL
,NIL
,NIL
)))
871 reply
= imap_reply (stream
,NIL
);
872 /* make sure greeting is good */
873 if (!reply
|| strcmp (reply
->tag
,"*") ||
874 (strcmp (reply
->key
,"OK") && strcmp (reply
->key
,"PREAUTH"))) {
875 if (reply
) mm_log (reply
->text
,ERROR
);
876 return NIL
; /* lost during greeting */
879 /* if connected and not preauthenticated */
880 if (LOCAL
->netstream
&& strcmp (reply
->key
,"PREAUTH")) {
881 sslstart_t stls
= (sslstart_t
) mail_parameters (NIL
,GET_SSLSTART
,NIL
);
882 /* get server capabilities */
883 if (!LOCAL
->gotcapability
) imap_capability (stream
);
884 if (LOCAL
->netstream
&& /* does server support STARTTLS? */
885 stls
&& LOCAL
->cap
.starttls
&& !mb
.sslflag
&& !mb
.notlsflag
&&
886 imap_OK (stream
,imap_send (stream
,"STARTTLS",NIL
))) {
887 mb
.tlsflag
= T
; /* TLS OK, get into TLS at this end */
888 LOCAL
->netstream
->dtb
= ssld
;
889 if (!(LOCAL
->netstream
->stream
=
890 (*stls
) (LOCAL
->netstream
->stream
,mb
.host
,
891 SSL_MTHD(mb
) | (mb
.novalidate
? NET_NOVALIDATECERT
: NIL
)))) {
892 /* drat, drop this connection */
893 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
894 LOCAL
->netstream
= NIL
;
896 /* get capabilities now that TLS in effect */
897 if (LOCAL
->netstream
) imap_capability (stream
);
899 else if (mb
.tlsflag
) { /* user specified /tls but can't do it */
900 mm_log ("Unable to negotiate TLS with this server",ERROR
);
903 if (LOCAL
->netstream
) { /* still in the land of the living? */
904 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
905 /* remote name for authentication */
906 strncpy (mb
.host
,(long) mail_parameters(NIL
,GET_SASLUSESPTRNAME
,NIL
)?
907 net_remotehost (LOCAL
->netstream
) :
908 net_host (LOCAL
->netstream
),NETMAXHOST
-1);
909 mb
.host
[NETMAXHOST
-1] = '\0';
911 /* need new capabilities after login */
912 LOCAL
->gotcapability
= NIL
;
913 if (!(stream
->anonymous
? imap_anon (stream
,tmp
) :
914 (LOCAL
->cap
.auth
? imap_auth (stream
,&mb
,tmp
,usr
) :
915 imap_login (stream
,&mb
,tmp
,usr
)))) {
916 /* failed, is there a referral? */
917 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
918 if (ir
&& LOCAL
->referral
&&
919 (s
= (*ir
) (stream
,LOCAL
->referral
,REFAUTHFAILED
))) {
920 imap_close (stream
,NIL
);
921 fs_give ((void **) &stream
->mailbox
);
922 /* set as new mailbox name to open */
924 return imap_open (stream
);
926 return NIL
; /* authentication failed */
928 else if (ir
&& LOCAL
->referral
&&
929 (s
= (*ir
) (stream
,LOCAL
->referral
,REFAUTH
))) {
930 imap_close (stream
,NIL
);
931 fs_give ((void **) &stream
->mailbox
);
932 stream
->mailbox
= s
; /* set as new mailbox name to open */
933 /* recurse to log in on real site */
934 return imap_open (stream
);
938 /* get server capabilities again */
939 if (LOCAL
->netstream
&& !LOCAL
->gotcapability
) imap_capability (stream
);
940 /* save state for future recycling */
941 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
942 if (mb
.tls1
) LOCAL
->tls1
= T
;
943 if (mb
.dtls1
) LOCAL
->dtls1
= T
;
944 if (mb
.tls1_1
) LOCAL
->tls1_1
= T
;
945 if (mb
.tls1_2
) LOCAL
->tls1_2
= T
;
946 if (mb
.tlssslv23
) LOCAL
->tlssslv23
= T
;
947 if (mb
.notlsflag
) LOCAL
->notlsflag
= T
;
948 if (mb
.sslflag
) LOCAL
->sslflag
= T
;
949 if (mb
.novalidate
) LOCAL
->novalidate
= T
;
950 if (mb
.loser
) LOCAL
->loser
= T
;
953 if (LOCAL
->netstream
) { /* still have a connection? */
954 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_answered
=
955 stream
->perm_draft
= LEVELIMAP4 (stream
) ? NIL
: T
;
956 stream
->perm_user_flags
= LEVELIMAP4 (stream
) ? NIL
: 0xffffffff;
957 stream
->sequence
++; /* bump sequence number */
958 sprintf (tmp
,"{%s",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
959 net_host (LOCAL
->netstream
) : mb
.host
);
960 if (!((i
= net_port (LOCAL
->netstream
)) & 0xffff0000))
961 sprintf (tmp
+ strlen (tmp
),":%lu",i
);
962 strcat (tmp
,"/imap");
963 if (LOCAL
->tlsflag
) strcat (tmp
,"/tls");
964 if (LOCAL
->tls1
) strcat (tmp
,"/tls1");
965 if (LOCAL
->tls1_1
) strcat (tmp
,"/tls1_1");
966 if (LOCAL
->tls1_2
) strcat (tmp
,"/tls1_2");
967 if (LOCAL
->dtls1
) strcat (tmp
,"/dtls1");
968 if (LOCAL
->tlssslv23
) strcat (tmp
,"/tls-sslv23");
969 if (LOCAL
->notlsflag
) strcat (tmp
,"/notls");
970 if (LOCAL
->sslflag
) strcat (tmp
,"/ssl");
971 if (LOCAL
->novalidate
) strcat (tmp
,"/novalidate-cert");
972 if (LOCAL
->loser
) strcat (tmp
,"/loser");
973 if (stream
->secure
) strcat (tmp
,"/secure");
974 if (stream
->rdonly
) strcat (tmp
,"/readonly");
975 if (stream
->anonymous
) strcat (tmp
,"/anonymous");
976 else { /* record user name */
977 if (!LOCAL
->user
&& usr
[0]) LOCAL
->user
= cpystr (usr
);
978 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",
983 if (!stream
->halfopen
) { /* wants to open a mailbox? */
987 ambx
.text
= (void *) mb
.mailbox
;
988 args
[0] = &ambx
; args
[1] = NIL
;
990 if (imap_OK (stream
,reply
= imap_send (stream
,stream
->rdonly
?
991 "EXAMINE": "SELECT",args
))) {
992 strcat (tmp
,mb
.mailbox
);/* mailbox name */
993 if (!stream
->nmsgs
&& !stream
->silent
)
994 mm_log ("Mailbox is empty",(long) NIL
);
995 /* note if an INBOX or not */
996 stream
->inbox
= !compare_cstring (mb
.mailbox
,"INBOX");
998 else if (ir
&& LOCAL
->referral
&&
999 (s
= (*ir
) (stream
,LOCAL
->referral
,REFSELECT
))) {
1000 imap_close (stream
,NIL
);
1001 fs_give ((void **) &stream
->mailbox
);
1002 stream
->mailbox
= s
; /* set as new mailbox name to open */
1003 return imap_open (stream
);
1006 mm_log (reply
->text
,ERROR
);
1007 if (imap_closeonerror
) return NIL
;
1008 stream
->halfopen
= T
; /* let him keep it half-open */
1011 if (stream
->halfopen
) { /* half-open connection? */
1012 strcat (tmp
,"<no_mailbox>");
1013 /* make sure dummy message counts */
1014 mail_exists (stream
,(long) 0);
1015 mail_recent (stream
,(long) 0);
1017 fs_give ((void **) &stream
->mailbox
);
1018 stream
->mailbox
= cpystr (tmp
);
1020 /* success if stream open */
1021 return LOCAL
->netstream
? stream
: NIL
;
1024 /* IMAP rimap connect
1025 * Accepts: MAIL stream
1026 * NETMBX specification
1030 * Returns: parsed reply if success, else NIL
1033 IMAPPARSEDREPLY
*imap_rimap (MAILSTREAM
*stream
,char *service
,NETMBX
*mb
,
1034 char *usr
,char *tmp
)
1039 IMAPPARSEDREPLY
*reply
= NIL
;
1040 /* try rimap open */
1041 if (!mb
->norsh
&& (tstream
= net_aopen (NIL
,mb
,service
,usr
))) {
1042 /* if success, see if reasonable banner */
1043 if (net_getbuffer (tstream
,(long) 1,c
) && (*c
== '*')) {
1044 i
= 0; /* copy to buffer */
1046 while (net_getbuffer (tstream
,(long) 1,c
) && (*c
!= '\015') &&
1047 (*c
!= '\012') && (i
< (MAILTMPLEN
-1)));
1048 tmp
[i
] = '\0'; /* tie off */
1049 /* snarfed a valid greeting? */
1050 if ((*c
== '\015') && net_getbuffer (tstream
,(long) 1,c
) &&
1052 !strcmp ((reply
= imap_parse_reply (stream
,cpystr (tmp
)))->tag
,"*")){
1053 /* parse line as IMAP */
1054 imap_parse_unsolicited (stream
,reply
);
1055 /* make sure greeting is good */
1056 if (!strcmp (reply
->key
,"OK") || !strcmp (reply
->key
,"PREAUTH")) {
1057 LOCAL
->netstream
= tstream
;
1058 return reply
; /* return success */
1062 net_close (tstream
); /* failed, punt the temporary netstream */
1067 /* IMAP log in as anonymous
1068 * Accepts: stream to authenticate
1070 * Returns: T on success, NIL on failure
1073 long imap_anon (MAILSTREAM
*stream
,char *tmp
)
1075 IMAPPARSEDREPLY
*reply
;
1076 char *s
= net_localhost (LOCAL
->netstream
);
1077 if (LOCAL
->cap
.authanon
) {
1080 char *broken
= "[CLOSED] IMAP connection broken (anonymous auth)";
1081 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
1083 sprintf (tmp
,"%s AUTHENTICATE ANONYMOUS",tag
);
1084 if (!imap_soutr (stream
,tmp
)) {
1085 mm_log (broken
,ERROR
);
1088 if (imap_challenge (stream
,&i
)) imap_response (stream
,s
,strlen (s
));
1090 if (!(reply
= &LOCAL
->reply
)->tag
) reply
= imap_fake (stream
,tag
,broken
);
1091 /* what we wanted? */
1092 if (compare_cstring (reply
->tag
,tag
)) {
1093 /* abort if don't have tagged response */
1094 while (compare_cstring ((reply
= imap_reply (stream
,tag
))->tag
,tag
))
1095 imap_soutr (stream
,"*");
1101 ausr
.type
= ASTRING
;
1102 ausr
.text
= (void *) s
;
1103 args
[0] = &ausr
; args
[1] = NIL
;
1104 /* send "LOGIN anonymous <host>" */
1105 reply
= imap_send (stream
,"LOGIN ANONYMOUS",args
);
1107 /* success if reply OK */
1108 if (imap_OK (stream
,reply
)) return T
;
1109 mm_log (reply
->text
,ERROR
);
1113 /* IMAP authenticate
1114 * Accepts: stream to authenticate
1115 * parsed network mailbox structure
1117 * place to return user name
1118 * Returns: T on success, NIL on failure
1121 long imap_auth (MAILSTREAM
*stream
,NETMBX
*mb
,char *tmp
,char *usr
)
1123 unsigned long trial
,ua
;
1128 IMAPPARSEDREPLY
*reply
;
1129 for (ua
= LOCAL
->cap
.auth
, LOCAL
->saslcancel
= NIL
; LOCAL
->netstream
&& ua
&&
1130 (at
= mail_lookup_auth (find_rightmost_bit (&ua
) + 1));) {
1131 if (lsterr
) { /* previous authenticator failed? */
1132 sprintf (tmp
,"Retrying using %s authentication after %.80s",
1135 fs_give ((void **) &lsterr
);
1137 trial
= 0; /* initial trial count */
1138 tmp
[0] = '\0'; /* no error */
1139 do { /* gensym a new tag */
1140 if (lsterr
) { /* previous attempt with this one failed? */
1141 sprintf (tmp
,"Retrying %s authentication after %.80s",at
->name
,lsterr
);
1143 fs_give ((void **) &lsterr
);
1145 LOCAL
->saslcancel
= NIL
;
1146 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
1148 sprintf (tmp
,"%s AUTHENTICATE %s",tag
,at
->name
);
1149 if (imap_soutr (stream
,tmp
)) {
1150 /* hide client authentication responses */
1151 if (!(at
->flags
& AU_SECURE
)) LOCAL
->sensitive
= T
;
1152 ok
= (*at
->client
) (imap_challenge
,imap_response
,"imap",mb
,stream
,
1154 LOCAL
->sensitive
= NIL
; /* unhide */
1155 /* make sure have a response */
1156 if (!(reply
= &LOCAL
->reply
)->tag
)
1157 reply
= imap_fake (stream
,tag
,
1158 "[CLOSED] IMAP connection broken (authenticate)");
1159 else if (compare_cstring (reply
->tag
,tag
))
1160 while (compare_cstring ((reply
= imap_reply (stream
,tag
))->tag
,tag
))
1161 imap_soutr (stream
,"*");
1162 /* good if SASL ok and success response */
1163 if (ok
&& imap_OK (stream
,reply
)) return T
;
1164 if (!trial
) { /* if main program requested cancellation */
1165 mm_log ("IMAP Authentication cancelled",ERROR
);
1168 /* no error if protocol-initiated cancel */
1169 lsterr
= cpystr (reply
->text
);
1172 while (LOCAL
->netstream
&& !LOCAL
->byeseen
&& trial
&&
1173 (trial
< imap_maxlogintrials
));
1175 if (lsterr
) { /* previous authenticator failed? */
1176 if (!LOCAL
->saslcancel
) { /* don't do this if a cancel */
1177 sprintf (tmp
,"Can not authenticate to IMAP server: %.80s",lsterr
);
1180 fs_give ((void **) &lsterr
);
1182 return NIL
; /* ran out of authenticators */
1186 * Accepts: stream to login
1187 * parsed network mailbox structure
1188 * scratch buffer of length MAILTMPLEN
1189 * place to return user name
1190 * Returns: T on success, NIL on failure
1193 long imap_login (MAILSTREAM
*stream
,NETMBX
*mb
,char *pwd
,char *usr
)
1195 unsigned long trial
= 0;
1196 IMAPPARSEDREPLY
*reply
;
1200 if (stream
->secure
) /* never do LOGIN if want security */
1201 mm_log ("Can't do secure authentication with this server",ERROR
);
1202 /* never do LOGIN if server disabled it */
1203 else if (LOCAL
->cap
.logindisabled
)
1204 mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR
);
1205 else if (mb
->authuser
[0]) /* never do LOGIN with /authuser */
1206 mm_log ("Can't do /authuser with this server",ERROR
);
1207 else { /* OK to try login */
1208 ausr
.type
= apwd
.type
= ASTRING
;
1209 ausr
.text
= (void *) usr
;
1210 apwd
.text
= (void *) pwd
;
1211 args
[0] = &ausr
; args
[1] = &apwd
; args
[2] = NIL
;
1213 pwd
[0] = 0; /* prompt user for password */
1214 mm_login (mb
,usr
,pwd
,trial
++);
1215 if (pwd
[0]) { /* send login command if have password */
1216 LOCAL
->sensitive
= T
; /* hide this command */
1217 /* send "LOGIN usr pwd" */
1218 if (imap_OK (stream
,reply
= imap_send (stream
,"LOGIN",args
)))
1219 ret
= LONGT
; /* success */
1221 mm_log (reply
->text
,WARN
);
1222 if (!LOCAL
->referral
&& (trial
== imap_maxlogintrials
))
1223 mm_log ("Too many login failures",ERROR
);
1225 LOCAL
->sensitive
= NIL
; /* unhide */
1227 /* user refused to give password */
1228 else mm_log ("Login aborted",ERROR
);
1229 } while (!ret
&& pwd
[0] && (trial
< imap_maxlogintrials
) &&
1230 LOCAL
->netstream
&& !LOCAL
->byeseen
&& !LOCAL
->referral
);
1232 memset (pwd
,0,MAILTMPLEN
); /* erase password */
1236 /* Get challenge to authenticator in binary
1238 * pointer to returned size
1239 * Returns: challenge or NIL if not challenge
1242 void *imap_challenge (void *s
,unsigned long *len
)
1244 char tmp
[MAILTMPLEN
];
1246 MAILSTREAM
*stream
= (MAILSTREAM
*) s
;
1247 IMAPPARSEDREPLY
*reply
= NIL
;
1248 /* get tagged response or challenge */
1249 while (stream
&& LOCAL
->netstream
&&
1250 (reply
= imap_parse_reply (stream
,net_getline (LOCAL
->netstream
))) &&
1251 !strcmp (reply
->tag
,"*")) imap_parse_unsolicited (stream
,reply
);
1252 /* parse challenge if have one */
1253 if (stream
&& LOCAL
->netstream
&& reply
&& reply
->tag
&&
1254 (*reply
->tag
== '+') && !reply
->tag
[1] && reply
->text
&&
1255 !(ret
= rfc822_base64 ((unsigned char *) reply
->text
,
1256 strlen (reply
->text
),len
))) {
1257 sprintf (tmp
,"IMAP SERVER BUG (invalid challenge): %.80s",
1258 (char *) reply
->text
);
1265 /* Send authenticator response in BASE64
1266 * Accepts: MAIL stream
1269 * Returns: T if successful, else NIL
1272 long imap_response (void *s
,char *response
,unsigned long size
)
1274 MAILSTREAM
*stream
= (MAILSTREAM
*) s
;
1275 unsigned long i
,j
,ret
;
1277 if (response
) { /* make CRLFless BASE64 string */
1279 for (t
= (char *) rfc822_binary ((void *) response
,size
,&i
),u
= t
,j
= 0;
1280 j
< i
; j
++) if (t
[j
] > ' ') *u
++ = t
[j
];
1281 *u
= '\0'; /* tie off string for mm_dlog() */
1282 if (stream
->debug
) mail_dlog (t
,LOCAL
->sensitive
);
1284 *u
++ = '\015'; *u
++ = '\012';
1285 ret
= net_sout (LOCAL
->netstream
,t
,u
- t
);
1286 fs_give ((void **) &t
);
1288 else ret
= imap_soutr (stream
,"");
1290 else { /* abort requested */
1291 ret
= imap_soutr (stream
,"*");
1292 LOCAL
->saslcancel
= T
; /* mark protocol-requested SASL cancel */
1298 * Accepts: MAIL stream
1302 void imap_close (MAILSTREAM
*stream
,long options
)
1305 IMAPPARSEDREPLY
*reply
;
1306 if (stream
&& LOCAL
) { /* send "LOGOUT" */
1307 if (!LOCAL
->byeseen
) { /* don't even think of doing it if saw a BYE */
1308 /* expunge silently if requested */
1309 if (options
& CL_EXPUNGE
)
1310 imap_send (stream
,LEVELIMAP4 (stream
) ? "CLOSE" : "EXPUNGE",NIL
);
1311 if (LOCAL
->netstream
&&
1312 !imap_OK (stream
,reply
= imap_send (stream
,"LOGOUT",NIL
)))
1313 mm_log (reply
->text
,WARN
);
1315 /* close NET connection if still open */
1316 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
1317 LOCAL
->netstream
= NIL
;
1318 /* free up memory */
1319 if (LOCAL
->sortdata
) fs_give ((void **) &LOCAL
->sortdata
);
1320 if (LOCAL
->namespace) {
1321 mail_free_namespace (&LOCAL
->namespace[0]);
1322 mail_free_namespace (&LOCAL
->namespace[1]);
1323 mail_free_namespace (&LOCAL
->namespace[2]);
1324 fs_give ((void **) &LOCAL
->namespace);
1326 if (LOCAL
->threaddata
) mail_free_threadnode (&LOCAL
->threaddata
);
1327 /* flush threaders */
1328 if ((thr
= LOCAL
->cap
.threader
) != NULL
) while ((t
= thr
) != NULL
) {
1329 fs_give ((void **) &t
->name
);
1331 fs_give ((void **) &t
);
1333 if (LOCAL
->referral
) fs_give ((void **) &LOCAL
->referral
);
1334 if (LOCAL
->user
) fs_give ((void **) &LOCAL
->user
);
1335 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
1336 if (LOCAL
->reform
) fs_give ((void **) &LOCAL
->reform
);
1337 /* nuke the local data */
1338 fs_give ((void **) &stream
->local
);
1342 /* IMAP fetch fast information
1343 * Accepts: MAIL stream
1347 * Generally, imap_structure is preferred
1350 void imap_fast (MAILSTREAM
*stream
,char *sequence
,long flags
)
1352 IMAPPARSEDREPLY
*reply
= imap_fetch (stream
,sequence
,flags
& FT_UID
);
1353 if (!imap_OK (stream
,reply
)) mm_log (reply
->text
,ERROR
);
1358 * Accepts: MAIL stream
1363 void imap_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
1364 { /* send "FETCH sequence FLAGS" */
1365 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ? "UID FETCH":"FETCH";
1366 IMAPPARSEDREPLY
*reply
;
1367 IMAPARG
*args
[3],aseq
,aatt
;
1368 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
1370 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
1371 aatt
.type
= ATOM
; aatt
.text
= (void *) "FLAGS";
1372 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1373 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
)))
1374 mm_log (reply
->text
,ERROR
);
1377 /* IMAP fetch overview
1378 * Accepts: MAIL stream, sequence bits set
1379 * pointer to overview return function
1380 * Returns: T if successful, NIL otherwise
1383 long imap_overview (MAILSTREAM
*stream
,overview_t ofn
)
1389 unsigned long i
,start
,last
,len
,slen
;
1390 if (!LOCAL
->netstream
) return NIL
;
1391 /* build overview sequence */
1392 for (i
= 1,len
= start
= last
= 0,s
= t
= NIL
; i
<= stream
->nmsgs
; ++i
)
1393 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1394 if (!elt
->private.msg
.env
) {
1395 if (s
) { /* continuing a sequence */
1396 if (i
== last
+ 1) last
= i
;
1397 else { /* end of range */
1398 if (last
!= start
) sprintf (t
,":%lu,%lu",last
,i
);
1399 else sprintf (t
,",%lu",i
);
1400 if ((len
- (slen
= (t
+= strlen (t
)) - s
)) < 20) {
1401 fs_resize ((void **) &s
,len
+= MAILTMPLEN
);
1402 t
= s
+ slen
; /* relocate current pointer */
1404 start
= last
= i
; /* begin a new range */
1407 else { /* first time, start new buffer */
1408 s
= (char *) fs_get (len
= MAILTMPLEN
);
1409 sprintf (s
,"%lu",start
= last
= i
);
1410 t
= s
+ strlen (s
); /* end of buffer */
1415 if (last
!= start
) sprintf (t
,":%lu",last
);
1416 if (s
) { /* prefetch as needed */
1417 imap_fetch (stream
,s
,FT_NEEDENV
);
1418 fs_give ((void **) &s
);
1420 ov
.optional
.lines
= 0; /* now overview each message */
1421 ov
.optional
.xref
= NIL
;
1422 if (ofn
) for (i
= 1; i
<= stream
->nmsgs
; i
++)
1423 if (((elt
= mail_elt (stream
,i
))->sequence
) &&
1424 (env
= mail_fetch_structure (stream
,i
,NIL
,NIL
)) && ofn
) {
1425 ov
.subject
= env
->subject
;
1426 ov
.from
= env
->from
;
1427 ov
.date
= env
->date
;
1428 ov
.message_id
= env
->message_id
;
1429 ov
.references
= env
->references
;
1430 ov
.optional
.octets
= elt
->rfc822_size
;
1431 (*ofn
) (stream
,mail_uid (stream
,i
),&ov
,i
);
1436 /* IMAP fetch structure
1437 * Accepts: MAIL stream
1438 * message # to fetch
1439 * pointer to return body
1441 * Returns: envelope of this message, body returned in body value
1443 * Fetches the "fast" information as well
1446 ENVELOPE
*imap_structure (MAILSTREAM
*stream
,unsigned long msgno
,BODY
**body
,
1449 unsigned long i
,j
,k
,x
;
1450 char *s
,seq
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
1454 IMAPPARSEDREPLY
*reply
= NIL
;
1455 IMAPARG
*args
[3],aseq
,aatt
;
1456 SEARCHSET
*set
= LOCAL
->lookahead
;
1457 LOCAL
->lookahead
= NIL
;
1458 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1459 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1460 aatt
.type
= ATOM
; aatt
.text
= NIL
;
1461 if (flags
& FT_UID
) /* see if can find msgno from UID */
1462 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1463 if ((elt
= mail_elt (stream
,i
))->private.uid
== msgno
) {
1464 msgno
= i
; /* found msgno, use it from now on */
1465 flags
&= ~FT_UID
; /* no longer a UID fetch */
1467 sprintf (s
= seq
,"%lu",msgno
);/* initial sequence */
1468 if (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) {
1469 /* UID fetching is requested and we can't map the UID to a message sequence
1470 * number. Assume that the message isn't cached at all.
1472 if (!imap_OK (stream
,reply
= imap_fetch (stream
,seq
,FT_NEEDENV
+
1473 (body
? FT_NEEDBODY
: NIL
) +
1474 (flags
& (FT_UID
+ FT_NOHDRS
)))))
1475 mm_log (reply
->text
,ERROR
);
1476 /* now hunt for this UID */
1477 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1478 if ((elt
= mail_elt (stream
,i
))->private.uid
== msgno
) {
1479 if (body
) *body
= elt
->private.msg
.body
;
1480 return elt
->private.msg
.env
;
1482 if (body
) *body
= NIL
; /* can't find the UID */
1485 elt
= mail_elt (stream
,msgno
);/* get cache pointer */
1486 if (stream
->scache
) { /* short caching? */
1487 env
= &stream
->env
; /* use temporaries on the stream */
1489 if (msgno
!= stream
->msgno
){/* flush old poop if a different message */
1490 mail_free_envelope (env
);
1492 stream
->msgno
= msgno
; /* this is now the current short cache msg */
1496 else { /* normal cache */
1497 env
= &elt
->private.msg
.env
;/* get envelope and body pointers */
1498 b
= &elt
->private.msg
.body
;
1499 /* prefetch if don't have envelope */
1500 if (!(flags
& FT_NOLOOKAHEAD
) &&
1501 ((!*env
|| (*env
)->incomplete
) ||
1502 (body
&& !*b
&& LEVELIMAP2bis (stream
)))) {
1503 if (set
) { /* have a lookahead list? */
1505 for (k
= imap_fetchlookaheadlimit
;
1506 k
&& set
&& (((s
+= strlen (s
)) - seq
) < (MAXCOMMAND
- 30));
1508 i
= (set
->first
== 0xffffffff) ? stream
->nmsgs
:
1509 min (set
->first
,stream
->nmsgs
);
1510 if ((j
= (set
->last
== 0xffffffff) ? stream
->nmsgs
:
1511 min (set
->last
,stream
->nmsgs
)) != 0L) {
1512 if (i
> j
) { /* swap the range if backwards */
1513 x
= i
; i
= j
; j
= x
;
1515 /* find first message not msgno or in cache */
1516 while (((i
== msgno
) ||
1517 ((msg
= &(mail_elt (stream
,i
)->private.msg
))->env
&&
1518 (!body
|| msg
->body
))) && (i
++ < j
));
1519 /* until range or lookahead finished */
1520 while (k
&& (i
<= j
)) {
1521 /* find first cached message in range */
1522 for (x
= i
+ 1; (x
<= j
) &&
1523 !((msg
= &(mail_elt (stream
,x
)->private.msg
))->env
&&
1524 (!body
|| msg
->body
)); x
++);
1525 if (i
== --x
) { /* only one message? */
1526 sprintf (s
+= strlen (s
),",%lu",i
++);
1527 k
--; /* prefetching one message */
1529 else { /* a range to prefetch */
1530 sprintf (s
+= strlen (s
),",%lu:%lu",i
,x
);
1531 i
= 1 + x
- i
; /* number of messages in this range */
1532 /* still can look ahead some more? */
1533 if ((k
= (k
> i
) ? k
- i
: 0) != 0)
1534 /* yes, scan further in this range */
1535 for (i
= x
+ 2; (i
<= j
) &&
1537 ((msg
= &(mail_elt (stream
,i
)->private.msg
))->env
&&
1538 (!body
|| msg
->body
)));
1543 else if ((i
!= msgno
) && !mail_elt (stream
,i
)->private.msg
.env
) {
1544 sprintf (s
+= strlen (s
),",%lu",i
);
1545 k
--; /* prefetching one message */
1549 /* build message number list */
1550 else for (i
= msgno
+1,k
= imap_lookahead
; k
&& (i
<= stream
->nmsgs
); i
++)
1551 if (!mail_elt (stream
,i
)->private.msg
.env
) {
1552 s
+= strlen (s
); /* find string end, see if nearing end */
1553 if ((s
- seq
) > (MAILTMPLEN
- 20)) break;
1554 sprintf (s
,",%lu",i
); /* append message */
1555 for (j
= i
+ 1, k
--; /* hunt for last message without an envelope */
1556 k
&& (j
<= stream
->nmsgs
) &&
1557 !mail_elt (stream
,j
)->private.msg
.env
; j
++, k
--);
1558 /* if different, make a range */
1559 if (i
!= --j
) sprintf (s
+ strlen (s
),":%lu",i
= j
);
1564 if (!stream
->lock
) { /* no-op if stream locked */
1565 /* Build the fetch attributes. Unlike imap_fetch(), this tries not to
1566 * fetch data that is already cached. However, since it is based on the
1567 * message requested and not on any of the prefetched messages, it can
1568 * goof, either by fetching data already cached or not prefetching data
1569 * that isn't cached (but was cached in the message requested).
1570 * Fortunately, no great harm is done. If it doesn't prefetch the data,
1571 * it will get it when the affected message(s) are requested.
1573 if (!elt
->private.uid
&& LEVELIMAP4 (stream
)) strcpy (tmp
," UID");
1574 else tmp
[0] = '\0'; /* initialize command */
1575 /* need envelope? */
1576 if (!*env
|| (*env
)->incomplete
) {
1577 strcat (tmp
," ENVELOPE"); /* yes, get it and possible extra poop */
1578 if (!(flags
& FT_NOHDRS
) && LEVELIMAP4rev1 (stream
)) {
1579 if (imap_extrahdrs
) sprintf (tmp
+ strlen (tmp
)," %s %s %s",
1580 hdrheader
[LOCAL
->cap
.extlevel
],
1581 imap_extrahdrs
,hdrtrailer
);
1582 else sprintf (tmp
+ strlen (tmp
)," %s %s",
1583 hdrheader
[LOCAL
->cap
.extlevel
],hdrtrailer
);
1587 if (body
&& !*b
&& LEVELIMAP2bis (stream
))
1588 strcat (tmp
,LEVELIMAP4 (stream
) ? " BODYSTRUCTURE" : " BODY");
1589 if (!elt
->day
) strcat (tmp
," INTERNALDATE");
1590 if (!elt
->rfc822_size
) strcat (tmp
," RFC822.SIZE");
1591 if (tmp
[0]) { /* anything to do? */
1592 tmp
[0] = '('; /* make into a list */
1593 strcat (tmp
," FLAGS)"); /* always get current flags */
1594 aatt
.text
= (void *) tmp
; /* do the built command */
1595 if (!imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
))) {
1596 /* failed, probably RFC-1176 server */
1597 if (!LEVELIMAP4 (stream
) && LEVELIMAP2bis (stream
) && body
&& !*b
){
1598 aatt
.text
= (void *) "ALL";
1599 if (imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
)))
1600 /* doesn't have body capabilities */
1601 LOCAL
->cap
.imap2bis
= NIL
;
1602 else mm_log (reply
->text
,ERROR
);
1604 else mm_log (reply
->text
,ERROR
);
1608 if (body
) { /* wants to return body */
1609 if (!*b
&& !LEVELIMAP2bis (stream
)) {
1610 /* simulate body structure fetch for IMAP2 */
1611 *b
= mail_initbody (mail_newbody ());
1612 (*b
)->subtype
= cpystr (rfc822_default_subtype ((*b
)->type
));
1613 ((*b
)->parameter
= mail_newbody_parameter ())->attribute
=
1615 (*b
)->parameter
->value
= cpystr ("US-ASCII");
1616 s
= mail_fetch_text (stream
,msgno
,NIL
,&i
,flags
);
1617 (*b
)->size
.bytes
= i
;
1618 while (i
--) if (*s
++ == '\n') (*b
)->size
.lines
++;
1620 *body
= *b
; /* return the body */
1622 return *env
; /* return the envelope */
1625 /* IMAP fetch message data
1626 * Accepts: MAIL stream
1629 * offset of first designated byte or 0 to start at beginning
1630 * maximum number of bytes or 0 for all bytes
1631 * lines to fetch if header
1633 * Returns: T on success, NIL on failure
1636 long imap_msgdata (MAILSTREAM
*stream
,unsigned long msgno
,char *section
,
1637 unsigned long first
,unsigned long last
,STRINGLIST
*lines
,
1641 char *t
,tmp
[MAILTMPLEN
],partial
[40],seq
[40];
1642 char *noextend
,*nopartial
,*nolines
,*nopeek
,*nononpeek
;
1643 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ? "UID FETCH":"FETCH";
1644 IMAPPARSEDREPLY
*reply
;
1645 IMAPARG
*args
[5],*auxargs
[3],aseq
,aatt
,alns
,acls
,aflg
;
1646 noextend
= nopartial
= nolines
= nopeek
= nononpeek
= NIL
;
1647 /* does searching desire a lookahead? */
1648 if ((flags
& FT_SEARCHLOOKAHEAD
) && (msgno
< stream
->nmsgs
) &&
1650 sprintf (seq
,"%lu:%lu",msgno
,
1651 (unsigned long) min (msgno
+ IMAPLOOKAHEAD
,stream
->nmsgs
));
1652 aseq
.type
= SEQUENCE
;
1653 aseq
.text
= (void *) seq
;
1655 else { /* no, do it the easy way */
1657 aseq
.text
= (void *) msgno
;
1659 aatt
.type
= ATOM
; /* assume atomic attribute */
1660 alns
.type
= LIST
; alns
.text
= (void *) lines
;
1661 acls
.type
= BODYCLOSE
; acls
.text
= (void *) partial
;
1662 aflg
.type
= ATOM
; aflg
.text
= (void *) "FLAGS";
1663 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = args
[3] = args
[4] = NIL
;
1664 auxargs
[0] = &aseq
; auxargs
[1] = &aflg
; auxargs
[2] = NIL
;
1665 partial
[0] = '\0'; /* initially no partial specifier */
1666 if (LEVELIMAP4rev1 (stream
)) {/* easy if IMAP4rev1 server */
1667 /* HEADER fetching with special handling? */
1668 if (!strcmp (section
,"HEADER") && (lines
|| (flags
& FT_PREFETCHTEXT
))) {
1669 if (lines
) { /* want specific header lines? */
1670 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1671 aatt
.text
= (void *) ((flags
& FT_NOT
) ?
1672 "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1673 args
[2] = &alns
; args
[3] = &acls
;
1675 /* must be prefetching */
1676 else aatt
.text
= (void *) ((flags
& FT_PEEK
) ?
1677 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1678 "(BODY[HEADER] BODY[TEXT])");
1680 else { /* simple case */
1681 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1682 aatt
.text
= (void *) section
;
1685 if (first
|| last
) sprintf (partial
,"<%lu.%lu>",first
,last
? last
:-1);
1688 /* IMAP4 did not have:
1689 * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1690 * . TEXT body part (can simulate top-level with RFC822.TEXT or
1693 * . (usable) partial fetching
1694 * . (usable) selective header line fetching
1696 else if (LEVEL1730 (stream
)) {/* IMAP4 (RFC 1730) compatibility */
1697 /* BODY[HEADER] becomes BODY.PEEK[0] */
1698 if (!strcmp (section
,"HEADER"))
1699 aatt
.text
= (void *)
1700 ((flags
& FT_PREFETCHTEXT
) ?
1701 ((flags
& FT_PEEK
) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1702 "(BODY[0] RFC822.TEXT)") :
1703 ((flags
& FT_PEEK
) ? "BODY.PEEK[0]" : "BODY[0]"));
1704 /* BODY[TEXT] becomes RFC822.TEXT */
1705 else if (!strcmp (section
,"TEXT"))
1706 aatt
.text
= (void *) ((flags
& FT_PEEK
) ? "RFC822.TEXT.PEEK" :
1708 else if (!section
[0]) /* BODY[] becomes RFC822 */
1709 aatt
.text
= (void *) ((flags
& FT_PEEK
) ? "RFC822.PEEK" : "RFC822");
1711 else if ((t
= strstr (section
,".HEADER")) != NULL
) {
1712 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1713 args
[2] = &acls
; /* will need to close section */
1714 aatt
.text
= (void *) tmp
; /* convert .HEADER to .0 */
1715 strncpy (tmp
,section
,t
-section
);
1716 strcpy (tmp
+(t
-section
),".0");
1718 else { /* IMAP4 body part */
1719 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1720 args
[2] = &acls
; /* will need to close section */
1721 aatt
.text
= (void *) section
;
1723 if (strstr (section
,".MIME") || strstr (section
,".TEXT")) noextend
= "4";
1724 if (first
|| last
) nopartial
= "4";
1725 if (lines
) nolines
= "4";
1728 /* IMAP2bis did not have:
1729 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1730 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1732 * . partial fetching
1733 * . selective header line fetching
1734 * . non-peeking header fetching
1735 * . peeking body fetching
1737 /* IMAP2bis compatibility */
1738 else if (LEVELIMAP2bis (stream
)) {
1739 /* BODY[HEADER] becomes RFC822.HEADER */
1740 if (!strcmp (section
,"HEADER")) {
1741 aatt
.text
= (void *)
1742 ((flags
& FT_PREFETCHTEXT
) ?
1743 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1744 if (flags
& FT_PEEK
) flags
&= ~FT_PEEK
;
1745 else nononpeek
= "2bis";
1747 /* BODY[TEXT] becomes RFC822.TEXT */
1748 else if (!strcmp (section
,"TEXT")) aatt
.text
= (void *) "RFC822.TEXT";
1749 /* BODY[] becomes RFC822 */
1750 else if (!section
[0]) aatt
.text
= (void *) "RFC822";
1751 else { /* IMAP2bis body part */
1752 aatt
.type
= BODYTEXT
;
1753 args
[2] = &acls
; /* will need to close section */
1754 aatt
.text
= (void *) section
;
1756 if (strstr (section
,".HEADER") || strstr (section
,".MIME") ||
1757 strstr (section
,".TEXT")) noextend
= "2bis";
1758 if (first
|| last
) nopartial
= "2bis";
1759 if (lines
) nolines
= "2bis";
1760 if (flags
& FT_PEEK
) nopeek
= "2bis";
1763 /* IMAP2 did not have:
1764 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1765 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1767 * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1768 * . partial fetching
1769 * . selective header line fetching
1770 * . non-peeking header fetching
1771 * . peeking body fetching
1773 else { /* IMAP2 (RFC 1176/1064) compatibility */
1775 if (!strcmp (section
,"HEADER")) {
1776 aatt
.text
= (void *) ((flags
& FT_PREFETCHTEXT
) ?
1777 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1778 if (flags
& FT_PEEK
) flags
&= ~FT_PEEK
;
1781 /* BODY[TEXT] becomes RFC822.TEXT */
1782 else if (!strcmp (section
,"TEXT")) aatt
.text
= (void *) "RFC822.TEXT";
1783 /* BODY[1] treated like RFC822.TEXT */
1784 else if (!strcmp (section
,"1")) {
1786 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1787 /* have a cached RFC822.TEXT? */
1788 if (elt
->private.msg
.text
.text
.data
) {
1789 text
.size
= elt
->private.msg
.text
.text
.size
;
1790 /* should move instead of copy */
1791 text
.data
= memcpy (fs_get (text
.size
+1),
1792 elt
->private.msg
.text
.text
.data
,text
.size
);
1793 (t
= (char *) text
.data
)[text
.size
] = '\0';
1794 imap_cache (stream
,msgno
,"1",NIL
,&text
);
1795 return LONGT
; /* don't have to do any fetches */
1797 /* otherwise do RFC822.TEXT */
1798 aatt
.text
= (void *) "RFC822.TEXT";
1800 /* BODY[] becomes RFC822 */
1801 else if (!section
[0]) aatt
.text
= (void *) "RFC822";
1802 else noextend
= "2"; /* how did we get here? */
1803 if (flags
& FT_PEEK
) nopeek
= "2";
1804 if (first
|| last
) nopartial
= "2";
1805 if (lines
) nolines
= "2";
1808 /* Report unavailable functionalities. The application can use the helpful
1809 * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1810 * imap4r1.h to avoid triggering these errors. There aren't any workarounds
1811 * for these restrictions.
1814 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1817 return NIL
; /* can't do anything close either */
1820 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1822 mm_notify (stream
,tmp
,WARN
);
1825 sprintf(tmp
,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1827 mm_notify (stream
,tmp
,WARN
);
1830 /* trying to do unsupported peek behavior? */
1831 if ((t
= nopeek
) || (t
= nononpeek
)) {
1832 /* get most recent \Seen setting */
1833 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,auxargs
)))
1834 mm_log (reply
->text
,WARN
);
1835 /* note current setting of \Seen flag */
1836 if (!(i
= mail_elt (stream
,msgno
)->seen
)) {
1837 sprintf (tmp
,nopeek
? /* only babble if \Seen not set */
1838 "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1839 "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t
);
1840 mm_notify (stream
,tmp
,NIL
);
1842 /* send the fetch command */
1843 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
))) {
1844 mm_log (reply
->text
,ERROR
);
1845 return NIL
; /* failure */
1847 /* send command if need to reset \Seen */
1848 if (((nopeek
&& !i
&& mail_elt (stream
,msgno
)->seen
&&
1849 (aflg
.text
= "-FLAGS \\Seen")) ||
1850 ((nononpeek
&& !mail_elt (stream
,msgno
)->seen
) &&
1851 (aflg
.text
= "+FLAGS \\Seen"))) &&
1852 !imap_OK (stream
,reply
= imap_send (stream
,"STORE",auxargs
)))
1853 mm_log (reply
->text
,WARN
);
1855 /* simple case if traditional behavior */
1856 else if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
))) {
1857 mm_log (reply
->text
,ERROR
);
1858 return NIL
; /* failure */
1860 /* simulate BODY[1] return for RFC 1064/1176 */
1861 if (!LEVELIMAP2bis (stream
) && !strcmp (section
,"1")) {
1863 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1864 text
.size
= elt
->private.msg
.text
.text
.size
;
1865 /* should move instead of copy */
1866 text
.data
= memcpy (fs_get (text
.size
+1),elt
->private.msg
.text
.text
.data
,
1868 (t
= (char *) text
.data
)[text
.size
] = '\0';
1869 imap_cache (stream
,msgno
,"1",NIL
,&text
);
1875 * Accepts: MAIL stream
1880 unsigned long imap_uid (MAILSTREAM
*stream
,unsigned long msgno
)
1883 IMAPPARSEDREPLY
*reply
;
1884 IMAPARG
*args
[3],aseq
,aatt
;
1885 char *s
,seq
[MAILTMPLEN
];
1886 unsigned long i
,j
,k
;
1887 /* IMAP2 didn't have UIDs */
1888 if (!LEVELIMAP4 (stream
)) return msgno
;
1889 /* do we know its UID yet? */
1890 if (!(elt
= mail_elt (stream
,msgno
))->private.uid
) {
1891 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1892 aatt
.type
= ATOM
; aatt
.text
= (void *) "UID";
1893 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1894 sprintf (seq
,"%lu",msgno
);
1895 if ((k
= imap_uidlookahead
) != 0L) {/* build UID list */
1896 for (i
= msgno
+ 1, s
= seq
; k
&& (i
<= stream
->nmsgs
); i
++)
1897 if (!mail_elt (stream
,i
)->private.uid
) {
1898 s
+= strlen (s
); /* find string end, see if nearing end */
1899 if ((s
- seq
) > (MAILTMPLEN
- 20)) break;
1900 sprintf (s
,",%lu",i
); /* append message */
1901 for (j
= i
+ 1, k
--; /* hunt for last message without a UID */
1902 k
&& (j
<= stream
->nmsgs
) && !mail_elt (stream
,j
)->private.uid
;
1904 /* if different, make a range */
1905 if (i
!= --j
) sprintf (s
+ strlen (s
),":%lu",i
= j
);
1908 /* send "FETCH msgno UID" */
1909 if (!imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
)))
1910 mm_log (reply
->text
,ERROR
);
1912 return elt
->private.uid
; /* return our UID now */
1915 /* IMAP fetch message number from UID
1916 * Accepts: MAIL stream
1918 * Returns: message number
1921 unsigned long imap_msgno (MAILSTREAM
*stream
,unsigned long uid
)
1923 IMAPPARSEDREPLY
*reply
;
1924 IMAPARG
*args
[3],aseq
,aatt
;
1925 char seq
[MAILTMPLEN
];
1927 unsigned long i
,msgno
;
1928 /* IMAP2 didn't have UIDs */
1929 if (!LEVELIMAP4 (stream
)) return uid
;
1930 /* This really should be a binary search, but since there are likely to be
1931 * holes in the msgno->UID map it's hard to do.
1933 for (msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++) {
1934 if (!(i
= mail_elt (stream
,msgno
)->private.uid
)) holes
= T
;
1935 else if (i
== uid
) return msgno
;
1937 if (holes
) { /* have holes in cache? */
1938 /* yes, have server hunt for UID */
1939 LOCAL
->lastuid
.uid
= LOCAL
->lastuid
.msgno
= 0;
1940 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1941 aatt
.type
= ATOM
; aatt
.text
= (void *) "UID";
1942 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1943 sprintf (seq
,"%lu",uid
);
1944 /* send "UID FETCH uid UID" */
1945 if (!imap_OK (stream
,reply
= imap_send (stream
,"UID FETCH",args
)))
1946 mm_log (reply
->text
,ERROR
);
1947 if (LOCAL
->lastuid
.uid
) { /* got any results from FETCH? */
1948 if ((LOCAL
->lastuid
.uid
== uid
) &&
1949 /* what, me paranoid? */
1950 (LOCAL
->lastuid
.msgno
<= stream
->nmsgs
) &&
1951 (mail_elt (stream
,LOCAL
->lastuid
.msgno
)->private.uid
== uid
))
1952 /* got it the easy way */
1953 return LOCAL
->lastuid
.msgno
;
1954 /* sigh, do another linear search... */
1955 for (msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++)
1956 if (mail_elt (stream
,msgno
)->private.uid
== uid
) return msgno
;
1959 return 0; /* didn't find the UID anywhere */
1962 /* IMAP modify flags
1963 * Accepts: MAIL stream
1969 void imap_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
1971 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& ST_UID
)) ? "UID STORE":"STORE";
1972 IMAPPARSEDREPLY
*reply
;
1973 IMAPARG
*args
[4],aseq
,ascm
,aflg
;
1974 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
1976 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
1977 ascm
.type
= ATOM
; ascm
.text
= (void *)
1979 ((LEVELIMAP4 (stream
) && (flags
& ST_SILENT
)) ?
1980 "+Flags.silent" : "+Flags") :
1981 ((LEVELIMAP4 (stream
) && (flags
& ST_SILENT
)) ?
1982 "-Flags.silent" : "-Flags"));
1983 aflg
.type
= FLAGS
; aflg
.text
= (void *) flag
;
1984 args
[0] = &aseq
; args
[1] = &ascm
; args
[2] = &aflg
; args
[3] = NIL
;
1985 /* send "STORE sequence +Flags flag" */
1986 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
)))
1987 mm_log (reply
->text
,ERROR
);
1990 /* IMAP search for messages
1991 * Accepts: MAIL stream
1995 * Returns: T on success, NIL on failure
1998 long imap_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
)
2000 unsigned long i
,j
,k
;
2002 IMAPPARSEDREPLY
*reply
;
2004 if ((flags
& SE_NOSERVER
) || /* if want to do local search */
2005 LOCAL
->loser
|| /* or loser */
2006 (!LEVELIMAP4 (stream
) && /* or old server but new functions... */
2007 (charset
|| (flags
& SE_UID
) || pgm
->msgno
|| pgm
->uid
|| pgm
->or ||
2008 pgm
->not || pgm
->header
|| pgm
->larger
|| pgm
->smaller
||
2009 pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
|| pgm
->draft
||
2010 pgm
->undraft
|| pgm
->return_path
|| pgm
->sender
|| pgm
->reply_to
||
2011 pgm
->message_id
|| pgm
->in_reply_to
|| pgm
->newsgroups
||
2012 pgm
->followup_to
|| pgm
->references
)) ||
2013 (!LEVELWITHIN (stream
) && (pgm
->older
|| pgm
->younger
))) {
2014 if ((flags
& SE_NOLOCAL
) ||
2015 !mail_search_default (stream
,charset
,pgm
,flags
| SE_NOSERVER
))
2018 /* do silly ALL or seq-only search locally */
2019 else if (!(flags
& (SE_NOLOCAL
|SE_SILLYOK
)) &&
2020 !(pgm
->uid
|| pgm
->or || pgm
->not ||
2021 pgm
->header
|| pgm
->from
|| pgm
->to
|| pgm
->cc
|| pgm
->bcc
||
2022 pgm
->subject
|| pgm
->body
|| pgm
->text
||
2023 pgm
->larger
|| pgm
->smaller
||
2024 pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
||
2025 pgm
->before
|| pgm
->on
|| pgm
->since
||
2026 pgm
->answered
|| pgm
->unanswered
||
2027 pgm
->deleted
|| pgm
->undeleted
|| pgm
->draft
|| pgm
->undraft
||
2028 pgm
->flagged
|| pgm
->unflagged
|| pgm
->recent
|| pgm
->old
||
2029 pgm
->seen
|| pgm
->unseen
||
2030 pgm
->keyword
|| pgm
->unkeyword
||
2031 pgm
->return_path
|| pgm
->sender
||
2032 pgm
->reply_to
|| pgm
->in_reply_to
|| pgm
->message_id
||
2033 pgm
->newsgroups
|| pgm
->followup_to
|| pgm
->references
)) {
2034 if (!mail_search_default (stream
,NIL
,pgm
,flags
| SE_NOSERVER
))
2035 fatal ("impossible mail_search_default() failure");
2038 else { /* do server-based SEARCH */
2039 char *cmd
= (flags
& SE_UID
) ? "UID SEARCH" : "SEARCH";
2040 IMAPARG
*args
[4],apgm
,aatt
,achs
;
2042 args
[1] = args
[2] = args
[3] = NIL
;
2043 apgm
.type
= SEARCHPROGRAM
; apgm
.text
= (void *) pgm
;
2044 if (charset
) { /* optional charset argument requested */
2045 args
[0] = &aatt
; args
[1] = &achs
; args
[2] = &apgm
;
2046 aatt
.type
= ATOM
; aatt
.text
= (void *) "CHARSET";
2047 achs
.type
= ASTRING
; achs
.text
= (void *) charset
;
2049 else args
[0] = &apgm
; /* no charset argument */
2050 /* tell receiver that these will be UIDs */
2051 LOCAL
->uidsearch
= (flags
& SE_UID
) ? T
: NIL
;
2052 reply
= imap_send (stream
,cmd
,args
);
2053 /* did server barf with that searchpgm? */
2054 if (!(flags
& SE_UID
) && pgm
&& (ss
= pgm
->msgno
) &&
2055 !strcmp (reply
->key
,"BAD")) {
2056 LOCAL
->filter
= T
; /* retry, filtering SEARCH results */
2057 for (i
= 1; i
<= stream
->nmsgs
; i
++)
2058 mail_elt (stream
,i
)->private.filter
= NIL
;
2059 for (set
= ss
; set
; set
= set
->next
) if ((i
= set
->first
) != 0L) {
2060 /* single message becomes one-message range */
2061 if (!(j
= set
->last
)) j
= i
;
2062 else if (j
< i
) { /* swap reversed range */
2063 i
= set
->last
; j
= set
->first
;
2065 while (i
<= j
) mail_elt (stream
,i
++)->private.filter
= T
;
2067 pgm
->msgno
= NIL
; /* and without the searchset */
2068 reply
= imap_send (stream
,cmd
,args
);
2069 pgm
->msgno
= ss
; /* restore searchset */
2070 LOCAL
->filter
= NIL
; /* turn off filtering */
2072 LOCAL
->uidsearch
= NIL
;
2073 /* do locally if server won't grok */
2074 if (!strcmp (reply
->key
,"BAD")) {
2075 if ((flags
& SE_NOLOCAL
) ||
2076 !mail_search_default (stream
,charset
,pgm
,flags
| SE_NOSERVER
))
2079 else if (!imap_OK (stream
,reply
)) {
2080 mm_log (reply
->text
,ERROR
);
2085 /* can never pre-fetch with a short cache */
2086 if ((k
= imap_prefetch
) && !(flags
& (SE_NOPREFETCH
| SE_UID
)) &&
2087 !stream
->scache
) { /* only if prefetching permitted */
2088 s
= LOCAL
->tmp
; /* build sequence in temporary buffer */
2089 *s
= '\0'; /* initially nothing */
2090 /* search through mailbox */
2091 for (i
= 1; k
&& (i
<= stream
->nmsgs
); ++i
)
2092 /* for searched messages with no envelope */
2093 if ((elt
= mail_elt (stream
,i
)) && elt
->searched
&&
2094 !mail_elt (stream
,i
)->private.msg
.env
) {
2095 /* prepend with comma if not first time */
2096 if (LOCAL
->tmp
[0]) *s
++ = ',';
2097 sprintf (s
,"%lu",j
= i
);/* output message number */
2098 s
+= strlen (s
); /* point at end of string */
2099 k
--; /* count one up */
2100 /* search for possible end of range */
2101 while (k
&& (i
< stream
->nmsgs
) &&
2102 (elt
= mail_elt (stream
,i
+1))->searched
&&
2103 !elt
->private.msg
.env
) i
++,k
--;
2104 if (i
!= j
) { /* if a range */
2105 sprintf (s
,":%lu",i
); /* output delimiter and end of range */
2106 s
+= strlen (s
); /* point at end of string */
2108 if ((s
- LOCAL
->tmp
) > (IMAPTMPLEN
- 50)) break;
2110 if (LOCAL
->tmp
[0]) { /* anything to pre-fetch? */
2111 /* pre-fetch envelopes for the first imap_prefetch number of messages */
2112 if (!imap_OK (stream
,reply
=
2113 imap_fetch (stream
,s
= cpystr (LOCAL
->tmp
),FT_NEEDENV
+
2114 ((flags
& SE_NOHDRS
) ? FT_NOHDRS
: NIL
) +
2115 ((flags
& SE_NEEDBODY
) ? FT_NEEDBODY
: NIL
))))
2116 mm_log (reply
->text
,ERROR
);
2117 fs_give ((void **) &s
); /* flush copy of sequence */
2123 /* IMAP sort messages
2124 * Accepts: mail stream
2129 * Returns: vector of sorted message sequences or NIL if error
2132 unsigned long *imap_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
2133 SORTPGM
*pgm
,long flags
)
2135 unsigned long i
,j
,start
,last
;
2136 unsigned long *ret
= NIL
;
2137 pgm
->nmsgs
= 0; /* start off with no messages */
2138 /* can use server-based sort? */
2139 if (LEVELSORT (stream
) && !(flags
& SE_NOSERVER
) &&
2140 (!spg
|| (LEVELWITHIN (stream
) || !(spg
->older
|| spg
->younger
)))) {
2141 char *cmd
= (flags
& SE_UID
) ? "UID SORT" : "SORT";
2142 IMAPARG
*args
[4],apgm
,achs
,aspg
;
2143 IMAPPARSEDREPLY
*reply
;
2144 SEARCHSET
*ss
= NIL
;
2145 SEARCHPGM
*tsp
= NIL
;
2146 apgm
.type
= SORTPROGRAM
; apgm
.text
= (void *) pgm
;
2147 achs
.type
= ASTRING
; achs
.text
= (void *) (charset
? charset
: "US-ASCII");
2148 aspg
.type
= SEARCHPROGRAM
;
2149 /* did he provide a searchpgm? */
2150 if (!(aspg
.text
= (void *) spg
)) {
2151 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
2152 if (mail_elt (stream
,i
)->searched
) {
2153 if (ss
) { /* continuing a sequence */
2154 if (i
== last
+ 1) last
= i
;
2155 else { /* end of range */
2156 if (last
!= start
) ss
->last
= last
;
2157 (ss
= ss
->next
= mail_newsearchset ())->first
= i
;
2158 start
= last
= i
; /* begin a new range */
2161 else { /* first time, start new searchpgm */
2162 (tsp
= mail_newsearchpgm ())->msgno
= ss
= mail_newsearchset ();
2163 ss
->first
= start
= last
= i
;
2166 /* nothing to sort if no messages */
2167 if (!(aspg
.text
= (void *) tsp
)) return NIL
;
2168 /* else install last sequence */
2169 if (last
!= start
) ss
->last
= last
;
2172 args
[0] = &apgm
; args
[1] = &achs
; args
[2] = &aspg
; args
[3] = NIL
;
2173 /* ask server to do it */
2174 reply
= imap_send (stream
,cmd
,args
);
2175 if (tsp
) { /* was there a temporary searchpgm? */
2176 aspg
.text
= NIL
; /* yes, flush it */
2177 mail_free_searchpgm (&tsp
);
2178 /* did server barf with that searchpgm? */
2179 if (!(flags
& SE_UID
) && !strcmp (reply
->key
,"BAD")) {
2180 LOCAL
->filter
= T
; /* retry, filtering SORT/THREAD results */
2181 reply
= imap_send (stream
,cmd
,args
);
2182 LOCAL
->filter
= NIL
; /* turn off filtering */
2185 /* do locally if server barfs */
2186 if (!strcmp (reply
->key
,"BAD"))
2187 return (flags
& SE_NOLOCAL
) ? NIL
:
2188 imap_sort (stream
,charset
,spg
,pgm
,flags
| SE_NOSERVER
);
2189 /* server sorted OK? */
2190 else if (imap_OK (stream
,reply
)) {
2191 pgm
->nmsgs
= LOCAL
->sortsize
;
2192 ret
= LOCAL
->sortdata
;
2193 LOCAL
->sortdata
= NIL
; /* mail program is responsible for flushing */
2195 else mm_log (reply
->text
,ERROR
);
2198 /* not much can do if short caching */
2199 else if (stream
->scache
) ret
= mail_sort_msgs (stream
,charset
,spg
,pgm
,flags
);
2200 else { /* try to be a bit more clever */
2207 /* see if need envelopes */
2208 for (sp
= pgm
; sp
&& !ftflags
; sp
= sp
->next
) switch (sp
->function
) {
2209 case SORTDATE
: case SORTFROM
: case SORTSUBJECT
: case SORTTO
: case SORTCC
:
2210 ftflags
= FT_NEEDENV
+ ((flags
& SE_NOHDRS
) ? FT_NOHDRS
: NIL
);
2212 if (spg
) { /* only if a search needs to be done */
2213 int silent
= stream
->silent
;
2214 stream
->silent
= T
; /* don't pass up mm_searched() events */
2215 /* search for messages */
2216 mail_search_full (stream
,charset
,spg
,flags
& SE_NOSERVER
);
2217 stream
->silent
= silent
; /* restore silence state */
2219 /* initialize progress counters */
2220 pgm
->nmsgs
= pgm
->progress
.cached
= 0;
2221 /* pass 1: count messages to sort */
2222 for (i
= 1,len
= start
= last
= 0,s
= t
= NIL
; i
<= stream
->nmsgs
; ++i
)
2223 if ((elt
= mail_elt (stream
,i
))->searched
) {
2225 if (ftflags
? !elt
->private.msg
.env
: !elt
->day
) {
2226 if (s
) { /* continuing a sequence */
2227 if (i
== last
+ 1) last
= i
;
2228 else { /* end of range */
2229 if (last
!= start
) sprintf (t
,":%lu,%lu",last
,i
);
2230 else sprintf (t
,",%lu",i
);
2231 start
= last
= i
; /* begin a new range */
2232 if ((len
- (j
= ((t
+= strlen (t
)) - s
)) < 20)) {
2233 fs_resize ((void **) &s
,len
+= MAILTMPLEN
);
2234 t
= s
+ j
; /* relocate current pointer */
2238 else { /* first time, start new buffer */
2239 s
= (char *) fs_get (len
= MAILTMPLEN
);
2240 sprintf (s
,"%lu",start
= last
= i
);
2241 t
= s
+ strlen (s
); /* end of buffer */
2246 if (last
!= start
) sprintf (t
,":%lu",last
);
2247 if (s
) { /* load cache for all messages being sorted */
2248 imap_fetch (stream
,s
,ftflags
);
2249 fs_give ((void **) &s
);
2251 if (pgm
->nmsgs
) { /* pass 2: sort cache */
2252 sortresults_t sr
= (sortresults_t
)
2253 mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
2254 sc
= mail_sort_loadcache (stream
,pgm
);
2255 /* pass 3: sort messages */
2256 if (!pgm
->abort
) ret
= mail_sort_cache (stream
,pgm
,sc
,flags
);
2257 fs_give ((void **) &sc
); /* don't need sort vector any more */
2258 /* also return via callback if requested */
2259 if (sr
) (*sr
) (stream
,ret
,pgm
->nmsgs
);
2265 /* IMAP thread messages
2266 * Accepts: mail stream
2271 * Returns: thread node tree or NIL if error
2274 THREADNODE
*imap_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
2275 SEARCHPGM
*spg
,long flags
)
2278 if (!(flags
& SE_NOSERVER
) &&
2279 (!spg
|| (LEVELWITHIN (stream
) || !(spg
->older
|| spg
->younger
))))
2280 /* does server have this threader type? */
2281 for (thr
= LOCAL
->cap
.threader
; thr
; thr
= thr
->next
)
2282 if (!compare_cstring (thr
->name
,type
))
2283 return imap_thread_work (stream
,type
,charset
,spg
,flags
);
2284 /* server doesn't support it, do locally */
2285 return (flags
& SE_NOLOCAL
) ? NIL
:
2286 mail_thread_msgs (stream
,type
,charset
,spg
,flags
| SE_NOSERVER
,imap_sort
);
2289 /* IMAP thread messages worker routine
2290 * Accepts: mail stream
2295 * Returns: thread node tree
2298 THREADNODE
*imap_thread_work (MAILSTREAM
*stream
,char *type
,char *charset
,
2299 SEARCHPGM
*spg
,long flags
)
2301 unsigned long i
,start
,last
;
2302 char *cmd
= (flags
& SE_UID
) ? "UID THREAD" : "THREAD";
2303 IMAPARG
*args
[4],apgm
,achs
,aspg
;
2304 IMAPPARSEDREPLY
*reply
;
2305 THREADNODE
*ret
= NIL
;
2306 SEARCHSET
*ss
= NIL
;
2307 SEARCHPGM
*tsp
= NIL
;
2308 apgm
.type
= ATOM
; apgm
.text
= (void *) type
;
2309 achs
.type
= ASTRING
;
2310 achs
.text
= (void *) (charset
? charset
: "US-ASCII");
2311 aspg
.type
= SEARCHPROGRAM
;
2312 /* did he provide a searchpgm? */
2313 if (!(aspg
.text
= (void *) spg
)) {
2314 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
2315 if (mail_elt (stream
,i
)->searched
) {
2316 if (ss
) { /* continuing a sequence */
2317 if (i
== last
+ 1) last
= i
;
2318 else { /* end of range */
2319 if (last
!= start
) ss
->last
= last
;
2320 (ss
= ss
->next
= mail_newsearchset ())->first
= i
;
2321 start
= last
=i
; /* begin a new range */
2324 else { /* first time, start new searchpgm */
2325 (tsp
= mail_newsearchpgm ())->msgno
= ss
= mail_newsearchset ();
2326 ss
->first
= start
= last
= i
;
2329 /* nothing to sort if no messages */
2330 if (!(aspg
.text
= (void *) tsp
)) return NIL
;
2331 /* else install last sequence */
2332 if (last
!= start
) ss
->last
= last
;
2335 args
[0] = &apgm
; args
[1] = &achs
; args
[2] = &aspg
; args
[3] = NIL
;
2336 /* ask server to do it */
2337 reply
= imap_send (stream
,cmd
,args
);
2338 if (tsp
) { /* was there a temporary searchpgm? */
2339 aspg
.text
= NIL
; /* yes, flush it */
2340 mail_free_searchpgm (&tsp
);
2341 /* did server barf with that searchpgm? */
2342 if (!(flags
& SE_UID
) && !strcmp (reply
->key
,"BAD")) {
2343 LOCAL
->filter
= T
; /* retry, filtering SORT/THREAD results */
2344 reply
= imap_send (stream
,cmd
,args
);
2345 LOCAL
->filter
= NIL
; /* turn off filtering */
2348 /* do locally if server barfs */
2349 if (!strcmp (reply
->key
,"BAD"))
2350 ret
= (flags
& SE_NOLOCAL
) ? NIL
:
2351 mail_thread_msgs (stream
,type
,charset
,spg
,flags
| SE_NOSERVER
,imap_sort
);
2352 /* server threaded OK? */
2353 else if (imap_OK (stream
,reply
)) {
2354 ret
= LOCAL
->threaddata
;
2355 LOCAL
->threaddata
= NIL
; /* mail program is responsible for flushing */
2357 else mm_log (reply
->text
,ERROR
);
2361 /* IMAP ping mailbox
2362 * Accepts: MAIL stream
2363 * Returns: T if stream still alive, else NIL
2366 long imap_ping (MAILSTREAM
*stream
)
2368 return (LOCAL
->netstream
&& /* send "NOOP" */
2369 imap_OK (stream
,imap_send (stream
,"NOOP",NIL
))) ? T
: NIL
;
2373 /* IMAP check mailbox
2374 * Accepts: MAIL stream
2377 void imap_check (MAILSTREAM
*stream
)
2380 IMAPPARSEDREPLY
*reply
= imap_send (stream
,"CHECK",NIL
);
2381 mm_log (reply
->text
,imap_OK (stream
,reply
) ? (long) NIL
: ERROR
);
2384 /* IMAP expunge mailbox
2385 * Accepts: MAIL stream
2386 * sequence to expunge if non-NIL
2388 * Returns: T if success, NIL if failure
2391 long imap_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
2394 IMAPPARSEDREPLY
*reply
= NIL
;
2395 if (sequence
) { /* wants selective expunging? */
2396 if (options
& EX_UID
) { /* UID EXPUNGE form? */
2397 if (LEVELUIDPLUS (stream
)) {/* server support UIDPLUS? */
2398 IMAPARG
*args
[2],aseq
;
2399 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
2400 args
[0] = &aseq
; args
[1] = NIL
;
2401 ret
= imap_OK (stream
,reply
= imap_send (stream
,"UID EXPUNGE",args
));
2403 else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR
);
2405 /* otherwise try to make into UID EXPUNGE */
2406 else if (mail_sequence (stream
,sequence
)) {
2408 char *t
= (char *) fs_get (IMAPTMPLEN
);
2410 /* search through mailbox */
2411 for (*s
= '\0', i
= 1; i
<= stream
->nmsgs
; ++i
)
2412 if (mail_elt (stream
,i
)->sequence
) {
2413 if (t
[0]) *s
++ = ','; /* prepend with comma if not first time */
2414 sprintf (s
,"%lu",mail_uid (stream
,j
= i
));
2415 s
+= strlen (s
); /* point at end of string */
2416 /* search for possible end of range */
2417 while ((i
< stream
->nmsgs
) && mail_elt (stream
,i
+1)->sequence
) i
++;
2418 if (i
!= j
) { /* output end of range */
2419 sprintf (s
,":%lu",mail_uid (stream
,i
));
2420 s
+= strlen (s
); /* point at end of string */
2422 if ((s
- t
) > (IMAPTMPLEN
- 50)) {
2423 mm_log ("Excessively complex sequence",ERROR
);
2427 /* now do as UID EXPUNGE */
2428 ret
= imap_expunge (stream
,t
,EX_UID
);
2429 fs_give ((void **) &t
);
2432 /* ordinary EXPUNGE */
2433 else ret
= imap_OK (stream
,reply
= imap_send (stream
,"EXPUNGE",NIL
));
2434 if (reply
) mm_log (reply
->text
,ret
? (long) NIL
: ERROR
);
2438 /* IMAP copy message(s)
2439 * Accepts: MAIL stream
2441 * destination mailbox
2443 * Returns: T if successful else NIL
2446 long imap_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long flags
)
2448 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& CP_UID
)) ? "UID COPY" : "COPY";
2451 IMAPPARSEDREPLY
*reply
;
2452 IMAPARG
*args
[3],aseq
,ambx
;
2454 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
2455 mailproxycopy_t pc
=
2456 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
2457 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
2459 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
2460 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2461 args
[0] = &aseq
; args
[1] = &ambx
; args
[2] = NIL
;
2462 /* note mailbox in case APPENDUID */
2463 LOCAL
->appendmailbox
= mailbox
;
2464 /* send "COPY sequence mailbox" */
2465 ret
= imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
));
2466 LOCAL
->appendmailbox
= NIL
; /* no longer appending */
2467 if (ret
) { /* success, delete messages if move */
2468 if (flags
& CP_MOVE
) imap_flag (stream
,sequence
,"\\Deleted",
2469 ST_SET
+ ((flags
&CP_UID
) ? ST_UID
: NIL
));
2471 /* failed, do referral action if any */
2472 else if (ir
&& pc
&& LOCAL
->referral
&& mail_sequence (stream
,sequence
) &&
2473 (s
= (*ir
) (stream
,LOCAL
->referral
,REFCOPY
)))
2474 ret
= (*pc
) (stream
,sequence
,s
,flags
| (stream
->debug
? CP_DEBUG
: NIL
));
2475 /* otherwise issue error message */
2476 else mm_log (reply
->text
,ERROR
);
2480 /* IMAP mail append message from stringstruct
2481 * Accepts: MAIL stream
2482 * destination mailbox
2485 * Returns: T if append successful, else NIL
2488 long imap_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
2490 MAILSTREAM
*st
= stream
;
2491 IMAPARG
*args
[3],ambx
,amap
;
2492 IMAPPARSEDREPLY
*reply
= NIL
;
2494 char tmp
[MAILTMPLEN
];
2495 long debug
= stream
? stream
->debug
: NIL
;
2498 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
2499 /* mailbox must be good */
2500 if (mail_valid_net (mailbox
,&imapdriver
,NIL
,tmp
)) {
2501 /* create a stream if given one no good */
2502 if ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
2503 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
|
2504 (debug
? OP_DEBUG
: NIL
)))) {
2505 /* note mailbox in case APPENDUID */
2506 LOCAL
->appendmailbox
= mailbox
;
2507 /* use multi-append? */
2508 if (LEVELMULTIAPPEND (stream
)) {
2509 ambx
.type
= ASTRING
; ambx
.text
= (void *) tmp
;
2510 amap
.type
= MULTIAPPEND
; amap
.text
= (void *) &map
;
2511 map
.af
= af
; map
.data
= data
;
2512 args
[0] = &ambx
; args
[1] = &amap
; args
[2] = NIL
;
2514 ret
= imap_OK (stream
,reply
= imap_send (stream
,"APPEND",args
));
2515 LOCAL
->appendmailbox
= NIL
;
2517 /* do succession of single appends */
2518 else while ((*af
) (stream
,data
,&map
.flags
,&map
.date
,&map
.message
) &&
2520 (ret
= imap_OK (stream
,reply
=
2521 imap_append_single (stream
,tmp
,map
.flags
,
2522 map
.date
,map
.message
))));
2523 LOCAL
->appendmailbox
= NIL
;
2524 /* don't do referrals if success or no reply */
2525 if (ret
|| !reply
) mailbox
= NIL
;
2526 /* otherwise generate referral */
2527 else if (!(mailbox
= (ir
&& LOCAL
->referral
) ?
2528 (*ir
) (stream
,LOCAL
->referral
,REFAPPEND
) : NIL
))
2529 mm_log (reply
->text
,ERROR
);
2530 /* close temporary stream */
2531 if (st
!= stream
) stream
= mail_close (stream
);
2532 if (mailbox
) /* chase referral if any */
2533 ret
= imap_append_referral (mailbox
,tmp
,af
,data
,map
.flags
,map
.date
,
2534 map
.message
,&map
,debug
);
2536 else mm_log ("Can't access server for append",ERROR
);
2538 return ret
; /* return */
2541 /* IMAP mail append message referral retry
2542 * Accepts: destination mailbox
2546 * flags from previous attempt
2547 * date from previous attempt
2548 * message stringstruct from previous attempt
2549 * options (currently non-zero to set OP_DEBUG)
2550 * Returns: T if append successful, else NIL
2553 long imap_append_referral (char *mailbox
,char *tmp
,append_t af
,void *data
,
2554 char *flags
,char *date
,STRING
*message
,
2555 APPENDDATA
*map
,long options
)
2558 IMAPARG
*args
[3],ambx
,amap
;
2559 IMAPPARSEDREPLY
*reply
;
2561 (imapreferral_t
) mail_parameters (NIL
,GET_IMAPREFERRAL
,NIL
);
2562 /* barf if bad mailbox */
2563 while (mailbox
&& mail_valid_net (mailbox
,&imapdriver
,NIL
,tmp
)) {
2564 /* create a stream if given one no good */
2565 if (!(stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
|
2566 (options
? OP_DEBUG
: NIL
)))) {
2567 sprintf (tmp
,"Can't access referral server: %.80s",mailbox
);
2571 /* got referral server, use multi-append? */
2572 if (LEVELMULTIAPPEND (stream
)) {
2573 ambx
.type
= ASTRING
; ambx
.text
= (void *) tmp
;
2574 amap
.type
= MULTIAPPENDREDO
; amap
.text
= (void *) map
;
2575 args
[0] = &ambx
; args
[1] = &amap
; args
[2] = NIL
;
2576 /* do multiappend on referral site */
2577 if (imap_OK (stream
,reply
= imap_send (stream
,"APPEND",args
))) {
2578 mail_close (stream
); /* multiappend OK, close stream */
2579 return LONGT
; /* all done */
2582 /* do multiple single appends */
2583 else while (imap_OK (stream
,reply
=
2584 imap_append_single (stream
,tmp
,flags
,date
,message
)))
2585 if (!((*af
) (stream
,data
,&flags
,&date
,&message
) && message
)) {
2586 mail_close (stream
); /* last message, close stream */
2587 return LONGT
; /* all done */
2589 /* generate error if no nested referral */
2590 if (!(mailbox
= (ir
&& LOCAL
->referral
) ?
2591 (*ir
) (stream
,LOCAL
->referral
,REFAPPEND
) : NIL
))
2592 mm_log (reply
->text
,ERROR
);
2593 mail_close (stream
); /* close previous referral stream */
2595 return NIL
; /* bogus mailbox */
2598 /* IMAP append single message
2599 * Accepts: mail stream
2600 * destination mailbox
2603 * stringstruct of message to append
2604 * Returns: reply from append
2607 IMAPPARSEDREPLY
*imap_append_single (MAILSTREAM
*stream
,char *mailbox
,
2608 char *flags
,char *date
,STRING
*message
)
2611 IMAPARG
*args
[5],ambx
,aflg
,adat
,amsg
;
2612 IMAPPARSEDREPLY
*reply
;
2613 char tmp
[MAILTMPLEN
];
2615 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2616 args
[i
= 0] = &ambx
;
2618 aflg
.type
= FLAGS
; aflg
.text
= (void *) flags
;
2621 if (date
) { /* ensure date in INTERNALDATE format */
2622 if (!mail_parse_date (&elt
,date
)) {
2623 /* flush previous reply */
2624 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
2625 /* build new fake reply */
2626 LOCAL
->reply
.tag
= LOCAL
->reply
.line
= cpystr ("*");
2627 LOCAL
->reply
.key
= "BAD";
2628 LOCAL
->reply
.text
= "Bad date in append";
2629 return &LOCAL
->reply
;
2631 adat
.type
= ASTRING
;
2632 adat
.text
= (void *) (date
= mail_date (tmp
,&elt
));
2635 amsg
.type
= LITERAL
; amsg
.text
= (void *) message
;
2638 /* easy if IMAP4[rev1] */
2639 if (LEVELIMAP4 (stream
)) reply
= imap_send (stream
,"APPEND",args
);
2640 else { /* try the IMAP2bis way */
2641 args
[1] = &amsg
; args
[2] = NIL
;
2642 reply
= imap_send (stream
,"APPEND",args
);
2647 /* IMAP garbage collect stream
2648 * Accepts: Mail stream
2649 * garbage collection flags
2652 void imap_gc (MAILSTREAM
*stream
,long gcflags
)
2656 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2657 /* make sure the cache is large enough */
2658 (*mc
) (stream
,stream
->nmsgs
,CH_SIZE
);
2659 if (gcflags
& GC_TEXTS
) { /* garbage collect texts? */
2660 if (!stream
->scache
) for (i
= 1; i
<= stream
->nmsgs
; ++i
)
2661 if ((elt
= (MESSAGECACHE
*) (*mc
) (stream
,i
,CH_ELT
)) != NULL
)
2662 imap_gc_body (elt
->private.msg
.body
);
2663 imap_gc_body (stream
->body
);
2665 /* gc cache if requested and unlocked */
2666 if (gcflags
& GC_ELT
) for (i
= 1; i
<= stream
->nmsgs
; ++i
)
2667 if ((elt
= (MESSAGECACHE
*) (*mc
) (stream
,i
,CH_ELT
)) &&
2668 (elt
->lockcount
== 1)) (*mc
) (stream
,i
,CH_FREE
);
2671 /* IMAP garbage collect body texts
2672 * Accepts: body to GC
2675 void imap_gc_body (BODY
*body
)
2678 if (body
) { /* have a body? */
2679 if (body
->mime
.text
.data
) /* flush MIME data */
2680 fs_give ((void **) &body
->mime
.text
.data
);
2681 /* flush text contents */
2682 if (body
->contents
.text
.data
)
2683 fs_give ((void **) &body
->contents
.text
.data
);
2684 body
->mime
.text
.size
= body
->contents
.text
.size
= 0;
2686 if (body
->type
== TYPEMULTIPART
)
2687 for (part
= body
->nested
.part
; part
; part
= part
->next
)
2688 imap_gc_body (&part
->body
);
2689 /* MESSAGE/RFC822? */
2690 else if ((body
->type
== TYPEMESSAGE
) && !strcmp (body
->subtype
,"RFC822")) {
2691 imap_gc_body (body
->nested
.msg
->body
);
2692 if (body
->nested
.msg
->full
.text
.data
)
2693 fs_give ((void **) &body
->nested
.msg
->full
.text
.data
);
2694 if (body
->nested
.msg
->header
.text
.data
)
2695 fs_give ((void **) &body
->nested
.msg
->header
.text
.data
);
2696 if (body
->nested
.msg
->text
.text
.data
)
2697 fs_give ((void **) &body
->nested
.msg
->text
.text
.data
);
2698 body
->nested
.msg
->full
.text
.size
= body
->nested
.msg
->header
.text
.size
=
2699 body
->nested
.msg
->text
.text
.size
= 0;
2704 /* IMAP get capabilities
2705 * Accepts: mail stream
2708 void imap_capability (MAILSTREAM
*stream
)
2711 LOCAL
->gotcapability
= NIL
; /* flush any previous capabilities */
2712 /* request new capabilities */
2713 imap_send (stream
,"CAPABILITY",NIL
);
2714 if (!LOCAL
->gotcapability
) { /* did server get any? */
2715 /* no, flush threaders just in case */
2716 if ((thr
= LOCAL
->cap
.threader
) != NULL
) while ((t
= thr
) != NULL
) {
2717 fs_give ((void **) &t
->name
);
2719 fs_give ((void **) &t
);
2721 /* zap most capabilities */
2722 memset (&LOCAL
->cap
,0,sizeof (LOCAL
->cap
));
2723 /* assume IMAP2bis server if failure */
2724 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
2729 * Accepts: mail stream
2731 * authentication identifer
2733 * Returns: T on success, NIL on failure
2736 long imap_setacl (MAILSTREAM
*stream
,char *mailbox
,char *id
,char *rights
)
2738 IMAPARG
*args
[4],ambx
,aid
,art
;
2739 ambx
.type
= aid
.type
= art
.type
= ASTRING
;
2740 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2741 art
.text
= (void *) rights
;
2742 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = &art
; args
[3] = NIL
;
2743 return imap_acl_work (stream
,"SETACL",args
);
2748 * Accepts: mail stream
2750 * authentication identifer
2751 * Returns: T on success, NIL on failure
2754 long imap_deleteacl (MAILSTREAM
*stream
,char *mailbox
,char *id
)
2756 IMAPARG
*args
[3],ambx
,aid
;
2757 ambx
.type
= aid
.type
= ASTRING
;
2758 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2759 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = NIL
;
2760 return imap_acl_work (stream
,"DELETEACL",args
);
2765 * Accepts: mail stream
2767 * Returns: T on success with data returned via callback, NIL on failure
2770 long imap_getacl (MAILSTREAM
*stream
,char *mailbox
)
2772 IMAPARG
*args
[2],ambx
;
2773 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2774 args
[0] = &ambx
; args
[1] = NIL
;
2775 return imap_acl_work (stream
,"GETACL",args
);
2779 * Accepts: mail stream
2781 * authentication identifer
2782 * Returns: T on success with data returned via callback, NIL on failure
2785 long imap_listrights (MAILSTREAM
*stream
,char *mailbox
,char *id
)
2787 IMAPARG
*args
[3],ambx
,aid
;
2788 ambx
.type
= aid
.type
= ASTRING
;
2789 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2790 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = NIL
;
2791 return imap_acl_work (stream
,"LISTRIGHTS",args
);
2796 * Accepts: mail stream
2798 * Returns: T on success with data returned via callback, NIL on failure
2801 long imap_myrights (MAILSTREAM
*stream
,char *mailbox
)
2803 IMAPARG
*args
[2],ambx
;
2804 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2805 args
[0] = &ambx
; args
[1] = NIL
;
2806 return imap_acl_work (stream
,"MYRIGHTS",args
);
2810 /* IMAP ACL worker routine
2811 * Accepts: mail stream
2814 * Returns: T on success, NIL on failure
2817 long imap_acl_work (MAILSTREAM
*stream
,char *command
,IMAPARG
*args
[])
2820 if (LEVELACL (stream
)) { /* send command */
2821 IMAPPARSEDREPLY
*reply
;
2822 if (imap_OK (stream
,reply
= imap_send (stream
,command
,args
)))
2824 else mm_log (reply
->text
,ERROR
);
2826 else mm_log ("ACL not available on this IMAP server",ERROR
);
2831 * Accepts: mail stream
2833 * resource limit list as a stringlist
2834 * Returns: T on success with data returned via callback, NIL on failure
2837 long imap_setquota (MAILSTREAM
*stream
,char *qroot
,STRINGLIST
*limits
)
2840 if (LEVELQUOTA (stream
)) { /* send "SETQUOTA" */
2841 IMAPPARSEDREPLY
*reply
;
2842 IMAPARG
*args
[3],aqrt
,alim
;
2843 aqrt
.type
= ASTRING
; aqrt
.text
= (void *) qroot
;
2844 alim
.type
= SNLIST
; alim
.text
= (void *) limits
;
2845 args
[0] = &aqrt
; args
[1] = &alim
; args
[2] = NIL
;
2846 if (imap_OK (stream
,reply
= imap_send (stream
,"SETQUOTA",args
)))
2848 else mm_log (reply
->text
,ERROR
);
2850 else mm_log ("Quota not available on this IMAP server",ERROR
);
2855 * Accepts: mail stream
2857 * Returns: T on success with data returned via callback, NIL on failure
2860 long imap_getquota (MAILSTREAM
*stream
,char *qroot
)
2863 if (LEVELQUOTA (stream
)) { /* send "GETQUOTA" */
2864 IMAPPARSEDREPLY
*reply
;
2865 IMAPARG
*args
[2],aqrt
;
2866 aqrt
.type
= ASTRING
; aqrt
.text
= (void *) qroot
;
2867 args
[0] = &aqrt
; args
[1] = NIL
;
2868 if (imap_OK (stream
,reply
= imap_send (stream
,"GETQUOTA",args
)))
2870 else mm_log (reply
->text
,ERROR
);
2872 else mm_log ("Quota not available on this IMAP server",ERROR
);
2877 /* IMAP get quota root
2878 * Accepts: mail stream
2880 * Returns: T on success with data returned via callback, NIL on failure
2883 long imap_getquotaroot (MAILSTREAM
*stream
,char *mailbox
)
2886 if (LEVELQUOTA (stream
)) { /* send "GETQUOTAROOT" */
2887 IMAPPARSEDREPLY
*reply
;
2888 IMAPARG
*args
[2],ambx
;
2889 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2890 args
[0] = &ambx
; args
[1] = NIL
;
2891 if (imap_OK (stream
,reply
= imap_send (stream
,"GETQUOTAROOT",args
)))
2893 else mm_log (reply
->text
,ERROR
);
2895 else mm_log ("Quota not available on this IMAP server",ERROR
);
2899 /* Internal routines */
2902 /* IMAP send command
2903 * Accepts: MAIL stream
2906 * Returns: parsed reply
2909 #define CMDBASE LOCAL->tmp /* command base */
2911 IMAPPARSEDREPLY
*imap_send (MAILSTREAM
*stream
,char *cmd
,IMAPARG
*args
[])
2913 IMAPPARSEDREPLY
*reply
;
2914 IMAPARG
*arg
,**arglst
;
2919 sendcommand_t sc
= (sendcommand_t
) mail_parameters (NIL
,GET_SENDCOMMAND
,NIL
);
2922 char c
,*s
,*t
,tag
[10];
2923 stream
->unhealthy
= NIL
; /* make stream healthy again */
2924 /* gensym a new tag */
2925 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
2926 if (!LOCAL
->netstream
) /* make sure have a session */
2927 return imap_fake (stream
,tag
,"[CLOSED] IMAP connection lost");
2928 mail_lock (stream
); /* lock up the stream */
2929 if (sc
) /* tell client sending a command */
2930 (*sc
) (stream
,cmd
,((compare_cstring (cmd
,"FETCH") &&
2931 compare_cstring (cmd
,"STORE") &&
2932 compare_cstring (cmd
,"SEARCH")) ?
2933 NIL
: SC_EXPUNGEDEFERRED
));
2934 /* ignore referral from previous command */
2935 if (LOCAL
->referral
) fs_give ((void **) &LOCAL
->referral
);
2936 sprintf (CMDBASE
,"%s %s",tag
,cmd
);
2937 s
= CMDBASE
+ strlen (CMDBASE
);
2938 if ((arglst
= args
) != NULL
) while ((arg
= *arglst
++) != NULL
) {
2939 *s
++ = ' '; /* delimit argument with space */
2940 switch (arg
->type
) {
2941 case ATOM
: /* atom */
2942 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
2944 case NUMBER
: /* number */
2945 sprintf (s
,"%lu",(unsigned long) arg
->text
);
2948 case FLAGS
: /* flag list as a single string */
2949 if (*(t
= (char *) arg
->text
) != '(') {
2950 *s
++ = '('; /* wrap parens around string */
2951 while (*t
) *s
++ = *t
++;
2952 *s
++ = ')'; /* wrap parens around string */
2954 else while (*t
) *s
++ = *t
++;
2956 case ASTRING
: /* atom or string, must be literal? */
2957 st
.size
= strlen ((char *) (st
.data
= (unsigned char *) arg
->text
));
2958 if ((reply
= imap_send_astring (stream
,tag
,&s
,&st
,NIL
,CMDBASE
+MAXCOMMAND
)) != NULL
)
2961 case LITERAL
: /* literal, as a stringstruct */
2962 if ((reply
= imap_send_literal (stream
,tag
,&s
,arg
->text
)) != NULL
) return reply
;
2965 case LIST
: /* list of strings */
2966 list
= (STRINGLIST
*) arg
->text
;
2967 c
= '('; /* open paren */
2968 do { /* for each list item */
2969 *s
++ = c
; /* write prefix character */
2970 if ((reply
= imap_send_astring (stream
,tag
,&s
,&list
->text
,NIL
,
2971 CMDBASE
+MAXCOMMAND
)) != NULL
) return reply
;
2972 c
= ' '; /* prefix character for subsequent strings */
2974 while ((list
= list
->next
) != NULL
);
2975 *s
++ = ')'; /* close list */
2977 case SEARCHPROGRAM
: /* search program */
2978 if ((reply
= imap_send_spgm (stream
,tag
,CMDBASE
,&s
,arg
->text
,
2979 CMDBASE
+MAXCOMMAND
)) != NULL
)
2982 case SORTPROGRAM
: /* search program */
2983 c
= '('; /* open paren */
2984 for (spg
= (SORTPGM
*) arg
->text
; spg
; spg
= spg
->next
) {
2985 *s
++ = c
; /* write prefix */
2986 if (spg
->reverse
) for (t
= "REVERSE "; *t
; *s
++ = *t
++);
2987 switch (spg
->function
) {
2989 for (t
= "DATE"; *t
; *s
++ = *t
++);
2992 for (t
= "ARRIVAL"; *t
; *s
++ = *t
++);
2995 for (t
= "FROM"; *t
; *s
++ = *t
++);
2998 for (t
= "SUBJECT"; *t
; *s
++ = *t
++);
3001 for (t
= "TO"; *t
; *s
++ = *t
++);
3004 for (t
= "CC"; *t
; *s
++ = *t
++);
3007 for (t
= "SIZE"; *t
; *s
++ = *t
++);
3010 fatal ("Unknown sort program function in imap_send()!");
3012 c
= ' '; /* prefix character for subsequent items */
3014 *s
++ = ')'; /* close list */
3017 case BODYTEXT
: /* body section */
3018 for (t
= "BODY["; *t
; *s
++ = *t
++);
3019 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3021 case BODYPEEK
: /* body section */
3022 for (t
= "BODY.PEEK["; *t
; *s
++ = *t
++);
3023 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3025 case BODYCLOSE
: /* close bracket and possible length */
3026 s
[-1] = ']'; /* no leading space */
3027 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3029 case SEQUENCE
: /* sequence */
3030 if ((i
= strlen (t
= (char *) arg
->text
)) <= (size_t) MAXCOMMAND
)
3031 while (*t
) *s
++ = *t
++; /* easy case */
3033 mail_unlock (stream
); /* unlock stream */
3034 a
= arg
->text
; /* save original sequence pointer */
3035 arg
->type
= ATOM
; /* make recursive call be faster */
3036 do { /* break up into multiple commands */
3037 if (i
<= MAXCOMMAND
) {/* final part? */
3038 reply
= imap_send (stream
,cmd
,args
);
3039 i
= 0; /* and mark as done */
3041 else { /* still needs to be split further */
3042 if (!(t
= strchr (t
+ MAXCOMMAND
- 30,',')) ||
3043 ((t
- (char *) arg
->text
) > MAXCOMMAND
))
3044 fatal ("impossible over-long sequence");
3045 *t
= '\0'; /* tie off sequence at point of split*/
3046 /* recurse to do this part */
3047 reply
= imap_send (stream
,cmd
,args
);
3048 *t
++ = ','; /* restore the comma in case something cares */
3050 if (!imap_OK (stream
,reply
)) break;
3051 /* calculate size of remaining sequence */
3052 i
-= (t
- (char *) arg
->text
);
3053 /* point to new remaining sequence */
3054 arg
->text
= (void *) t
;
3057 arg
->type
= SEQUENCE
; /* restore in case something cares */
3059 return reply
; /* return result */
3062 case LISTMAILBOX
: /* astring with wildcards */
3063 st
.size
= strlen ((char *) (st
.data
= (unsigned char *) arg
->text
));
3064 if ((reply
= imap_send_astring (stream
,tag
,&s
,&st
,T
,CMDBASE
+MAXCOMMAND
)) != NULL
)
3068 case MULTIAPPEND
: /* append multiple messages */
3069 /* get package pointer */
3070 map
= (APPENDDATA
*) arg
->text
;
3071 if (!(*map
->af
) (stream
,map
->data
,&map
->flags
,&map
->date
,&map
->message
)||
3074 INIT (&es
,mail_string
,"",0);
3075 return (reply
= imap_send_literal (stream
,tag
,&s
,&es
)) ?
3076 reply
: imap_fake (stream
,tag
,"Server zero-length literal error");
3078 case MULTIAPPENDREDO
: /* redo multiappend */
3079 /* get package pointer */
3080 map
= (APPENDDATA
*) arg
->text
;
3081 do { /* make sure date valid if given */
3082 char datetmp
[MAILTMPLEN
];
3085 if (!map
->date
|| mail_parse_date (&elt
,map
->date
)) {
3086 if ((t
= map
->flags
) != NULL
) { /* flags given? */
3088 *s
++ = '('; /* wrap parens around string */
3089 while (*t
) *s
++ = *t
++;
3090 *s
++ = ')'; /* wrap parens around string */
3092 else while (*t
) *s
++ = *t
++;
3093 *s
++ = ' '; /* delimit with space */
3095 if (map
->date
) { /* date given? */
3096 st
.size
= strlen ((char *) (st
.data
= (unsigned char *)
3097 mail_date (datetmp
,&elt
)));
3098 if ((reply
= imap_send_astring (stream
,tag
,&s
,&st
,NIL
,
3099 CMDBASE
+MAXCOMMAND
)) != NULL
) return reply
;
3100 *s
++ = ' '; /* delimit with space */
3102 if ((reply
= imap_send_literal (stream
,tag
,&s
,map
->message
)) != NULL
)
3104 /* get next message */
3105 if ((*map
->af
) (stream
,map
->data
,&map
->flags
,&map
->date
,
3107 /* have a message, delete next in command */
3108 if (map
->message
) *s
++ = ' ';
3109 continue; /* loop back for next message */
3112 /* bad date or need to abort */
3113 INIT (&es
,mail_string
,"",0);
3114 return (reply
= imap_send_literal (stream
,tag
,&s
,&es
)) ?
3115 reply
: imap_fake (stream
,tag
,"Server zero-length literal error");
3116 break; /* exit the loop */
3117 } while (map
->message
);
3120 case SNLIST
: /* list of string/number pairs */
3121 list
= (STRINGLIST
*) arg
->text
;
3122 c
= '('; /* open paren */
3123 do { /* for each list item */
3124 *s
++ = c
; /* write prefix character */
3125 if (list
) { /* sigh, QUOTA has bizarre syntax! */
3126 for (t
= (char *) list
->text
.data
; *t
; *s
++ = *t
++);
3127 sprintf (s
," %lu",list
->text
.size
);
3129 c
= ' '; /* prefix character for subsequent strings */
3132 while ((list
= list
->next
) != NULL
);
3133 *s
++ = ')'; /* close list */
3136 fatal ("Unknown argument type in imap_send()!");
3139 /* send the command */
3140 reply
= imap_sout (stream
,tag
,CMDBASE
,&s
);
3141 mail_unlock (stream
); /* unlock stream */
3145 /* IMAP send atom-string
3146 * Accepts: MAIL stream
3148 * pointer to current position pointer of output bigbuf
3149 * atom-string to output
3150 * flag if list_wildcards allowed
3151 * maximum to write as atom or qstring
3152 * Returns: error reply or NIL if success
3155 IMAPPARSEDREPLY
*imap_send_astring (MAILSTREAM
*stream
,char *tag
,char **s
,
3156 SIZEDTEXT
*as
,long wildok
,char *limit
)
3161 /* default to atom unless empty or loser */
3162 int qflag
= (as
->size
&& !LOCAL
->loser
) ? NIL
: T
;
3163 /* in case needed */
3164 INIT (&st
,mail_string
,(void *) as
->data
,as
->size
);
3165 /* always write literal if no space */
3166 if ((*s
+ as
->size
) > limit
) return imap_send_literal (stream
,tag
,s
,&st
);
3167 for (j
= 0; j
< as
->size
; j
++) switch (c
= as
->data
[j
]) {
3168 default: /* all other characters */
3169 if (!(c
& 0x80)) { /* must not be 8bit */
3170 if (c
<= ' ') qflag
= T
; /* must quote if a CTL */
3173 case '\0': /* not a CHAR */
3174 case '\012': case '\015': /* not a TEXT-CHAR */
3175 case '"': case '\\': /* quoted-specials (IMAP2 required this) */
3176 return imap_send_literal (stream
,tag
,s
,&st
);
3177 case '*': case '%': /* list_wildcards */
3178 if (wildok
) break; /* allowed if doing the wild thing */
3180 case '(': case ')': case '{': case ' ': case 0x7f:
3182 case '"': case '\\': /* quoted-specials (could work in IMAP4) */
3184 qflag
= T
; /* must use quoted string format */
3187 if (qflag
) *(*s
)++ = '"'; /* write open quote */
3188 for (j
= 0; j
< as
->size
; j
++) *(*s
)++ = as
->data
[j
];
3189 if (qflag
) *(*s
)++ = '"'; /* write close quote */
3193 /* IMAP send literal
3194 * Accepts: MAIL stream
3196 * pointer to current position pointer of output bigbuf
3197 * literal to output as stringstruct
3198 * Returns: error reply or NIL if success
3201 IMAPPARSEDREPLY
*imap_send_literal (MAILSTREAM
*stream
,char *tag
,char **s
,
3204 IMAPPARSEDREPLY
*reply
;
3205 unsigned long i
= SIZE (st
);
3207 sprintf (*s
,"{%lu}",i
); /* write literal count */
3208 *s
+= strlen (*s
); /* size of literal count */
3209 /* send the command */
3210 reply
= imap_sout (stream
,tag
,CMDBASE
,s
);
3211 if (strcmp (reply
->tag
,"+")) {/* prompt for more data? */
3212 mail_unlock (stream
); /* no, give up */
3215 while (i
) { /* dump the text */
3216 if (st
->cursize
) { /* if text to do in this chunk */
3217 /* RFC 3501 technically forbids NULs in literals. Normally, the
3218 * delivering MTA would take care of MIME converting the message text
3219 * so that it is NUL-free. If it doesn't, then we have the choice of
3220 * either violating IMAP by sending NULs, corrupting the data, or going
3221 * to lots of work to do MIME conversion in the IMAP server.
3223 * No current stringstruct driver objects to having its buffer patched.
3224 * If this ever changes, it will be necessary to change this kludge.
3226 /* patch NULs to C1 control */
3227 for (j
= 0; j
< st
->cursize
; ++j
)
3228 if (!st
->curpos
[j
]) st
->curpos
[j
] = 0x80;
3229 if (!net_sout (LOCAL
->netstream
,st
->curpos
,st
->cursize
)) {
3230 mail_unlock (stream
);
3231 return imap_fake (stream
,tag
,"[CLOSED] IMAP connection broken (data)");
3233 i
-= st
->cursize
; /* note that we wrote out this much */
3234 st
->curpos
+= (st
->cursize
- 1);
3237 (*st
->dtb
->next
) (st
); /* advance to next buffer's worth */
3239 return NIL
; /* success */
3242 /* IMAP send search program
3243 * Accepts: MAIL stream
3245 * base pointer if trimming needed
3246 * pointer to current position pointer of output bigbuf
3247 * search program to output
3248 * pointer to limit guideline
3249 * Returns: error reply or NIL if success
3253 IMAPPARSEDREPLY
*imap_send_spgm (MAILSTREAM
*stream
,char *tag
,char *base
,
3254 char **s
,SEARCHPGM
*pgm
,char *limit
)
3256 IMAPPARSEDREPLY
*reply
;
3261 /* trim if called recursively */
3262 if (base
) *s
= imap_send_spgm_trim (base
,*s
,NIL
);
3263 base
= *s
; /* this is the new base */
3264 /* default searchpgm */
3265 for (t
= "ALL"; *t
; *(*s
)++ = *t
++);
3266 if (!pgm
) return NIL
; /* done if NIL searchpgm */
3267 if ((pgm
->msgno
&& /* message sequences */
3268 (pgm
->msgno
->next
|| /* trim away first:last */
3269 (pgm
->msgno
->first
!= 1) || (pgm
->msgno
->last
!= stream
->nmsgs
)) &&
3270 (reply
= imap_send_sset (stream
,tag
,base
,s
,pgm
->msgno
," ",limit
))) ||
3272 (reply
= imap_send_sset (stream
,tag
,base
,s
,pgm
->uid
," UID ",limit
))))
3276 sprintf (*s
," LARGER %lu",pgm
->larger
);
3280 sprintf (*s
," SMALLER %lu",pgm
->smaller
);
3285 if (pgm
->answered
) for (t
= " ANSWERED"; *t
; *(*s
)++ = *t
++);
3286 if (pgm
->unanswered
) for (t
=" UNANSWERED"; *t
; *(*s
)++ = *t
++);
3287 if (pgm
->deleted
) for (t
=" DELETED"; *t
; *(*s
)++ = *t
++);
3288 if (pgm
->undeleted
) for (t
=" UNDELETED"; *t
; *(*s
)++ = *t
++);
3289 if (pgm
->draft
) for (t
=" DRAFT"; *t
; *(*s
)++ = *t
++);
3290 if (pgm
->undraft
) for (t
=" UNDRAFT"; *t
; *(*s
)++ = *t
++);
3291 if (pgm
->flagged
) for (t
=" FLAGGED"; *t
; *(*s
)++ = *t
++);
3292 if (pgm
->unflagged
) for (t
=" UNFLAGGED"; *t
; *(*s
)++ = *t
++);
3293 if (pgm
->recent
) for (t
=" RECENT"; *t
; *(*s
)++ = *t
++);
3294 if (pgm
->old
) for (t
=" OLD"; *t
; *(*s
)++ = *t
++);
3295 if (pgm
->seen
) for (t
=" SEEN"; *t
; *(*s
)++ = *t
++);
3296 if (pgm
->unseen
) for (t
=" UNSEEN"; *t
; *(*s
)++ = *t
++);
3297 if ((pgm
->keyword
&& /* keywords */
3298 (reply
= imap_send_slist (stream
,tag
,base
,s
," KEYWORD ",pgm
->keyword
,
3301 (reply
= imap_send_slist (stream
,tag
,base
,s
," UNKEYWORD ",
3302 pgm
->unkeyword
,limit
))))
3304 /* sent date ranges */
3305 if (pgm
->sentbefore
) imap_send_sdate (s
,"SENTBEFORE",pgm
->sentbefore
);
3306 if (pgm
->senton
) imap_send_sdate (s
,"SENTON",pgm
->senton
);
3307 if (pgm
->sentsince
) imap_send_sdate (s
,"SENTSINCE",pgm
->sentsince
);
3308 /* internal date ranges */
3309 if (pgm
->before
) imap_send_sdate (s
,"BEFORE",pgm
->before
);
3310 if (pgm
->on
) imap_send_sdate (s
,"ON",pgm
->on
);
3311 if (pgm
->since
) imap_send_sdate (s
,"SINCE",pgm
->since
);
3313 sprintf (*s
," OLDER %lu",pgm
->older
);
3317 sprintf (*s
," YOUNGER %lu",pgm
->younger
);
3321 if ((pgm
->bcc
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," BCC ",
3322 pgm
->bcc
,limit
))) ||
3323 (pgm
->cc
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," CC ",pgm
->cc
,
3325 (pgm
->from
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," FROM ",
3326 pgm
->from
,limit
))) ||
3327 (pgm
->to
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," TO ",pgm
->to
,
3330 if ((pgm
->subject
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," SUBJECT ",
3331 pgm
->subject
,limit
))) ||
3332 (pgm
->body
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," BODY ",
3333 pgm
->body
,limit
))) ||
3334 (pgm
->text
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," TEXT ",
3338 /* Note that these criteria are not supported by IMAP and have to be
3340 if ((pgm
->return_path
&&
3341 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Return-Path ",
3342 pgm
->return_path
,limit
))) ||
3344 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Sender ",
3345 pgm
->sender
,limit
))) ||
3347 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Reply-To ",
3348 pgm
->reply_to
,limit
))) ||
3349 (pgm
->in_reply_to
&&
3350 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER In-Reply-To ",
3351 pgm
->in_reply_to
,limit
))) ||
3353 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Message-ID ",
3354 pgm
->message_id
,limit
))) ||
3356 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Newsgroups ",
3357 pgm
->newsgroups
,limit
))) ||
3358 (pgm
->followup_to
&&
3359 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Followup-To ",
3360 pgm
->followup_to
,limit
))) ||
3362 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER References ",
3363 pgm
->references
,limit
)))) return reply
;
3365 /* all other headers */
3366 if ((hdr
= pgm
->header
) != NULL
) do {
3367 *s
= imap_send_spgm_trim (base
,*s
," HEADER ");
3368 if ((reply
= imap_send_astring (stream
,tag
,s
,&hdr
->line
,NIL
,limit
)) != NULL
)
3371 if ((reply
= imap_send_astring (stream
,tag
,s
,&hdr
->text
,NIL
,limit
)) != NULL
)
3373 } while ((hdr
= hdr
->next
) != NULL
);
3374 for (pgo
= pgm
->or; pgo
; pgo
= pgo
->next
) {
3375 *s
= imap_send_spgm_trim (base
,*s
," OR (");
3376 if ((reply
= imap_send_spgm (stream
,tag
,base
,s
,pgo
->first
,limit
)) != NULL
)
3378 for (t
= ") ("; *t
; *(*s
)++ = *t
++);
3379 if ((reply
= imap_send_spgm (stream
,tag
,base
,s
,pgo
->second
,limit
)) != NULL
)
3383 for (pgl
= pgm
->not; pgl
; pgl
= pgl
->next
) {
3384 *s
= imap_send_spgm_trim (base
,*s
," NOT (");
3385 if ((reply
= imap_send_spgm (stream
,tag
,base
,s
,pgl
->pgm
,limit
)) != NULL
)
3389 /* trim if needed */
3390 *s
= imap_send_spgm_trim (base
,*s
,NIL
);
3391 return NIL
; /* search program written OK */
3395 /* Write new text and trim extraneous "ALL" from searchpgm
3396 * Accepts: pointer to start of searchpgm or NIL
3397 * current end pointer
3398 * new text to write or NIL
3399 * Returns: new end pointer, trimmed if needed
3402 char *imap_send_spgm_trim (char *base
,char *s
,char *text
)
3405 /* write new text */
3406 if (text
) for (t
= text
; *t
; *s
++ = *t
++);
3408 if (base
&& (s
> (t
= (base
+ 4))) && (*base
== 'A') && (base
[1] == 'L') &&
3409 (base
[2] == 'L') && (base
[3] == ' ')) {
3410 memmove (base
,t
,s
- t
); /* yes, blat down remaining text */
3411 s
-= 4; /* and reduce current pointer */
3413 return s
; /* return new end pointer */
3416 /* IMAP send search set
3417 * Accepts: MAIL stream
3418 * current command tag
3419 * base pointer if trimming needed
3420 * pointer to current position pointer of output bigbuf
3421 * search set to output
3423 * maximum output pointer
3424 * Returns: NIL if success, error reply if error
3427 IMAPPARSEDREPLY
*imap_send_sset (MAILSTREAM
*stream
,char *tag
,char *base
,
3428 char **s
,SEARCHSET
*set
,char *prefix
,
3431 IMAPPARSEDREPLY
*reply
;
3435 /* trim and write prefix */
3436 *s
= imap_send_spgm_trim (base
,*s
,prefix
);
3437 /* run down search list */
3438 for (c
= NIL
; set
&& (*s
< limit
); set
= set
->next
, c
= ',') {
3439 if (c
) *(*s
)++ = c
; /* write delimiter and first value */
3440 if (set
->first
== 0xffffffff) *(*s
)++ = '*';
3442 sprintf (*s
,"%lu",set
->first
);
3445 /* have a second value? */
3446 if (set
->last
&& (set
->first
!= set
->last
)) {
3447 *(*s
)++ = ':'; /* write delimiter and second value */
3448 if (set
->last
== 0xffffffff) *(*s
)++ = '*';
3450 sprintf (*s
,"%lu",set
->last
);
3455 if (set
) { /* insert "OR" in front of incomplete set */
3456 memmove (start
+ 3,start
,*s
- start
);
3457 memcpy (start
," OR",3);
3458 *s
+= 3; /* point to end of buffer */
3459 /* write glue that is equivalent to ALL */
3460 for (t
=" ((OR BCC FOO NOT BCC "; *t
; *(*s
)++ = *t
++);
3461 /* but broken by a literal */
3462 INIT (&st
,mail_string
,(void *) "FOO",3);
3463 if ((reply
= imap_send_literal (stream
,tag
,s
,&st
)) != NULL
) return reply
;
3464 *(*s
)++ = ')'; /* close glue */
3465 if ((reply
= imap_send_sset (stream
,tag
,NIL
,s
,set
,prefix
,limit
)) != NULL
)
3467 *(*s
)++ = ')'; /* close second OR argument */
3472 /* IMAP send search list
3473 * Accepts: MAIL stream
3475 * base pointer if trimming needed
3476 * pointer to current position pointer of output bigbuf
3477 * name of search list
3478 * search list to output
3479 * maximum output pointer
3480 * Returns: NIL if success, error reply if error
3483 IMAPPARSEDREPLY
*imap_send_slist (MAILSTREAM
*stream
,char *tag
,char *base
,
3484 char **s
,char *name
,STRINGLIST
*list
,
3487 IMAPPARSEDREPLY
*reply
;
3489 *s
= imap_send_spgm_trim (base
,*s
,name
);
3490 base
= NIL
; /* no longer need trimming */
3491 reply
= imap_send_astring (stream
,tag
,s
,&list
->text
,NIL
,limit
);
3493 while (!reply
&& (list
= list
->next
));
3498 /* IMAP send search date
3499 * Accepts: pointer to current position pointer of output bigbuf
3501 * search date to output
3504 void imap_send_sdate (char **s
,char *name
,unsigned short date
)
3506 sprintf (*s
," %s %d-%s-%d",name
,date
& 0x1f,
3507 months
[((date
>> 5) & 0xf) - 1],BASEYEAR
+ (date
>> 9));
3511 /* IMAP send buffered command to sender
3512 * Accepts: MAIL stream
3515 * pointer to string tail pointer
3519 IMAPPARSEDREPLY
*imap_sout (MAILSTREAM
*stream
,char *tag
,char *base
,char **s
)
3521 IMAPPARSEDREPLY
*reply
;
3522 if (stream
->debug
) { /* output debugging telemetry */
3524 mail_dlog (base
,LOCAL
->sensitive
);
3526 *(*s
)++ = '\015'; /* append CRLF */
3529 reply
= net_sout (LOCAL
->netstream
,base
,*s
- base
) ?
3530 imap_reply (stream
,tag
) :
3531 imap_fake (stream
,tag
,"[CLOSED] IMAP connection broken (command)");
3532 *s
= base
; /* restart buffer */
3537 /* IMAP send null-terminated string to sender
3538 * Accepts: MAIL stream
3540 * Returns: T if success, else NIL
3543 long imap_soutr (MAILSTREAM
*stream
,char *string
)
3548 if (stream
->debug
) mm_dlog (string
);
3549 sprintf (s
= (char *) fs_get ((i
= strlen (string
) + 2) + 1),
3550 "%s\015\012",string
);
3551 ret
= net_sout (LOCAL
->netstream
,s
,i
);
3552 fs_give ((void **) &s
);
3557 * Accepts: MAIL stream
3558 * tag to search or NIL if want a greeting
3559 * Returns: parsed reply, never NIL
3562 IMAPPARSEDREPLY
*imap_reply (MAILSTREAM
*stream
,char *tag
)
3564 IMAPPARSEDREPLY
*reply
;
3565 while (LOCAL
->netstream
) { /* parse reply from server */
3566 if ((reply
= imap_parse_reply (stream
,net_getline (LOCAL
->netstream
))) != NULL
) {
3567 /* continuation ready? */
3568 if (!strcmp (reply
->tag
,"+")) return reply
;
3569 /* untagged data? */
3570 else if (!strcmp (reply
->tag
,"*")) {
3571 imap_parse_unsolicited (stream
,reply
);
3572 if (!tag
) return reply
; /* return if just wanted greeting */
3574 else { /* tagged data */
3575 if (tag
&& !compare_cstring (tag
,reply
->tag
)) return reply
;
3577 sprintf (LOCAL
->tmp
,"Unexpected tagged response: %.80s %.80s %.80s",
3578 (char *) reply
->tag
,(char *) reply
->key
,(char *) reply
->text
);
3579 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3580 stream
->unhealthy
= T
;
3584 return imap_fake (stream
,tag
,
3585 "[CLOSED] IMAP connection broken (server response)");
3589 * Accepts: MAIL stream
3591 * Returns: parsed reply, or NIL if can't parse at least a tag and key
3595 IMAPPARSEDREPLY
*imap_parse_reply (MAILSTREAM
*stream
,char *text
)
3598 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
3599 /* init fields in case error */
3600 LOCAL
->reply
.key
= LOCAL
->reply
.text
= LOCAL
->reply
.tag
= NIL
;
3601 if (!(LOCAL
->reply
.line
= text
)) {
3602 /* NIL text means the stream died */
3603 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
3604 LOCAL
->netstream
= NIL
;
3607 if (stream
->debug
) mm_dlog (LOCAL
->reply
.line
);
3608 if (!(LOCAL
->reply
.tag
= strtok_r (LOCAL
->reply
.line
," ",&r
))) {
3609 mm_notify (stream
,"IMAP server sent a blank line",WARN
);
3610 stream
->unhealthy
= T
;
3613 /* non-continuation replies */
3614 if (strcmp (LOCAL
->reply
.tag
,"+")) {
3616 if (!(LOCAL
->reply
.key
= strtok_r (NIL
," ",&r
))) {
3617 /* determine what is missing */
3618 sprintf (LOCAL
->tmp
,"Missing IMAP reply key: %.80s",
3619 (char *) LOCAL
->reply
.tag
);
3620 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3621 stream
->unhealthy
= T
;
3622 return NIL
; /* can't parse this text */
3624 ucase (LOCAL
->reply
.key
); /* canonicalize key to upper */
3625 /* get text as well, allow empty text */
3626 if (!(LOCAL
->reply
.text
= strtok_r (NIL
,"\n",&r
)))
3627 LOCAL
->reply
.text
= LOCAL
->reply
.key
+ strlen (LOCAL
->reply
.key
);
3629 else { /* special handling of continuation */
3630 LOCAL
->reply
.key
= "BAD"; /* so it barfs if not expecting continuation */
3631 if (!(LOCAL
->reply
.text
= strtok_r (NIL
,"\n",&r
)))
3632 LOCAL
->reply
.text
= "";
3634 return &LOCAL
->reply
; /* return parsed reply */
3637 /* IMAP fake reply when stream determined to be dead
3638 * Accepts: MAIL stream
3640 * text of fake reply (must start with "[CLOSED]")
3641 * Returns: parsed reply
3644 IMAPPARSEDREPLY
*imap_fake (MAILSTREAM
*stream
,char *tag
,char *text
)
3646 mm_notify (stream
,text
,BYE
); /* send bye alert */
3647 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
3648 LOCAL
->netstream
= NIL
; /* farewell, dear NET stream... */
3649 /* flush previous reply */
3650 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
3651 /* build new fake reply */
3652 LOCAL
->reply
.tag
= LOCAL
->reply
.line
= cpystr (tag
? tag
: "*");
3653 LOCAL
->reply
.key
= "NO";
3654 LOCAL
->reply
.text
= text
;
3655 return &LOCAL
->reply
; /* return parsed reply */
3659 /* IMAP check for OK response in tagged reply
3660 * Accepts: MAIL stream
3662 * Returns: T if OK else NIL
3665 long imap_OK (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
3668 /* OK - operation succeeded */
3669 if (!strcmp (reply
->key
,"OK")) {
3670 imap_parse_response (stream
,reply
->text
,NIL
,NIL
);
3673 /* NO - operation failed */
3674 else if (!strcmp (reply
->key
,"NO"))
3675 imap_parse_response (stream
,reply
->text
,WARN
,NIL
);
3676 else { /* BAD - operation rejected */
3677 if (!strcmp (reply
->key
,"BAD")) {
3678 imap_parse_response (stream
,reply
->text
,ERROR
,NIL
);
3679 sprintf (LOCAL
->tmp
,"IMAP protocol error: %.80s",(char *) reply
->text
);
3681 /* bad protocol received */
3682 else sprintf (LOCAL
->tmp
,"Unexpected IMAP response: %.80s %.80s",
3683 (char *) reply
->key
,(char *) reply
->text
);
3684 mm_log (LOCAL
->tmp
,ERROR
); /* either way, this is not good */
3689 /* IMAP parse and act upon unsolicited reply
3690 * Accepts: MAIL stream
3694 void imap_parse_unsolicited (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
3696 unsigned long i
= 0;
3697 unsigned long j
,msgno
;
3698 unsigned char *s
,*t
;
3700 /* see if key is a number */
3701 if (isdigit (*reply
->key
)) {
3702 msgno
= strtoul (reply
->key
,(char **) &s
,10);
3703 if (*s
) { /* better be nothing after number */
3704 sprintf (LOCAL
->tmp
,"Unexpected untagged message: %.80s",
3705 (char *) reply
->key
);
3706 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3707 stream
->unhealthy
= T
;
3710 if (!reply
->text
) { /* better be some data */
3711 mm_notify (stream
,"Missing message data",WARN
);
3712 stream
->unhealthy
= T
;
3715 /* get message data type, canonicalize upper */
3716 s
= ucase (strtok_r (reply
->text
," ",&r
));
3717 t
= strtok_r (NIL
,"\n",&r
); /* and locate the text after it */
3718 /* now take the action */
3719 /* change in size of mailbox */
3720 if (!strcmp (s
,"EXISTS") && (msgno
>= stream
->nmsgs
))
3721 mail_exists (stream
,msgno
);
3722 else if (!strcmp (s
,"RECENT") && (msgno
<= stream
->nmsgs
))
3723 mail_recent (stream
,msgno
);
3724 else if (!strcmp (s
,"EXPUNGE") && msgno
&& (msgno
<= stream
->nmsgs
)) {
3725 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
3726 MESSAGECACHE
*elt
= (MESSAGECACHE
*) (*mc
) (stream
,msgno
,CH_ELT
);
3727 if (elt
) imap_gc_body (elt
->private.msg
.body
);
3728 /* notify upper level */
3729 mail_expunged (stream
,msgno
);
3732 else if (t
&& (!strcmp (s
,"FETCH") || !strcmp (s
,"STORE")) &&
3733 msgno
&& (msgno
<= stream
->nmsgs
)) {
3737 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
3738 ENVELOPE
*env
= NIL
;
3740 (imapenvelope_t
) mail_parameters (stream
,GET_IMAPENVELOPE
,NIL
);
3741 ++t
; /* skip past open parenthesis */
3742 /* parse Lisp-form property list */
3743 while ((prop
= (strtok_r (t
," )",&r
))) && (t
= strtok_r (NIL
,"\n",&r
))) {
3744 INIT_GETS (md
,stream
,elt
->msgno
,NIL
,0,0);
3745 e
= NIL
; /* not pointing at any envelope yet */
3746 /* canonicalize property, parse it */
3747 if (!strcmp (ucase (prop
),"FLAGS")) imap_parse_flags (stream
,elt
,&t
);
3748 else if (!strcmp (prop
,"INTERNALDATE") &&
3749 (s
= imap_parse_string (stream
,&t
,reply
,NIL
,NIL
,LONGT
))) {
3750 if (!mail_parse_date (elt
,s
)) {
3751 sprintf (LOCAL
->tmp
,"Bogus date: %.80s",(char *) s
);
3752 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3753 stream
->unhealthy
= T
;
3754 /* slam in default so we don't try again */
3755 mail_parse_date (elt
,"01-Jan-1970 00:00:00 +0000");
3757 fs_give ((void **) &s
);
3759 /* unique identifier */
3760 else if (!strcmp (prop
,"UID")) {
3761 LOCAL
->lastuid
.uid
= elt
->private.uid
= strtoul (t
,(char **) &t
,10);
3762 LOCAL
->lastuid
.msgno
= elt
->msgno
;
3764 else if (!strcmp (prop
,"ENVELOPE")) {
3765 if (stream
->scache
) { /* short cache, flush old stuff */
3766 mail_free_body (&stream
->body
);
3767 stream
->msgno
= elt
->msgno
;
3768 e
= &stream
->env
; /* get pointer to envelope */
3770 else e
= &elt
->private.msg
.env
;
3771 imap_parse_envelope (stream
,e
,&t
,reply
);
3773 else if (!strncmp (prop
,"BODY",4)) {
3774 if (!prop
[4] || !strcmp (prop
+4,"STRUCTURE")) {
3776 if (stream
->scache
){/* short cache, flush old stuff */
3777 if (stream
->msgno
!= msgno
) {
3778 mail_free_envelope (&stream
->env
);
3779 sprintf (LOCAL
->tmp
,"Body received for %lu but current is %lu",
3780 msgno
,stream
->msgno
);
3781 stream
->msgno
= msgno
;
3783 /* get pointer to body */
3784 body
= &stream
->body
;
3786 else body
= &elt
->private.msg
.body
;
3787 /* flush any prior body */
3788 mail_free_body (body
);
3789 /* instantiate and parse a new body */
3790 imap_parse_body_structure (stream
,*body
= mail_newbody(),&t
,reply
);
3793 else if (prop
[4] == '[') {
3794 STRINGLIST
*stl
= NIL
;
3796 /* will want to return envelope data */
3797 if (!strcmp (md
.what
= cpystr (prop
+ 5),"HEADER]") ||
3798 !strcmp (md
.what
,"0]"))
3799 e
= stream
->scache
? &stream
->env
: &elt
->private.msg
.env
;
3800 LOCAL
->tmp
[0] ='\0';/* no errors yet */
3801 /* found end of section? */
3802 if (!(s
= strchr (md
.what
,']'))) {
3803 /* skip leading nesting */
3804 for (s
= md
.what
; *s
&& (isdigit (*s
) || (*s
== '.')); s
++);
3805 /* better be one of these */
3806 if (strncmp (s
,"HEADER.FIELDS",13) &&
3807 (!s
[13] || strcmp (s
+13,".NOT")))
3808 sprintf (LOCAL
->tmp
,"Unterminated section: %.80s",md
.what
);
3809 /* get list of headers */
3810 else if (!(stl
= imap_parse_stringlist (stream
,&t
,reply
)))
3811 sprintf (LOCAL
->tmp
,"Bogus header field list: %.80s",
3814 sprintf (LOCAL
->tmp
,"Unterminated header section: %.80s",
3816 /* point after the text */
3817 else if ((t
= strchr (s
= t
,' ')) != NULL
) *t
++ = '\0';
3819 if (s
&& !LOCAL
->tmp
[0]) {
3820 *s
++ = '\0'; /* tie off section specifier */
3821 if (*s
== '<') { /* partial specifier? */
3822 md
.first
= strtoul (s
+1,(char **) &s
,10) + 1;
3823 if (*s
++ != '>') /* make sure properly terminated */
3824 sprintf (LOCAL
->tmp
,"Unterminated partial data: %.80s",
3827 if (!LOCAL
->tmp
[0] && *s
)
3828 sprintf (LOCAL
->tmp
,"Junk after section: %.80s",(char *) s
);
3830 if (LOCAL
->tmp
[0]) { /* got any errors? */
3831 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3832 stream
->unhealthy
= T
;
3833 mail_free_stringlist (&stl
);
3835 else { /* parse text from server */
3836 text
.data
= (unsigned char *)
3837 imap_parse_string (stream
,&t
,reply
,
3838 ((md
.what
[0] && (md
.what
[0] != 'H')) ||
3839 md
.first
|| md
.last
) ? &md
: NIL
,
3841 /* all done if partial */
3842 if (md
.first
|| md
.last
) mail_free_stringlist (&stl
);
3843 /* otherwise register it in the cache */
3844 else imap_cache (stream
,msgno
,md
.what
,stl
,&text
);
3846 fs_give ((void **) &md
.what
);
3849 sprintf (LOCAL
->tmp
,"Unknown body message property: %.80s",prop
);
3850 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3851 stream
->unhealthy
= T
;
3855 /* one of the RFC822 props? */
3856 else if (!strncmp (prop
,"RFC822",6) && (!prop
[6] || (prop
[6] == '.'))){
3858 if (!prop
[6]) { /* cache full message */
3860 text
.data
= (unsigned char *)
3861 imap_parse_string (stream
,&t
,reply
,&md
,&text
.size
,NIL
);
3862 imap_cache (stream
,msgno
,md
.what
,NIL
,&text
);
3864 else if (!strcmp (prop
+7,"SIZE"))
3865 elt
->rfc822_size
= strtoul (t
,(char **) &t
,10);
3866 /* legacy properties */
3867 else if (!strcmp (prop
+7,"HEADER")) {
3868 text
.data
= (unsigned char *)
3869 imap_parse_string (stream
,&t
,reply
,NIL
,&text
.size
,NIL
);
3870 imap_cache (stream
,msgno
,"HEADER",NIL
,&text
);
3871 e
= stream
->scache
? &stream
->env
: &elt
->private.msg
.env
;
3873 else if (!strcmp (prop
+7,"TEXT")) {
3875 text
.data
= (unsigned char *)
3876 imap_parse_string (stream
,&t
,reply
,&md
,&text
.size
,NIL
);
3877 imap_cache (stream
,msgno
,md
.what
,NIL
,&text
);
3880 sprintf (LOCAL
->tmp
,"Unknown RFC822 message property: %.80s",prop
);
3881 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3882 stream
->unhealthy
= T
;
3886 sprintf (LOCAL
->tmp
,"Unknown message property: %.80s",prop
);
3887 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3888 stream
->unhealthy
= T
;
3890 if (e
&& *e
) env
= *e
; /* note envelope if we got one */
3893 sprintf (LOCAL
->tmp
,"Missing data for property: %.80s",prop
);
3894 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3895 stream
->unhealthy
= T
;
3897 /* do callback if requested */
3898 if (ie
&& env
) (*ie
) (stream
,msgno
,env
);
3900 /* obsolete response to COPY */
3901 else if (strcmp (s
,"COPY")) {
3902 sprintf (LOCAL
->tmp
,"Unknown message data: %lu %.80s",msgno
,(char *) s
);
3903 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3904 stream
->unhealthy
= T
;
3908 else if (!strcmp (reply
->key
,"FLAGS") && reply
->text
&&
3909 (*reply
->text
== '(') &&
3910 (s
= strtok_r (reply
->text
+1," )",&r
)))
3911 do if (*s
!= '\\') {
3912 for (i
= 0; (i
< NUSERFLAGS
) && stream
->user_flags
[i
] &&
3913 compare_cstring (s
,stream
->user_flags
[i
]); i
++);
3914 if (i
> NUSERFLAGS
) {
3915 sprintf (LOCAL
->tmp
,"Too many server flags, discarding: %.80s",
3917 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3919 else if (!stream
->user_flags
[i
]) stream
->user_flags
[i
++] = cpystr (s
);
3921 while ((s
= strtok_r (NIL
," )",&r
)) != NULL
);
3922 else if (!strcmp (reply
->key
,"SEARCH")) {
3923 /* only do something if have text */
3924 if (reply
->text
&& (t
= strtok_r (reply
->text
," ",&r
))) do
3925 if ((i
= strtoul (t
,NIL
,10)) != 0L) {
3926 /* UIDs always passed to main program */
3927 if (LOCAL
->uidsearch
) mm_searched (stream
,i
);
3928 /* should be a msgno then */
3929 else if ((i
<= stream
->nmsgs
) &&
3930 (!LOCAL
->filter
|| mail_elt (stream
,i
)->private.filter
)) {
3931 mail_elt (stream
,i
)->searched
= T
;
3932 if (!stream
->silent
) mm_searched (stream
,i
);
3934 } while ((t
= strtok_r (NIL
," ",&r
)) != NULL
);
3936 else if (!strcmp (reply
->key
,"SORT")) {
3937 sortresults_t sr
= (sortresults_t
)
3938 mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
3939 LOCAL
->sortsize
= 0; /* initialize sort data */
3940 if (LOCAL
->sortdata
) fs_give ((void **) &LOCAL
->sortdata
);
3941 LOCAL
->sortdata
= (unsigned long *)
3942 fs_get ((stream
->nmsgs
+ 1) * sizeof (unsigned long));
3943 /* only do something if have text */
3944 if (reply
->text
&& (t
= strtok_r (reply
->text
," ",&r
))) {
3945 do if ((i
= atol (t
)) && (LOCAL
->filter
?
3946 mail_elt (stream
,i
)->searched
: T
))
3947 LOCAL
->sortdata
[LOCAL
->sortsize
++] = i
;
3948 while ((t
= strtok_r (NIL
," ",&r
)) && (LOCAL
->sortsize
< stream
->nmsgs
));
3950 LOCAL
->sortdata
[LOCAL
->sortsize
] = 0;
3951 /* also return via callback if requested */
3952 if (sr
) (*sr
) (stream
,LOCAL
->sortdata
,LOCAL
->sortsize
);
3954 else if (!strcmp (reply
->key
,"THREAD")) {
3955 threadresults_t tr
= (threadresults_t
)
3956 mail_parameters (NIL
,GET_THREADRESULTS
,NIL
);
3957 if (LOCAL
->threaddata
) mail_free_threadnode (&LOCAL
->threaddata
);
3958 if ((s
= reply
->text
) != NULL
) {
3959 LOCAL
->threaddata
= imap_parse_thread (stream
,&s
);
3960 if (tr
) (*tr
) (stream
,LOCAL
->threaddata
);
3962 sprintf (LOCAL
->tmp
,"Junk at end of thread: %.80s",(char *) s
);
3963 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3964 stream
->unhealthy
= T
;
3969 else if (!strcmp (reply
->key
,"STATUS") && reply
->text
) {
3971 unsigned char *txt
= reply
->text
;
3972 if ((t
= imap_parse_astring (stream
,&txt
,reply
,&j
)) && txt
&&
3973 (*txt
++ == ' ') && (*txt
++ == '(') && (s
= strchr (txt
,')')) &&
3974 (s
- txt
) && !s
[1]) {
3975 *s
= '\0'; /* tie off status data */
3976 /* initialize data block */
3977 status
.flags
= status
.messages
= status
.recent
= status
.unseen
=
3978 status
.uidnext
= status
.uidvalidity
= 0;
3979 while (*txt
&& (s
= strchr (txt
,' '))) {
3980 *s
++ = '\0'; /* tie off status attribute name */
3981 /* get attribute value */
3982 i
= strtoul (s
,(char **) &s
,10);
3983 if (!compare_cstring (txt
,"MESSAGES")) {
3984 status
.flags
|= SA_MESSAGES
;
3985 status
.messages
= i
;
3987 else if (!compare_cstring (txt
,"RECENT")) {
3988 status
.flags
|= SA_RECENT
;
3991 else if (!compare_cstring (txt
,"UNSEEN")) {
3992 status
.flags
|= SA_UNSEEN
;
3995 else if (!compare_cstring (txt
,"UIDNEXT")) {
3996 status
.flags
|= SA_UIDNEXT
;
3999 else if (!compare_cstring (txt
,"UIDVALIDITY")) {
4000 status
.flags
|= SA_UIDVALIDITY
;
4001 status
.uidvalidity
= i
;
4003 /* next attribute */
4004 txt
= (*s
== ' ') ? s
+ 1 : s
;
4006 if (((i
= 1 + strchr (stream
->mailbox
,'}') - stream
->mailbox
) + j
) <
4008 strcpy (strncpy (LOCAL
->tmp
,stream
->mailbox
,i
) + i
,t
);
4009 /* pass status to main program */
4010 mm_status (stream
,LOCAL
->tmp
,&status
);
4013 if (t
) fs_give ((void **) &t
);
4016 else if ((!strcmp (reply
->key
,"LIST") || !strcmp (reply
->key
,"LSUB")) &&
4017 reply
->text
&& (*reply
->text
== '(') &&
4018 (s
= strchr (reply
->text
,')')) && (s
[1] == ' ')) {
4019 char delimiter
= '\0';
4020 *s
++ = '\0'; /* tie off attribute list */
4021 /* parse attribute list */
4022 if ((t
= strtok_r (reply
->text
+1," ",&r
)) != NULL
) do {
4023 if (!compare_cstring (t
,"\\NoInferiors")) i
|= LATT_NOINFERIORS
;
4024 else if (!compare_cstring (t
,"\\NoSelect")) i
|= LATT_NOSELECT
;
4025 else if (!compare_cstring (t
,"\\Marked")) i
|= LATT_MARKED
;
4026 else if (!compare_cstring (t
,"\\Unmarked")) i
|= LATT_UNMARKED
;
4027 else if (!compare_cstring (t
,"\\HasChildren")) i
|= LATT_HASCHILDREN
;
4028 else if (!compare_cstring (t
,"\\HasNoChildren")) i
|= LATT_HASNOCHILDREN
;
4029 /* ignore extension flags */
4031 while ((t
= strtok_r (NIL
," ",&r
)) != NULL
);
4032 switch (*++s
) { /* process delimiter */
4035 s
+= 4; /* skip over NIL<space> */
4037 case '"': /* have a delimiter */
4038 delimiter
= (*++s
== '\\') ? *++s
: *s
;
4039 s
+= 3; /* skip over <delimiter><quote><space> */
4041 /* parse the mailbox name */
4042 if ((t
= imap_parse_astring (stream
,&s
,reply
,&j
)) != NULL
) {
4043 /* prepend prefix if requested */
4044 if (LOCAL
->prefix
&& ((strlen (LOCAL
->prefix
) + j
) < IMAPTMPLEN
))
4045 sprintf (s
= LOCAL
->tmp
,"%s%s",LOCAL
->prefix
,(char *) t
);
4046 else s
= t
; /* otherwise just mailbox name */
4047 /* pass data to main program */
4048 if (reply
->key
[1] == 'S') mm_lsub (stream
,delimiter
,s
,i
);
4049 else mm_list (stream
,delimiter
,s
,i
);
4050 fs_give ((void **) &t
); /* flush mailbox name */
4053 else if (!strcmp (reply
->key
,"NAMESPACE")) {
4054 if (LOCAL
->namespace) {
4055 mail_free_namespace (&LOCAL
->namespace[0]);
4056 mail_free_namespace (&LOCAL
->namespace[1]);
4057 mail_free_namespace (&LOCAL
->namespace[2]);
4059 else LOCAL
->namespace = (NAMESPACE
**) fs_get (3 * sizeof (NAMESPACE
*));
4060 if ((s
= reply
->text
) != NULL
) { /* parse namespace results */
4061 LOCAL
->namespace[0] = imap_parse_namespace (stream
,&s
,reply
);
4062 LOCAL
->namespace[1] = imap_parse_namespace (stream
,&s
,reply
);
4063 LOCAL
->namespace[2] = imap_parse_namespace (stream
,&s
,reply
);
4065 sprintf (LOCAL
->tmp
,"Junk after namespace list: %.80s",(char *) s
);
4066 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4067 stream
->unhealthy
= T
;
4071 mm_notify (stream
,"Missing namespace list",WARN
);
4072 stream
->unhealthy
= T
;
4076 else if (!strcmp (reply
->key
,"ACL") && (s
= reply
->text
) &&
4077 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4078 getacl_t ar
= (getacl_t
) mail_parameters (NIL
,GET_ACL
,NIL
);
4079 if (s
&& (*s
++ == ' ')) {
4080 ACLLIST
*al
= mail_newacllist ();
4082 do if ((ac
->identifier
= imap_parse_astring (stream
,&s
,reply
,NIL
)) &&
4084 ac
->rights
= imap_parse_astring (stream
,&s
,reply
,NIL
);
4085 while (ac
->rights
&& s
&& (*s
== ' ') && s
++ &&
4086 (ac
= ac
->next
= mail_newacllist ()));
4087 if (!ac
->rights
|| (s
&& *s
)) {
4088 sprintf (LOCAL
->tmp
,"Invalid ACL identifer/rights for %.80s",
4090 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4091 stream
->unhealthy
= T
;
4093 else if (ar
) (*ar
) (stream
,t
,al
);
4094 mail_free_acllist (&al
); /* clean up */
4096 /* no optional rights */
4097 else if (ar
) (*ar
) (stream
,t
,NIL
);
4098 fs_give ((void **) &t
); /* free mailbox name */
4101 else if (!strcmp (reply
->key
,"LISTRIGHTS") && (s
= reply
->text
) &&
4102 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4103 listrights_t lr
= (listrights_t
) mail_parameters (NIL
,GET_LISTRIGHTS
,NIL
);
4105 if (s
&& (*s
++ == ' ') && (id
= imap_parse_astring (stream
,&s
,reply
,NIL
))){
4106 if (s
&& (*s
++ == ' ') &&
4107 (r
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4108 if (s
&& (*s
++ == ' ')) {
4109 STRINGLIST
*rl
= mail_newstringlist ();
4110 STRINGLIST
*rc
= rl
;
4111 do rc
->text
.data
= (unsigned char *)
4112 imap_parse_astring (stream
,&s
,reply
,&rc
->text
.size
);
4113 while (rc
->text
.data
&& s
&& (*s
== ' ') && s
++ &&
4114 (rc
= rc
->next
= mail_newstringlist ()));
4115 if (!rc
->text
.data
|| (s
&& *s
)) {
4116 sprintf (LOCAL
->tmp
,"Invalid optional LISTRIGHTS for %.80s",
4118 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4119 stream
->unhealthy
= T
;
4121 else if (lr
) (*lr
) (stream
,t
,id
,r
,rl
);
4123 mail_free_stringlist (&rl
);
4125 /* no optional rights */
4126 else if (lr
) (*lr
) (stream
,t
,id
,r
,NIL
);
4127 fs_give ((void **) &r
); /* free rights */
4130 sprintf (LOCAL
->tmp
,"Missing LISTRIGHTS rights for %.80s",(char *) t
);
4131 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4132 stream
->unhealthy
= T
;
4134 fs_give ((void **) &id
); /* free identifier */
4137 sprintf (LOCAL
->tmp
,"Missing LISTRIGHTS identifer for %.80s",(char *) t
);
4138 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4139 stream
->unhealthy
= T
;
4141 fs_give ((void **) &t
); /* free mailbox name */
4144 else if (!strcmp (reply
->key
,"MYRIGHTS") && (s
= reply
->text
) &&
4145 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4146 myrights_t mr
= (myrights_t
) mail_parameters (NIL
,GET_MYRIGHTS
,NIL
);
4148 if (s
&& (*s
++ == ' ') && (r
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4150 sprintf (LOCAL
->tmp
,"Junk after MYRIGHTS for %.80s",(char *) t
);
4151 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4152 stream
->unhealthy
= T
;
4154 else if (mr
) (*mr
) (stream
,t
,r
);
4155 fs_give ((void **) &r
); /* free rights */
4158 sprintf (LOCAL
->tmp
,"Missing MYRIGHTS for %.80s",(char *) t
);
4159 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4160 stream
->unhealthy
= T
;
4162 fs_give ((void **) &t
); /* free mailbox name */
4165 /* this response has a bizarre syntax! */
4166 else if (!strcmp (reply
->key
,"QUOTA") && (s
= reply
->text
) &&
4167 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4169 sprintf (LOCAL
->tmp
,"Bad quota resource list for %.80s",(char *) t
);
4170 if (s
&& (*s
++ == ' ') && (*s
++ == '(') && *s
&& ((*s
!= ')') || !s
[1])) {
4171 quota_t qt
= (quota_t
) mail_parameters (NIL
,GET_QUOTA
,NIL
);
4172 QUOTALIST
*ql
= NIL
;
4174 /* parse non-empty quota resource list */
4175 if (*s
!= ')') for (ql
= qc
= mail_newquotalist (); T
;
4176 qc
= qc
->next
= mail_newquotalist ()) {
4177 if ((qc
->name
= imap_parse_astring (stream
,&s
,reply
,NIL
)) && s
&&
4178 (*s
++ == ' ') && (isdigit (*s
) || (LOCAL
->loser
&& (*s
== '-')))) {
4179 if (isdigit (*s
)) qc
->usage
= strtoul (s
,(char **) &s
,10);
4180 else if ((t
= strchr (s
,' ')) != NULL
) t
= s
;
4181 if ((*s
++ == ' ') && (isdigit (*s
) || (LOCAL
->loser
&&(*s
== '-')))){
4182 if (isdigit (*s
)) qc
->limit
= strtoul (s
,(char **) &s
,10);
4183 else if ((t
= strpbrk (s
," )")) != NULL
) t
= s
;
4184 /* another resource follows? */
4185 if (*s
== ' ') continue;
4186 /* end of resource list? */
4187 if ((*s
== ')') && !s
[1]) {
4188 if (qt
) (*qt
) (stream
,t
,ql
);
4189 break; /* all done */
4193 /* something bad happened */
4194 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4195 stream
->unhealthy
= T
;
4196 break; /* parse failed */
4198 /* all done with quota resource list now */
4199 if (ql
) mail_free_quotalist (&ql
);
4202 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4203 stream
->unhealthy
= T
;
4205 fs_give ((void **) &t
); /* free root name */
4207 else if (!strcmp (reply
->key
,"QUOTAROOT") && (s
= reply
->text
) &&
4208 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4209 sprintf (LOCAL
->tmp
,"Bad quota root list for %.80s",(char *) t
);
4210 if (s
&& (*s
++ == ' ')) {
4211 quotaroot_t qr
= (quotaroot_t
) mail_parameters (NIL
,GET_QUOTAROOT
,NIL
);
4212 STRINGLIST
*rl
= mail_newstringlist ();
4213 STRINGLIST
*rc
= rl
;
4214 do rc
->text
.data
= (unsigned char *)
4215 imap_parse_astring (stream
,&s
,reply
,&rc
->text
.size
);
4216 while (rc
->text
.data
&& *s
&& (*s
++ == ' ') &&
4217 (rc
= rc
->next
= mail_newstringlist ()));
4218 if (!rc
->text
.data
|| (s
&& *s
)) {
4219 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4220 stream
->unhealthy
= T
;
4222 else if (qr
) (*qr
) (stream
,t
,rl
);
4224 mail_free_stringlist (&rl
);
4227 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4228 stream
->unhealthy
= T
;
4230 fs_give ((void **) &t
);
4233 else if (!strcmp (reply
->key
,"OK") || !strcmp (reply
->key
,"PREAUTH"))
4234 imap_parse_response (stream
,reply
->text
,NIL
,T
);
4235 else if (!strcmp (reply
->key
,"NO"))
4236 imap_parse_response (stream
,reply
->text
,WARN
,T
);
4237 else if (!strcmp (reply
->key
,"BAD"))
4238 imap_parse_response (stream
,reply
->text
,ERROR
,T
);
4239 else if (!strcmp (reply
->key
,"BYE")) {
4240 LOCAL
->byeseen
= T
; /* note that a BYE seen */
4241 imap_parse_response (stream
,reply
->text
,BYE
,T
);
4243 else if (!strcmp (reply
->key
,"CAPABILITY") && reply
->text
)
4244 imap_parse_capabilities (stream
,reply
->text
);
4245 else if (!strcmp (reply
->key
,"MAILBOX") && reply
->text
) {
4246 if (LOCAL
->prefix
&&
4247 ((strlen (LOCAL
->prefix
) + strlen (reply
->text
)) < IMAPTMPLEN
))
4248 sprintf (t
= LOCAL
->tmp
,"%s%s",LOCAL
->prefix
,(char *) reply
->text
);
4249 else t
= reply
->text
;
4250 mm_list (stream
,NIL
,t
,NIL
);
4253 sprintf (LOCAL
->tmp
,"Unexpected untagged message: %.80s",
4254 (char *) reply
->key
);
4255 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4256 stream
->unhealthy
= T
;
4260 /* Parse human-readable response text
4261 * Accepts: mail stream
4263 * error level for mm_notify()
4264 * non-NIL if want mm_notify() event even if no response code
4267 void imap_parse_response (MAILSTREAM
*stream
,char *text
,long errflg
,long ntfy
)
4275 SEARCHSET
*source
= NIL
;
4276 SEARCHSET
*dest
= NIL
;
4277 if (text
&& (*text
== '[') && (t
= strchr (s
= text
+ 1,']')) &&
4278 ((i
= t
- s
) < IMAPTMPLEN
)) {
4279 LOCAL
->tmp
[i
] = '\0'; /* make mungable copy of text code */
4280 if ((s
= strchr (strncpy (t
= LOCAL
->tmp
,s
,i
),' ')) != NULL
) *s
++ = '\0';
4281 if (s
) { /* have argument? */
4282 ntfy
= NIL
; /* suppress mm_notify if normal SELECT data */
4283 if (!compare_cstring (t
,"CAPABILITY")) imap_parse_capabilities(stream
,s
);
4284 else if (!compare_cstring (t
,"PERMANENTFLAGS") && (*s
== '(') &&
4286 t
[i
-1] = '\0'; /* tie off flags */
4287 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_answered
=
4288 stream
->perm_draft
= stream
->kwd_create
= NIL
;
4289 stream
->perm_user_flags
= NIL
;
4290 if ((s
= strtok_r (s
+1," ",&r
)) != NULL
) do {
4291 if (*s
== '\\') { /* system flags */
4292 if (!compare_cstring (s
,"\\Seen")) stream
->perm_seen
= T
;
4293 else if (!compare_cstring (s
,"\\Deleted"))
4294 stream
->perm_deleted
= T
;
4295 else if (!compare_cstring (s
,"\\Flagged"))
4296 stream
->perm_flagged
= T
;
4297 else if (!compare_cstring (s
,"\\Answered"))
4298 stream
->perm_answered
= T
;
4299 else if (!compare_cstring (s
,"\\Draft")) stream
->perm_draft
= T
;
4300 else if (!strcmp (s
,"\\*")) stream
->kwd_create
= T
;
4302 else stream
->perm_user_flags
|= imap_parse_user_flag (stream
,s
);
4304 while ((s
= strtok_r (NIL
," ",&r
)) != NULL
);
4307 else if (!compare_cstring (t
,"UIDVALIDITY") && (j
= strtoul (s
,NIL
,10))){
4308 /* do this in separate if because of ntfy */
4309 if (j
!= stream
->uid_validity
) {
4310 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
4311 stream
->uid_validity
= j
;
4312 /* purge any UIDs in cache */
4313 for (j
= 1; j
<= stream
->nmsgs
; j
++)
4314 if ((elt
= (MESSAGECACHE
*) (*mc
) (stream
,j
,CH_ELT
)) != NULL
)
4315 elt
->private.uid
= 0;
4318 else if (!compare_cstring (t
,"UIDNEXT"))
4319 stream
->uid_last
= strtoul (s
,NIL
,10) - 1;
4320 else if ((j
= LEVELUIDPLUS (stream
) && LOCAL
->appendmailbox
) &&
4321 !compare_cstring (t
,"COPYUID") &&
4322 (cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
)) &&
4323 isdigit (*s
) && (j
= strtoul (s
,&s
,10)) && (*s
++ == ' ') &&
4324 (source
= mail_parse_set (s
,&s
)) && (*s
++ == ' ') &&
4325 (dest
= mail_parse_set (s
,&s
)) && !*s
)
4326 (*cu
) (stream
,LOCAL
->appendmailbox
,j
,source
,dest
);
4327 else if (j
&& !compare_cstring (t
,"APPENDUID") &&
4328 (au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
)) &&
4329 isdigit (*s
) && (j
= strtoul (s
,&s
,10)) && (*s
++ == ' ') &&
4330 (dest
= mail_parse_set (s
,&s
)) && !*s
)
4331 (*au
) (LOCAL
->appendmailbox
,j
,dest
);
4332 else { /* all other response code events */
4333 ntfy
= T
; /* must mm_notify() */
4334 if (!compare_cstring (t
,"REFERRAL"))
4335 LOCAL
->referral
= cpystr (t
+ 9);
4337 mail_free_searchset (&source
);
4338 mail_free_searchset (&dest
);
4340 else { /* no arguments */
4341 if (!compare_cstring (t
,"UIDNOTSTICKY")) {
4343 stream
->uid_nosticky
= T
;
4345 else if (!compare_cstring (t
,"READ-ONLY")) stream
->rdonly
= T
;
4346 else if (!compare_cstring (t
,"READ-WRITE"))
4347 stream
->rdonly
= NIL
;
4348 else if (!compare_cstring (t
,"PARSE") && !errflg
)
4352 /* give event to main program */
4353 if (ntfy
&& !stream
->silent
) mm_notify (stream
,text
? text
: "",errflg
);
4356 /* Parse a namespace
4357 * Accepts: mail stream
4358 * current text pointer
4360 * Returns: namespace list, text pointer updated
4363 NAMESPACE
*imap_parse_namespace (MAILSTREAM
*stream
,unsigned char **txtptr
,
4364 IMAPPARSEDREPLY
*reply
)
4366 NAMESPACE
*ret
= NIL
;
4367 NAMESPACE
*nam
= NIL
;
4368 NAMESPACE
*prev
= NIL
;
4369 PARAMETER
*par
= NIL
;
4370 if (*txtptr
) { /* only if argument given */
4371 /* ignore leading space */
4372 while (**txtptr
== ' ') ++*txtptr
;
4374 case 'N': /* if NIL */
4376 ++*txtptr
; /* bump past "N" */
4377 ++*txtptr
; /* bump past "I" */
4378 ++*txtptr
; /* bump past "L" */
4381 ++*txtptr
; /* skip past open paren */
4382 while (**txtptr
== '(') {
4383 ++*txtptr
; /* skip past open paren */
4384 prev
= nam
; /* note previous if any */
4385 nam
= (NAMESPACE
*) memset (fs_get (sizeof (NAMESPACE
)),0,
4386 sizeof (NAMESPACE
));
4387 if (!ret
) ret
= nam
; /* if first time note first namespace */
4388 /* if previous link new block to it */
4389 if (prev
) prev
->next
= nam
;
4390 nam
->name
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,NIL
);
4391 /* ignore whitespace */
4392 while (**txtptr
== ' ') ++*txtptr
;
4393 switch (**txtptr
) { /* parse delimiter */
4396 *txtptr
+= 3; /* bump past "NIL" */
4399 if (*++*txtptr
== '\\') nam
->delimiter
= *++*txtptr
;
4400 else nam
->delimiter
= **txtptr
;
4401 *txtptr
+= 2; /* bump past character and closing quote */
4404 sprintf (LOCAL
->tmp
,"Missing delimiter in namespace: %.80s",
4406 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4407 stream
->unhealthy
= T
;
4408 *txtptr
= NIL
; /* stop parse */
4412 while (**txtptr
== ' '){/* append new parameter to tail */
4413 if (nam
->param
) par
= par
->next
= mail_newbody_parameter ();
4414 else nam
->param
= par
= mail_newbody_parameter ();
4415 if (!(par
->attribute
= imap_parse_string (stream
,txtptr
,reply
,NIL
,
4417 mm_notify (stream
,"Missing namespace extension attribute",WARN
);
4418 stream
->unhealthy
= T
;
4419 par
->attribute
= cpystr ("UNKNOWN");
4422 while (**txtptr
== ' ') ++*txtptr
;
4423 if (**txtptr
== '(') {/* have value list? */
4424 char *att
= par
->attribute
;
4425 ++*txtptr
; /* yes */
4426 do { /* parse each value */
4427 if (!(par
->value
= imap_parse_string (stream
,txtptr
,reply
,NIL
,
4429 sprintf (LOCAL
->tmp
,
4430 "Missing value for namespace attribute %.80s",att
);
4431 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4432 stream
->unhealthy
= T
;
4433 par
->value
= cpystr ("UNKNOWN");
4435 /* is there another value? */
4436 if (**txtptr
== ' ') par
= par
->next
= mail_newbody_parameter ();
4437 } while (!par
->value
);
4440 sprintf (LOCAL
->tmp
,"Missing values for namespace attribute %.80s",
4442 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4443 stream
->unhealthy
= T
;
4444 par
->value
= cpystr ("UNKNOWN");
4447 if (**txtptr
== ')') ++*txtptr
;
4448 else { /* missing trailing paren */
4449 sprintf (LOCAL
->tmp
,"Junk at end of namespace: %.80s",
4451 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4452 stream
->unhealthy
= T
;
4456 if (**txtptr
== ')') { /* expected trailing paren? */
4457 ++*txtptr
; /* got it! */
4461 sprintf (LOCAL
->tmp
,"Not a namespace: %.80s",(char *) *txtptr
);
4462 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4463 stream
->unhealthy
= T
;
4464 *txtptr
= NIL
; /* stop parse now */
4471 /* Parse a thread node list
4472 * Accepts: mail stream
4473 * current text pointer
4474 * Returns: thread node list, text pointer updated
4477 THREADNODE
*imap_parse_thread (MAILSTREAM
*stream
,unsigned char **txtptr
)
4480 THREADNODE
*ret
= NIL
; /* returned tree */
4481 THREADNODE
*last
= NIL
; /* last branch in this tree */
4482 THREADNODE
*parent
= NIL
; /* parent of current node */
4483 THREADNODE
*cur
; /* current node */
4484 while (**txtptr
== '(') { /* see a thread? */
4485 ++*txtptr
; /* skip past open paren */
4486 while (**txtptr
!= ')') { /* parse thread */
4487 if (**txtptr
== '(') { /* thread branch */
4488 cur
= imap_parse_thread (stream
,txtptr
);
4490 if (parent
) parent
= parent
->next
= cur
;
4491 else { /* no parent, create dummy */
4492 if (last
) last
= last
->branch
= mail_newthreadnode (NIL
);
4494 else ret
= last
= mail_newthreadnode (NIL
);
4495 /* add to dummy parent */
4496 last
->next
= parent
= cur
;
4499 /* threaded message number */
4500 else if (isdigit (*(s
= *txtptr
)) &&
4501 ((cur
= mail_newthreadnode (NIL
))->num
=
4502 strtoul (*txtptr
,(char **) txtptr
,10))) {
4503 if (LOCAL
->filter
&& !mail_elt (stream
,cur
->num
)->searched
)
4504 cur
->num
= NIL
; /* make dummy if filtering and not searched */
4506 if (parent
) parent
= parent
->next
= cur
;
4507 /* no parent, start new thread */
4508 else if (last
) last
= last
->branch
= parent
= cur
;
4509 /* create new tree */
4510 else ret
= last
= parent
= cur
;
4512 else { /* anything else is a bogon */
4513 char tmp
[MAILTMPLEN
];
4514 sprintf (tmp
,"Bogus thread member: %.80s",s
);
4515 mm_notify (stream
,tmp
,WARN
);
4516 stream
->unhealthy
= T
;
4519 /* skip past any space */
4520 if (**txtptr
== ' ') ++*txtptr
;
4522 ++*txtptr
; /* skip pase end of thread */
4523 parent
= NIL
; /* close this thread */
4525 return ret
; /* return parsed thread */
4528 /* Parse RFC822 message header
4529 * Accepts: MAIL stream
4530 * envelope to parse into
4531 * header as sized text
4532 * stringlist if partial header
4535 void imap_parse_header (MAILSTREAM
*stream
,ENVELOPE
**env
,SIZEDTEXT
*hdr
,
4539 /* parse what we can from this header */
4540 rfc822_parse_msg (&nenv
,NIL
,(char *) hdr
->data
,hdr
->size
,NIL
,
4541 net_host (LOCAL
->netstream
),stream
->dtb
->flags
);
4542 if (*env
) { /* need to merge this header into envelope? */
4543 if (!(*env
)->newsgroups
) { /* need Newsgroups? */
4544 (*env
)->newsgroups
= nenv
->newsgroups
;
4545 (*env
)->ngpathexists
= nenv
->ngpathexists
;
4546 nenv
->newsgroups
= NIL
;
4548 if (!(*env
)->followup_to
) { /* need Followup-To? */
4549 (*env
)->followup_to
= nenv
->followup_to
;
4550 nenv
->followup_to
= NIL
;
4552 if (!(*env
)->references
) { /* need References? */
4553 (*env
)->references
= nenv
->references
;
4554 nenv
->references
= NIL
;
4556 if (!(*env
)->sparep
) { /* need spare pointer? */
4557 (*env
)->sparep
= nenv
->sparep
;
4560 mail_free_envelope (&nenv
);
4561 (*env
)->imapenvonly
= NIL
; /* have complete envelope now */
4563 /* otherwise set it to this envelope */
4564 else (*env
= nenv
)->incomplete
= stl
? T
: NIL
;
4567 /* IMAP parse envelope
4568 * Accepts: MAIL stream
4569 * pointer to envelope pointer
4570 * current text pointer
4573 * Updates text pointer
4576 void imap_parse_envelope (MAILSTREAM
*stream
,ENVELOPE
**env
,
4577 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
4579 ENVELOPE
*oenv
= *env
;
4580 char c
= **txtptr
; /* grab first character */
4581 /* ignore leading spaces */
4582 while (c
== ' ') c
= *++*txtptr
;
4583 if (c
) ++*txtptr
; /* skip past first character */
4584 switch (c
) { /* dispatch on first character */
4585 case '(': /* if envelope S-expression */
4586 *env
= mail_newenvelope (); /* parse the new envelope */
4587 (*env
)->date
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4588 (*env
)->subject
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4589 (*env
)->from
= imap_parse_adrlist (stream
,txtptr
,reply
);
4590 (*env
)->sender
= imap_parse_adrlist (stream
,txtptr
,reply
);
4591 (*env
)->reply_to
= imap_parse_adrlist (stream
,txtptr
,reply
);
4592 (*env
)->to
= imap_parse_adrlist (stream
,txtptr
,reply
);
4593 (*env
)->cc
= imap_parse_adrlist (stream
,txtptr
,reply
);
4594 (*env
)->bcc
= imap_parse_adrlist (stream
,txtptr
,reply
);
4595 (*env
)->in_reply_to
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
4597 (*env
)->message_id
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4598 if (oenv
) { /* need to merge old envelope? */
4599 (*env
)->newsgroups
= oenv
->newsgroups
;
4600 oenv
->newsgroups
= NIL
;
4601 (*env
)->ngpathexists
= oenv
->ngpathexists
;
4602 (*env
)->followup_to
= oenv
->followup_to
;
4603 oenv
->followup_to
= NIL
;
4604 (*env
)->references
= oenv
->references
;
4605 oenv
->references
= NIL
;
4606 mail_free_envelope(&oenv
);/* free old envelope */
4608 /* have IMAP envelope components only */
4609 else (*env
)->imapenvonly
= T
;
4610 if (**txtptr
!= ')') {
4611 sprintf (LOCAL
->tmp
,"Junk at end of envelope: %.80s",(char *) *txtptr
);
4612 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4613 stream
->unhealthy
= T
;
4615 else ++*txtptr
; /* skip past delimiter */
4617 case 'N': /* if NIL */
4619 ++*txtptr
; /* bump past "I" */
4620 ++*txtptr
; /* bump past "L" */
4623 sprintf (LOCAL
->tmp
,"Not an envelope: %.80s",(char *) *txtptr
);
4624 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4625 stream
->unhealthy
= T
;
4630 /* IMAP parse address list
4631 * Accepts: MAIL stream
4632 * current text pointer
4634 * Returns: address list, NIL on failure
4636 * Updates text pointer
4639 ADDRESS
*imap_parse_adrlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
4640 IMAPPARSEDREPLY
*reply
)
4643 char c
= **txtptr
; /* sniff at first character */
4644 /* ignore leading spaces */
4645 while (c
== ' ') c
= *++*txtptr
;
4646 if (c
) ++*txtptr
; /* skip past open paren */
4648 case '(': /* if envelope S-expression */
4649 adr
= imap_parse_address (stream
,txtptr
,reply
);
4650 if (**txtptr
!= ')') {
4651 sprintf (LOCAL
->tmp
,"Junk at end of address list: %.80s",
4653 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4654 stream
->unhealthy
= T
;
4656 else ++*txtptr
; /* skip past delimiter */
4658 case 'N': /* if NIL */
4660 ++*txtptr
; /* bump past "I" */
4661 ++*txtptr
; /* bump past "L" */
4664 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
4665 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4666 stream
->unhealthy
= T
;
4672 /* IMAP parse address
4673 * Accepts: MAIL stream
4674 * current text pointer
4676 * Returns: address, NIL on failure
4678 * Updates text pointer
4681 ADDRESS
*imap_parse_address (MAILSTREAM
*stream
,unsigned char **txtptr
,
4682 IMAPPARSEDREPLY
*reply
)
4687 ADDRESS
*prev
= NIL
;
4688 char c
= **txtptr
; /* sniff at first address character */
4690 case '(': /* if envelope S-expression */
4691 while (c
== '(') { /* recursion dies on small stack machines */
4692 ++*txtptr
; /* skip past open paren */
4693 if (adr
) prev
= adr
; /* note previous if any */
4694 adr
= mail_newaddr (); /* instantiate address and parse its fields */
4695 adr
->personal
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4696 adr
->adl
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4697 adr
->mailbox
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4698 adr
->host
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4699 if (**txtptr
!= ')') { /* handle trailing paren */
4700 sprintf (LOCAL
->tmp
,"Junk at end of address: %.80s",(char *) *txtptr
);
4701 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4702 stream
->unhealthy
= T
;
4704 else ++*txtptr
; /* skip past close paren */
4705 c
= **txtptr
; /* set up for while test */
4706 /* ignore leading spaces in front of next */
4707 while (c
== ' ') c
= *++*txtptr
;
4709 if (!adr
->mailbox
) { /* end of group? */
4710 /* decrement group if all looks well */
4711 if (ingroup
&& !(adr
->personal
|| adr
->adl
|| adr
->host
)) --ingroup
;
4713 if (ingroup
) { /* in a group? */
4714 sprintf (LOCAL
->tmp
,/* yes, must be bad syntax */
4715 "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
4716 adr
->personal
? adr
->personal
: "",
4717 adr
->adl
? adr
->adl
: "",
4718 adr
->host
? adr
->host
: "");
4719 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4721 else mm_notify (stream
,"End of group encountered when not in group",
4723 stream
->unhealthy
= T
;
4724 mail_free_address (&adr
);
4729 else if (!adr
->host
) { /* start of group? */
4730 if (adr
->personal
|| adr
->adl
) {
4731 sprintf (LOCAL
->tmp
,"Junk in start of group: pn=%.80s al=%.80s",
4732 adr
->personal
? adr
->personal
: "",
4733 adr
->adl
? adr
->adl
: "");
4734 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4735 stream
->unhealthy
= T
;
4736 mail_free_address (&adr
);
4740 else ++ingroup
; /* in a group now */
4742 if (adr
) { /* good address */
4743 if (!ret
) ret
= adr
; /* if first time note first adr */
4744 /* if previous link new block to it */
4745 if (prev
) prev
->next
= adr
;
4746 /* flush bogus personal name */
4747 if (LOCAL
->loser
&& adr
->personal
&& strchr (adr
->personal
,'@'))
4748 fs_give ((void **) &adr
->personal
);
4752 case 'N': /* if NIL */
4754 *txtptr
+= 3; /* bump past NIL */
4757 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
4758 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4759 stream
->unhealthy
= T
;
4766 * Accepts: current message cache
4767 * current text pointer
4769 * Updates text pointer
4772 void imap_parse_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
,
4773 unsigned char **txtptr
)
4777 struct { /* old flags */
4778 unsigned int valid
: 1;
4779 unsigned int seen
: 1;
4780 unsigned int deleted
: 1;
4781 unsigned int flagged
: 1;
4782 unsigned int answered
: 1;
4783 unsigned int draft
: 1;
4784 unsigned long user_flags
;
4786 old
.valid
= elt
->valid
; old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
;
4787 old
.flagged
= elt
->flagged
; old
.answered
= elt
->answered
;
4788 old
.draft
= elt
->draft
; old
.user_flags
= elt
->user_flags
;
4789 elt
->valid
= T
; /* mark have valid flags now */
4790 elt
->user_flags
= NIL
; /* zap old flag values */
4791 elt
->seen
= elt
->deleted
= elt
->flagged
= elt
->answered
= elt
->draft
=
4793 do { /* parse list of flags */
4794 /* point at a flag */
4795 while (*(flag
= ++*txtptr
) == ' ');
4796 /* scan for end of flag */
4797 while (**txtptr
&& (**txtptr
!= ' ') && (**txtptr
!= ')')) ++*txtptr
;
4798 c
= **txtptr
; /* save delimiter */
4799 **txtptr
= '\0'; /* tie off flag */
4800 if (!*flag
) break; /* null flag */
4801 /* if starts with \ must be sys flag */
4802 else if (*flag
== '\\') {
4803 if (!compare_cstring (flag
,"\\Seen")) elt
->seen
= T
;
4804 else if (!compare_cstring (flag
,"\\Deleted")) elt
->deleted
= T
;
4805 else if (!compare_cstring (flag
,"\\Flagged")) elt
->flagged
= T
;
4806 else if (!compare_cstring (flag
,"\\Answered")) elt
->answered
= T
;
4807 else if (!compare_cstring (flag
,"\\Recent")) elt
->recent
= T
;
4808 else if (!compare_cstring (flag
,"\\Draft")) elt
->draft
= T
;
4810 /* otherwise user flag */
4811 else elt
->user_flags
|= imap_parse_user_flag (stream
,flag
);
4812 } while (c
&& (c
!= ')'));
4813 if (c
) ++*txtptr
; /* bump past delimiter */
4815 mm_notify (stream
,"Unterminated flags list",WARN
);
4816 stream
->unhealthy
= T
;
4818 if (!old
.valid
|| (old
.seen
!= elt
->seen
) ||
4819 (old
.deleted
!= elt
->deleted
) || (old
.flagged
!= elt
->flagged
) ||
4820 (old
.answered
!= elt
->answered
) || (old
.draft
!= elt
->draft
) ||
4821 (old
.user_flags
!= elt
->user_flags
)) mm_flags (stream
,elt
->msgno
);
4825 /* IMAP parse user flag
4826 * Accepts: MAIL stream
4828 * Returns: flag bit position
4831 unsigned long imap_parse_user_flag (MAILSTREAM
*stream
,char *flag
)
4834 /* sniff through all user flags */
4835 for (i
= 0; i
< NUSERFLAGS
; ++i
) if (stream
->user_flags
[i
])
4836 if (!compare_cstring (flag
,stream
->user_flags
[i
])) return (1 << i
);
4837 return (unsigned long) 0; /* not found */
4840 /* IMAP parse atom-string
4841 * Accepts: MAIL stream
4842 * current text pointer
4844 * returned string length
4847 * Updates text pointer
4850 unsigned char *imap_parse_astring (MAILSTREAM
*stream
,unsigned char **txtptr
,
4851 IMAPPARSEDREPLY
*reply
,unsigned long *len
)
4854 unsigned char c
,*s
,*ret
;
4855 /* ignore leading spaces */
4856 for (c
= **txtptr
; c
== ' '; c
= *++*txtptr
);
4858 case '"': /* quoted string? */
4859 case '{': /* literal? */
4860 ret
= imap_parse_string (stream
,txtptr
,reply
,NIL
,len
,NIL
);
4862 default: /* must be atom */
4863 for (c
= *(s
= *txtptr
); /* find end of atom */
4864 c
&& (c
> ' ') && (c
!= '(') && (c
!= ')') && (c
!= '{') &&
4865 (c
!= '%') && (c
!= '*') && (c
!= '"') && (c
!= '\\') && (c
< 0x80);
4867 if ((i
= *txtptr
- s
) != 0L) { /* atom ends at atom_special */
4868 if (len
) *len
= i
; /* return length of atom */
4869 ret
= strncpy ((char *) fs_get (i
+ 1),s
,i
);
4870 ret
[i
] = '\0'; /* tie off string */
4872 else { /* no atom found */
4873 sprintf (LOCAL
->tmp
,"Not an atom: %.80s",(char *) *txtptr
);
4874 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4875 stream
->unhealthy
= T
;
4884 /* IMAP parse string
4885 * Accepts: MAIL stream
4886 * current text pointer
4889 * returned string length
4890 * filter newline flag
4893 * Updates text pointer
4896 unsigned char *imap_parse_string (MAILSTREAM
*stream
,unsigned char **txtptr
,
4897 IMAPPARSEDREPLY
*reply
,GETS_DATA
*md
,
4898 unsigned long *len
,long flags
)
4902 unsigned long i
,j
,k
;
4904 unsigned char c
= **txtptr
; /* sniff at first character */
4905 mailgets_t mg
= (mailgets_t
) mail_parameters (NIL
,GET_GETS
,NIL
);
4907 (readprogress_t
) mail_parameters (NIL
,GET_READPROGRESS
,NIL
);
4908 /* ignore leading spaces */
4909 while (c
== ' ') c
= *++*txtptr
;
4910 if (c
) st
= ++*txtptr
; /* remember start of string */
4912 case '"': /* if quoted string */
4913 i
= 0; /* initial byte count */
4914 /* search for end of string */
4915 for (c
= **txtptr
; c
!= '"'; ++i
,c
= *++*txtptr
) {
4916 /* backslash quotes next character */
4917 if (c
== '\\') c
= *++*txtptr
;
4918 /* CHAR8 not permitted in quoted string */
4919 if (!bogon
&& (bogon
= (c
& 0x80))) {
4920 sprintf (LOCAL
->tmp
,"Invalid CHAR in quoted string: %x",
4922 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4923 stream
->unhealthy
= T
;
4925 else if (!c
) { /* NUL not permitted either */
4926 mm_notify (stream
,"Unterminated quoted string",WARN
);
4927 stream
->unhealthy
= T
;
4928 if (len
) *len
= 0; /* punt, since may be at end of string */
4932 ++*txtptr
; /* bump past delimiter */
4933 string
= (char *) fs_get ((size_t) i
+ 1);
4934 for (j
= 0; j
< i
; j
++) { /* copy the string */
4935 if (*st
== '\\') ++st
; /* quoted character */
4938 string
[j
] = '\0'; /* tie off string */
4939 if (len
) *len
= i
; /* set return value too */
4940 if (md
&& mg
) { /* have special routine to slurp string? */
4942 if (md
->first
) { /* partial fetch? */
4943 md
->first
--; /* restore origin octet */
4944 md
->last
= i
; /* number of octets that we got */
4946 INIT (&bs
,mail_string
,string
,i
);
4947 (*mg
) (mail_read
,&bs
,i
,md
);
4951 case 'N': /* if NIL */
4953 ++*txtptr
; /* bump past "I" */
4954 ++*txtptr
; /* bump past "L" */
4957 case '{': /* if literal string */
4958 if (!isdigit (**txtptr
)) {
4959 sprintf (LOCAL
->tmp
,"Invalid server literal length %.80s",*txtptr
);
4960 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4961 stream
->unhealthy
= T
; /* read and discard */
4964 /* get size of string */
4965 else if ((i
= strtoul (*txtptr
,(char **) txtptr
,10)) > MAXSERVERLIT
) {
4966 sprintf (LOCAL
->tmp
,"Absurd server literal length %lu",i
);
4967 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4968 stream
->unhealthy
= T
; /* read and discard */
4969 for (j
= IMAPTMPLEN
- 1; i
; i
-= j
) {
4971 net_getbuffer (LOCAL
->netstream
,j
,LOCAL
->tmp
);
4974 if (len
) *len
= i
; /* set return value */
4975 if (md
&& mg
) { /* have special routine to slurp string? */
4976 if (md
->first
) { /* partial fetch? */
4977 md
->first
--; /* restore origin octet */
4978 md
->last
= i
; /* number of octets that we got */
4980 else md
->flags
|= MG_COPY
;/* otherwise flag need to copy */
4981 string
= (*mg
) (net_getbuffer
,LOCAL
->netstream
,i
,md
);
4983 else { /* must slurp into free storage */
4984 string
= (char *) fs_get ((size_t) i
+ 1);
4985 *string
= '\0'; /* init in case getbuffer fails */
4986 /* get the literal */
4987 if (rp
) for (k
= 0; (j
= min ((long) MAILTMPLEN
,(long) i
)) != 0L; i
-= j
) {
4988 net_getbuffer (LOCAL
->netstream
,j
,string
+ k
);
4991 else net_getbuffer (LOCAL
->netstream
,i
,string
);
4993 fs_give ((void **) &reply
->line
);
4994 if (flags
&& string
) /* need to filter newlines? */
4995 for (st
= string
; (st
= strpbrk (st
,"\015\012\011")) != NULL
; *st
++ = ' ');
4996 /* get new reply text line */
4997 if (!(reply
->line
= net_getline (LOCAL
->netstream
)))
4998 reply
->line
= cpystr ("");
4999 if (stream
->debug
) mm_dlog (reply
->line
);
5000 *txtptr
= reply
->line
; /* set text pointer to point at it */
5003 sprintf (LOCAL
->tmp
,"Not a string: %c%.80s",c
,(char *) *txtptr
);
5004 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5005 stream
->unhealthy
= T
;
5009 return (unsigned char *) string
;
5012 /* Register text in IMAP cache
5013 * Accepts: MAIL stream
5015 * IMAP segment specifier
5016 * header string list (if a HEADER section specifier)
5017 * sized text to register
5018 * Returns: non-zero if cache non-empty
5021 long imap_cache (MAILSTREAM
*stream
,unsigned long msgno
,char *seg
,
5022 STRINGLIST
*stl
,SIZEDTEXT
*text
)
5024 char *t
,tmp
[MAILTMPLEN
];
5029 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
5030 /* top-level header never does mailgets */
5031 if (!strcmp (seg
,"HEADER") || !strcmp (seg
,"0") ||
5032 !strcmp (seg
,"HEADER.FIELDS") || !strcmp (seg
,"HEADER.FIELDS.NOT")) {
5033 ret
= &elt
->private.msg
.header
.text
;
5034 if (text
) { /* don't do this if no text */
5035 if (ret
->data
) fs_give ((void **) &ret
->data
);
5036 mail_free_stringlist (&elt
->private.msg
.lines
);
5037 elt
->private.msg
.lines
= stl
;
5038 /* prevent cache reuse of .NOT */
5039 if ((seg
[0] == 'H') && (seg
[6] == '.') && (seg
[13] == '.'))
5040 for (stc
= stl
; stc
; stc
= stc
->next
) stc
->text
.size
= 0;
5041 if (stream
->scache
) { /* short caching puts it in the stream */
5042 if (stream
->msgno
!= msgno
) {
5043 /* flush old stuff */
5044 mail_free_envelope (&stream
->env
);
5045 mail_free_body (&stream
->body
);
5046 stream
->msgno
= msgno
;
5048 imap_parse_header (stream
,&stream
->env
,text
,stl
);
5050 /* regular caching */
5051 else imap_parse_header (stream
,&elt
->private.msg
.env
,text
,stl
);
5054 /* top level text */
5055 else if (!strcmp (seg
,"TEXT")) {
5056 ret
= &elt
->private.msg
.text
.text
;
5057 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5059 else if (!*seg
) { /* full message */
5060 ret
= &elt
->private.msg
.full
.text
;
5061 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5064 else { /* nested, find non-contents specifier */
5065 for (t
= seg
; *t
&& !((*t
== '.') && (isalpha(t
[1]) || !atol (t
+1))); t
++);
5066 if (*t
) *t
++ = '\0'; /* tie off section from data specifier */
5067 if (!(b
= mail_body (stream
,msgno
,seg
))) {
5068 sprintf (tmp
,"Unknown section number: %.80s",seg
);
5069 mm_notify (stream
,tmp
,WARN
);
5070 stream
->unhealthy
= T
;
5073 if (*t
) { /* if a non-numberic subpart */
5074 if ((i
= (b
->type
== TYPEMESSAGE
) && (!strcmp (b
->subtype
,"RFC822"))) &&
5075 (!strcmp (t
,"HEADER") || !strcmp (t
,"0") ||
5076 !strcmp (t
,"HEADER.FIELDS") || !strcmp (t
,"HEADER.FIELDS.NOT"))) {
5077 ret
= &b
->nested
.msg
->header
.text
;
5079 if (ret
->data
) fs_give ((void **) &ret
->data
);
5080 mail_free_stringlist (&b
->nested
.msg
->lines
);
5081 b
->nested
.msg
->lines
= stl
;
5082 /* prevent cache reuse of .NOT */
5083 if ((t
[0] == 'H') && (t
[6] == '.') && (t
[13] == '.'))
5084 for (stc
= stl
; stc
; stc
= stc
->next
) stc
->text
.size
= 0;
5085 imap_parse_header (stream
,&b
->nested
.msg
->env
,text
,stl
);
5088 else if (i
&& !strcmp (t
,"TEXT")) {
5089 ret
= &b
->nested
.msg
->text
.text
;
5090 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5092 /* otherwise it must be MIME */
5093 else if (!strcmp (t
,"MIME")) {
5094 ret
= &b
->mime
.text
;
5095 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5098 sprintf (tmp
,"Unknown section specifier: %.80s.%.80s",seg
,t
);
5099 mm_notify (stream
,tmp
,WARN
);
5100 stream
->unhealthy
= T
;
5104 else { /* ordinary contents */
5105 ret
= &b
->contents
.text
;
5106 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5109 if (text
) { /* update cache if requested */
5110 ret
->data
= text
->data
;
5111 ret
->size
= text
->size
;
5113 return ret
->data
? LONGT
: NIL
;
5116 /* IMAP parse body structure
5117 * Accepts: MAIL stream
5118 * body structure to write into
5119 * current text pointer
5122 * Updates text pointer
5125 void imap_parse_body_structure (MAILSTREAM
*stream
,BODY
*body
,
5126 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
5131 char c
= **txtptr
; /* grab first character */
5132 /* ignore leading spaces */
5133 while (c
== ' ') c
= *++*txtptr
;
5134 if (c
) ++*txtptr
; /* skip past first character */
5135 switch (c
) { /* dispatch on first character */
5136 case '(': /* body structure list */
5137 if (**txtptr
== '(') { /* multipart body? */
5138 body
->type
= TYPEMULTIPART
;/* yes, set its type */
5139 do { /* instantiate new body part */
5140 if (part
) part
= part
->next
= mail_newbody_part ();
5141 else body
->nested
.part
= part
= mail_newbody_part ();
5143 imap_parse_body_structure (stream
,&part
->body
,txtptr
,reply
);
5144 } while (**txtptr
== '(');/* for each body part */
5145 if ((body
->subtype
= imap_parse_string(stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) != NULL
)
5146 ucase (body
->subtype
);
5148 mm_notify (stream
,"Missing multipart subtype",WARN
);
5149 stream
->unhealthy
= T
;
5150 body
->subtype
= cpystr (rfc822_default_subtype (body
->type
));
5152 if (**txtptr
== ' ') /* multipart parameters */
5153 body
->parameter
= imap_parse_body_parameter (stream
,txtptr
,reply
);
5154 if (**txtptr
== ' ') { /* disposition */
5155 imap_parse_disposition (stream
,body
,txtptr
,reply
);
5156 if (LOCAL
->cap
.extlevel
< BODYEXTDSP
) LOCAL
->cap
.extlevel
= BODYEXTDSP
;
5158 if (**txtptr
== ' ') { /* language */
5159 body
->language
= imap_parse_language (stream
,txtptr
,reply
);
5160 if (LOCAL
->cap
.extlevel
< BODYEXTLANG
)
5161 LOCAL
->cap
.extlevel
= BODYEXTLANG
;
5163 if (**txtptr
== ' ') { /* location */
5164 body
->location
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5165 if (LOCAL
->cap
.extlevel
< BODYEXTLOC
) LOCAL
->cap
.extlevel
= BODYEXTLOC
;
5167 while (**txtptr
== ' ') imap_parse_extension (stream
,txtptr
,reply
);
5168 if (**txtptr
!= ')') { /* validate ending */
5169 sprintf (LOCAL
->tmp
,"Junk at end of multipart body: %.80s",
5171 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5172 stream
->unhealthy
= T
;
5174 else ++*txtptr
; /* skip past delimiter */
5177 else { /* not multipart, parse type name */
5178 if (**txtptr
== ')') { /* empty body? */
5179 ++*txtptr
; /* bump past it */
5180 break; /* and punt */
5182 body
->type
= TYPEOTHER
; /* assume unknown type */
5183 body
->encoding
= ENCOTHER
;/* and unknown encoding */
5185 if ((s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) != NULL
) {
5186 ucase (s
); /* application always gets uppercase form */
5187 for (i
= 0; /* look in existing table */
5188 (i
<= TYPEMAX
) && body_types
[i
] && strcmp (s
,body_types
[i
]); i
++);
5189 if (i
<= TYPEMAX
) { /* only if found a slot */
5190 body
->type
= i
; /* set body type */
5191 if (!body_types
[i
]) { /* assign empty slot */
5193 s
= NIL
; /* don't free this string */
5196 if (s
) fs_give ((void **) &s
);
5198 if ((body
->subtype
= imap_parse_string(stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) != NULL
)
5199 ucase (body
->subtype
); /* parse subtype */
5201 mm_notify (stream
,"Missing body subtype",WARN
);
5202 stream
->unhealthy
= T
;
5203 body
->subtype
= cpystr (rfc822_default_subtype (body
->type
));
5205 body
->parameter
= imap_parse_body_parameter (stream
,txtptr
,reply
);
5206 body
->id
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5207 body
->description
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5209 if ((s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) != NULL
) {
5210 ucase (s
); /* application always gets uppercase form */
5211 for (i
= 0; /* search for body encoding */
5212 (i
<= ENCMAX
) && body_encodings
[i
] && strcmp(s
,body_encodings
[i
]);
5214 if (i
> ENCMAX
) body
->encoding
= ENCOTHER
;
5215 else { /* only if found a slot */
5216 body
->encoding
= i
; /* set body encoding */
5217 /* assign empty slot */
5218 if (!body_encodings
[i
]) {
5219 body_encodings
[i
] = s
;
5220 s
= NIL
; /* don't free this string */
5223 if (s
) fs_give ((void **) &s
);
5225 \f /* parse size of contents in bytes */
5226 body
->size
.bytes
= strtoul (*txtptr
,(char **) txtptr
,10);
5227 switch (body
->type
) { /* possible extra stuff */
5228 case TYPEMESSAGE
: /* message envelope and body */
5229 /* non MESSAGE/RFC822 is basic type */
5230 if (strcmp (body
->subtype
,"RFC822")) break;
5231 { /* make certain server sends an envelope */
5232 ENVELOPE
*env
= NIL
;
5233 imap_parse_envelope (stream
,&env
,txtptr
,reply
);
5235 mm_notify (stream
,"Missing body message envelope",WARN
);
5236 stream
->unhealthy
= T
;
5237 fs_give ((void **) &body
->subtype
);
5238 body
->subtype
= cpystr ("RFC822_MISSING_ENVELOPE");
5241 (body
->nested
.msg
= mail_newmsg ())->env
= env
;
5243 body
->nested
.msg
->body
= mail_newbody ();
5244 imap_parse_body_structure (stream
,body
->nested
.msg
->body
,txtptr
,reply
);
5245 /* drop into text case */
5246 case TYPETEXT
: /* size in lines */
5247 body
->size
.lines
= strtoul (*txtptr
,(char **) txtptr
,10);
5249 default: /* otherwise nothing special */
5253 if (**txtptr
== ' ') { /* extension data - md5 */
5254 body
->md5
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5255 if (LOCAL
->cap
.extlevel
< BODYEXTMD5
) LOCAL
->cap
.extlevel
= BODYEXTMD5
;
5257 if (**txtptr
== ' ') { /* disposition */
5258 imap_parse_disposition (stream
,body
,txtptr
,reply
);
5259 if (LOCAL
->cap
.extlevel
< BODYEXTDSP
) LOCAL
->cap
.extlevel
= BODYEXTDSP
;
5261 if (**txtptr
== ' ') { /* language */
5262 body
->language
= imap_parse_language (stream
,txtptr
,reply
);
5263 if (LOCAL
->cap
.extlevel
< BODYEXTLANG
)
5264 LOCAL
->cap
.extlevel
= BODYEXTLANG
;
5266 if (**txtptr
== ' ') { /* location */
5267 body
->location
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5268 if (LOCAL
->cap
.extlevel
< BODYEXTLOC
) LOCAL
->cap
.extlevel
= BODYEXTLOC
;
5270 while (**txtptr
== ' ') imap_parse_extension (stream
,txtptr
,reply
);
5271 if (**txtptr
!= ')') { /* validate ending */
5272 sprintf (LOCAL
->tmp
,"Junk at end of body part: %.80s",
5274 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5275 stream
->unhealthy
= T
;
5277 else ++*txtptr
; /* skip past delimiter */
5280 case 'N': /* if NIL */
5282 ++*txtptr
; /* bump past "I" */
5283 ++*txtptr
; /* bump past "L" */
5285 default: /* otherwise quite bogus */
5286 sprintf (LOCAL
->tmp
,"Bogus body structure: %.80s",(char *) *txtptr
);
5287 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5288 stream
->unhealthy
= T
;
5293 /* IMAP parse body parameter
5294 * Accepts: MAIL stream
5295 * current text pointer
5297 * Returns: body parameter
5298 * Updates text pointer
5301 PARAMETER
*imap_parse_body_parameter (MAILSTREAM
*stream
,
5302 unsigned char **txtptr
,
5303 IMAPPARSEDREPLY
*reply
)
5305 PARAMETER
*ret
= NIL
;
5306 PARAMETER
*par
= NIL
;
5308 /* ignore leading spaces */
5309 while ((c
= *(*txtptr
)++) == ' ');
5310 if (c
== '(') do { /* parse parameter list */
5311 /* append new parameter to tail */
5312 if (ret
) par
= par
->next
= mail_newbody_parameter ();
5313 else ret
= par
= mail_newbody_parameter ();
5314 if(!(par
->attribute
=imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5316 mm_notify (stream
,"Missing parameter attribute",WARN
);
5317 stream
->unhealthy
= T
;
5318 par
->attribute
= cpystr ("UNKNOWN");
5320 if (!(par
->value
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
))){
5321 sprintf (LOCAL
->tmp
,"Missing value for parameter %.80s",par
->attribute
);
5322 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5323 stream
->unhealthy
= T
;
5324 par
->value
= cpystr ("UNKNOWN");
5326 switch (c
= **txtptr
) { /* see what comes after */
5327 case ' ': /* flush whitespace */
5328 while ((c
= *++*txtptr
) == ' ');
5330 case ')': /* end of attribute/value pairs */
5331 ++*txtptr
; /* skip past closing paren */
5334 mm_notify (stream
,"Unterminated parameter list", WARN
);
5335 stream
->unhealthy
= T
;
5338 sprintf (LOCAL
->tmp
,"Junk at end of parameter: %.80s",(char *) *txtptr
);
5339 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5340 stream
->unhealthy
= T
;
5343 } while (c
&& (c
!= ')'));
5344 /* empty parameter, must be NIL */
5345 else if (((c
== 'N') || (c
== 'n')) &&
5346 ((*(s
= *txtptr
) == 'I') || (*s
== 'i')) &&
5347 ((s
[1] == 'L') || (s
[1] == 'l'))) *txtptr
+= 2;
5349 sprintf (LOCAL
->tmp
,"Bogus body parameter: %c%.80s",c
,
5350 (char *) (*txtptr
) - 1);
5351 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5352 stream
->unhealthy
= T
;
5357 /* IMAP parse body disposition
5358 * Accepts: MAIL stream
5359 * body structure to write into
5360 * current text pointer
5364 void imap_parse_disposition (MAILSTREAM
*stream
,BODY
*body
,
5365 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
5367 switch (*++*txtptr
) {
5369 ++*txtptr
; /* skip open paren */
5370 body
->disposition
.type
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5372 body
->disposition
.parameter
=
5373 imap_parse_body_parameter (stream
,txtptr
,reply
);
5374 if (**txtptr
!= ')') { /* validate ending */
5375 sprintf (LOCAL
->tmp
,"Junk at end of disposition: %.80s",
5377 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5378 stream
->unhealthy
= T
;
5380 else ++*txtptr
; /* skip past delimiter */
5382 case 'N': /* if NIL */
5384 ++*txtptr
; /* bump past "N" */
5385 ++*txtptr
; /* bump past "I" */
5386 ++*txtptr
; /* bump past "L" */
5389 sprintf (LOCAL
->tmp
,"Unknown body disposition: %.80s",(char *) *txtptr
);
5390 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5391 stream
->unhealthy
= T
;
5392 /* try to skip to next space */
5393 while (**txtptr
&& (*++*txtptr
!= ' ') && (**txtptr
!= ')'));
5398 /* IMAP parse body language
5399 * Accepts: MAIL stream
5400 * current text pointer
5402 * Returns: string list or NIL if empty or error
5405 STRINGLIST
*imap_parse_language (MAILSTREAM
*stream
,unsigned char **txtptr
,
5406 IMAPPARSEDREPLY
*reply
)
5410 STRINGLIST
*ret
= NIL
;
5411 /* language is a list */
5412 if (*++*txtptr
== '(') ret
= imap_parse_stringlist (stream
,txtptr
,reply
);
5413 else if ((s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,&i
,LONGT
)) != NULL
) {
5414 (ret
= mail_newstringlist ())->text
.data
= (unsigned char *) s
;
5420 /* IMAP parse string list
5421 * Accepts: MAIL stream
5422 * current text pointer
5424 * Returns: string list or NIL if empty or error
5427 STRINGLIST
*imap_parse_stringlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
5428 IMAPPARSEDREPLY
*reply
)
5430 STRINGLIST
*stl
= NIL
;
5431 STRINGLIST
*stc
= NIL
;
5432 unsigned char *t
= *txtptr
;
5433 /* parse the list */
5434 if (*t
++ == '(') while (*t
!= ')') {
5435 if (stl
) stc
= stc
->next
= mail_newstringlist ();
5436 else stc
= stl
= mail_newstringlist ();
5438 if (!(stc
->text
.data
=
5439 imap_parse_astring (stream
,&t
,reply
,&stc
->text
.size
))) {
5440 sprintf (LOCAL
->tmp
,"Bogus string list member: %.80s",(char *) t
);
5441 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5442 stream
->unhealthy
= T
;
5443 mail_free_stringlist (&stl
);
5446 else if (*t
== ' ') ++t
; /* another token follows */
5448 if (stl
) *txtptr
= ++t
; /* update return string */
5452 /* IMAP parse unknown body extension data
5453 * Accepts: MAIL stream
5454 * current text pointer
5457 * Updates text pointer
5460 void imap_parse_extension (MAILSTREAM
*stream
,unsigned char **txtptr
,
5461 IMAPPARSEDREPLY
*reply
)
5464 switch (*++*txtptr
) { /* action depends upon first character */
5466 while (**txtptr
&& (**txtptr
!= ')'))
5467 imap_parse_extension (stream
,txtptr
,reply
);
5468 if (**txtptr
) ++*txtptr
; /* bump past closing parenthesis */
5470 case '"': /* if quoted string */
5471 while ((*++*txtptr
!= '"') && **txtptr
) if (**txtptr
== '\\') ++*txtptr
;
5472 if (**txtptr
) ++*txtptr
; /* bump past closing quote */
5474 case 'N': /* if NIL */
5476 ++*txtptr
; /* bump past "N" */
5477 ++*txtptr
; /* bump past "I" */
5478 ++*txtptr
; /* bump past "L" */
5480 case '{': /* get size of literal */
5481 ++*txtptr
; /* bump past open squiggle */
5482 if ((i
= strtoul (*txtptr
,(char **) txtptr
,10)) != 0L) do
5483 net_getbuffer (LOCAL
->netstream
,j
= min (i
,(long) IMAPTMPLEN
- 1),
5486 /* get new reply text line */
5487 if (!(reply
->line
= net_getline (LOCAL
->netstream
)))
5488 reply
->line
= cpystr ("");
5489 if (stream
->debug
) mm_dlog (reply
->line
);
5490 *txtptr
= reply
->line
; /* set text pointer to point at it */
5492 case '0': case '1': case '2': case '3': case '4':
5493 case '5': case '6': case '7': case '8': case '9':
5494 strtoul (*txtptr
,(char **) txtptr
,10);
5497 sprintf (LOCAL
->tmp
,"Unknown extension token: %.80s",(char *) *txtptr
);
5498 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5499 stream
->unhealthy
= T
;
5500 /* try to skip to next space */
5501 while (**txtptr
&& (*++*txtptr
!= ' ') && (**txtptr
!= ')'));
5506 /* IMAP parse capabilities
5507 * Accepts: MAIL stream
5511 void imap_parse_capabilities (MAILSTREAM
*stream
,char *t
)
5516 if (!LOCAL
->gotcapability
) { /* need to save previous capabilities? */
5517 /* no, flush threaders */
5518 if ((thr
= LOCAL
->cap
.threader
) != NULL
) while ((th
= thr
) != NULL
) {
5519 fs_give ((void **) &th
->name
);
5521 fs_give ((void **) &th
);
5523 /* zap capabilities */
5524 memset (&LOCAL
->cap
,0,sizeof (LOCAL
->cap
));
5525 LOCAL
->gotcapability
= T
; /* flag that capabilities arrived */
5527 for (t
= strtok_r (t
," ",&r
); t
; t
= strtok_r (NIL
," ",&r
)) {
5528 if (!compare_cstring (t
,"IMAP4"))
5529 LOCAL
->cap
.imap4
= LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5530 else if (!compare_cstring (t
,"IMAP4rev1"))
5531 LOCAL
->cap
.imap4rev1
= LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5532 else if (!compare_cstring (t
,"IMAP2")) LOCAL
->cap
.rfc1176
= T
;
5533 else if (!compare_cstring (t
,"IMAP2bis"))
5534 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5535 else if (!compare_cstring (t
,"ACL")) LOCAL
->cap
.acl
= T
;
5536 else if (!compare_cstring (t
,"QUOTA")) LOCAL
->cap
.quota
= T
;
5537 else if (!compare_cstring (t
,"LITERAL+")) LOCAL
->cap
.litplus
= T
;
5538 else if (!compare_cstring (t
,"IDLE")) LOCAL
->cap
.idle
= T
;
5539 else if (!compare_cstring (t
,"MAILBOX-REFERRALS")) LOCAL
->cap
.mbx_ref
= T
;
5540 else if (!compare_cstring (t
,"LOGIN-REFERRALS")) LOCAL
->cap
.log_ref
= T
;
5541 else if (!compare_cstring (t
,"NAMESPACE")) LOCAL
->cap
.namespace = T
;
5542 else if (!compare_cstring (t
,"UIDPLUS")) LOCAL
->cap
.uidplus
= T
;
5543 else if (!compare_cstring (t
,"STARTTLS")) LOCAL
->cap
.starttls
= T
;
5544 else if (!compare_cstring (t
,"LOGINDISABLED"))LOCAL
->cap
.logindisabled
= T
;
5545 else if (!compare_cstring (t
,"ID")) LOCAL
->cap
.id
= T
;
5546 else if (!compare_cstring (t
,"CHILDREN")) LOCAL
->cap
.children
= T
;
5547 else if (!compare_cstring (t
,"MULTIAPPEND")) LOCAL
->cap
.multiappend
= T
;
5548 else if (!compare_cstring (t
,"BINARY")) LOCAL
->cap
.binary
= T
;
5549 else if (!compare_cstring (t
,"UNSELECT")) LOCAL
->cap
.unselect
= T
;
5550 else if (!compare_cstring (t
,"SASL-IR")) LOCAL
->cap
.sasl_ir
= T
;
5551 else if (!compare_cstring (t
,"SCAN")) LOCAL
->cap
.scan
= T
;
5552 else if (!compare_cstring (t
,"URLAUTH")) LOCAL
->cap
.urlauth
= T
;
5553 else if (!compare_cstring (t
,"CATENATE")) LOCAL
->cap
.catenate
= T
;
5554 else if (!compare_cstring (t
,"CONDSTORE")) LOCAL
->cap
.condstore
= T
;
5555 else if (!compare_cstring (t
,"ESEARCH")) LOCAL
->cap
.esearch
= T
;
5556 else if (((t
[0] == 'S') || (t
[0] == 's')) &&
5557 ((t
[1] == 'O') || (t
[1] == 'o')) &&
5558 ((t
[2] == 'R') || (t
[2] == 'r')) &&
5559 ((t
[3] == 'T') || (t
[3] == 't'))) LOCAL
->cap
.sort
= T
;
5560 /* capability with value? */
5561 else if ((s
= strchr (t
,'=')) != NULL
) {
5562 *s
++ = '\0'; /* separate token from value */
5563 if (!compare_cstring (t
,"THREAD") && !LOCAL
->loser
) {
5564 THREADER
*thread
= (THREADER
*) fs_get (sizeof (THREADER
));
5565 thread
->name
= cpystr (s
);
5566 thread
->dispatch
= NIL
;
5567 thread
->next
= LOCAL
->cap
.threader
;
5568 LOCAL
->cap
.threader
= thread
;
5570 else if (!compare_cstring (t
,"AUTH")) {
5571 if ((i
= mail_lookup_auth_name (s
,LOCAL
->authflags
)) &&
5572 (--i
< MAXAUTHENTICATORS
)) LOCAL
->cap
.auth
|= (1 << i
);
5573 else if (!compare_cstring (s
,"ANONYMOUS")) LOCAL
->cap
.authanon
= T
;
5576 /* ignore other capabilities */
5578 /* disable LOGIN if PLAIN also advertised */
5579 if ((i
= mail_lookup_auth_name ("PLAIN",NIL
)) && (--i
< MAXAUTHENTICATORS
) &&
5580 (LOCAL
->cap
.auth
& (1 << i
)) &&
5581 (i
= mail_lookup_auth_name ("LOGIN",NIL
)) && (--i
< MAXAUTHENTICATORS
))
5582 LOCAL
->cap
.auth
&= ~(1 << i
);
5586 * Accepts: MAIL stream
5589 * Returns: parsed reply from fetch
5592 IMAPPARSEDREPLY
*imap_fetch (MAILSTREAM
*stream
,char *sequence
,long flags
)
5595 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ?
5596 "UID FETCH" : "FETCH";
5597 IMAPARG
*args
[9],aseq
,aarg
,aenv
,ahhr
,axtr
,ahtr
,abdy
,atrl
;
5598 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
5600 args
[0] = &aseq
; aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
5601 args
[1] = &aarg
; aarg
.type
= ATOM
;
5602 aenv
.type
= ATOM
; aenv
.text
= (void *) "ENVELOPE";
5603 ahhr
.type
= ATOM
; ahhr
.text
= (void *) hdrheader
[LOCAL
->cap
.extlevel
];
5604 axtr
.type
= ATOM
; axtr
.text
= (void *) imap_extrahdrs
;
5605 ahtr
.type
= ATOM
; ahtr
.text
= (void *) hdrtrailer
;
5606 abdy
.type
= ATOM
; abdy
.text
= (void *) "BODYSTRUCTURE";
5607 atrl
.type
= ATOM
; atrl
.text
= (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
5608 if (LEVELIMAP4 (stream
)) { /* include UID if IMAP4 or IMAP4rev1 */
5609 aarg
.text
= (void *) "(UID";
5610 if (flags
& FT_NEEDENV
) { /* if need envelopes */
5611 args
[i
++] = &aenv
; /* include envelope */
5612 /* extra header poop if IMAP4rev1 */
5613 if (!(flags
& FT_NOHDRS
) && LEVELIMAP4rev1 (stream
)) {
5614 args
[i
++] = &ahhr
; /* header header */
5615 if (axtr
.text
) args
[i
++] = &axtr
;
5616 args
[i
++] = &ahtr
; /* header trailer */
5618 /* fetch body if requested */
5619 if (flags
& FT_NEEDBODY
) args
[i
++] = &abdy
;
5621 args
[i
++] = &atrl
; /* fetch trailer */
5624 else aarg
.text
= (void *) (flags
& FT_NEEDENV
) ?
5625 ((flags
& FT_NEEDBODY
) ?
5626 "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
5627 "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
5628 args
[i
] = NIL
; /* tie off command */
5629 return imap_send (stream
,cmd
,args
);
5632 /* Reform sequence for losing server that doesn't handle ranges right
5633 * Accepts: MAIL stream
5639 char *imap_reform_sequence (MAILSTREAM
*stream
,char *sequence
,long flags
)
5641 unsigned long i
,j
,star
;
5643 /* can't win if empty */
5644 if (!stream
->nmsgs
) return sequence
;
5645 /* get highest possible range value */
5646 star
= flags
? mail_uid (stream
,stream
->nmsgs
) : stream
->nmsgs
;
5647 /* flush old reformed sequence */
5648 if (LOCAL
->reform
) fs_give ((void **) &LOCAL
->reform
);
5649 rs
= LOCAL
->reform
= (char *) fs_get (1+ strlen (sequence
));
5650 for (s
= sequence
; (t
= strpbrk (s
,",:")) != NULL
; ) switch (*t
++) {
5651 case ',': /* single message */
5652 strncpy (rs
,s
,i
= t
- s
); /* copy string up to that point */
5653 rs
+= i
; /* advance destination pointer */
5654 s
+= i
; /* and source */
5656 case ':': /* message range */
5657 i
= (*s
== '*') ? star
: strtoul (s
,NIL
,10);
5658 if (*t
== '*') { /* range ends with star */
5662 else { /* numeric range end */
5663 j
= strtoul (t
,(char **) &tl
,10);
5664 if (!tl
) tl
= t
+ strlen (t
);
5666 if (i
<= j
) { /* if first less than second */
5667 if (*tl
) tl
++; /* skip past end of range if present */
5668 strncpy (rs
,s
,i
= tl
- s
);/* copy string up to that point */
5669 rs
+= i
; /* advance destination and source pointers */
5672 else { /* here's the workaround for losing servers */
5673 strncpy (rs
,t
,i
= tl
- t
);/* swap the order */
5674 rs
[i
] = ':'; /* delimit */
5675 strncpy (rs
+i
+1,s
,j
= (t
-1) - s
);
5676 rs
+= i
+ 1 + j
; /* advance destination pointer */
5677 if (*tl
) *rs
++ = *tl
++; /* write trailing delimiter if present */
5678 s
= tl
; /* advance source pointer */
5681 if (*s
) strcpy (rs
,s
); /* write remainder of sequence */
5682 else *rs
= '\0'; /* tie off string */
5683 return LOCAL
->reform
;
5686 /* IMAP return host name
5687 * Accepts: MAIL stream
5688 * Returns: host name
5691 char *imap_host (MAILSTREAM
*stream
)
5693 if (stream
->dtb
!= &imapdriver
)
5694 fatal ("imap_host called on non-IMAP stream!");
5695 /* return host name on stream if open */
5696 return (LOCAL
&& LOCAL
->netstream
) ? net_host (LOCAL
->netstream
) :
5697 ".NO-IMAP-CONNECTION.";
5701 /* IMAP return IMAP capability structure
5702 * Accepts: MAIL stream
5703 * Returns: IMAP capability structure
5706 IMAPCAP
*imap_cap (MAILSTREAM
*stream
)
5708 if (stream
->dtb
!= &imapdriver
)
5709 fatal ("imap_cap called on non-IMAP stream!");
5710 return &LOCAL
->cap
; /* return capability structure */