1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
17 * Author: Mark Crispin
19 * University of Washington
21 * Internet: MRC@CAC.Washington.EDU
24 * Last Edited: 8 May 2008
26 * This original version of this file is
27 * Copyright 1988 Stanford University
28 * and was developed in the Symbolic Systems Resources Group of the Knowledge
29 * Systems Laboratory at Stanford University in 1987-88, and was funded by the
30 * Biomedical Research Technology Program of the National Institutes of Health
31 * under grant number RR-00785.
43 #define IMAPLOOKAHEAD 20 /* envelope lookahead */
44 #define IMAPUIDLOOKAHEAD 1000 /* UID lookahead */
45 #define IMAPTCPPORT (long) 143 /* assigned TCP contact port */
46 #define IMAPSSLPORT (long) 993 /* assigned SSL TCP contact port */
47 #define MAXCOMMAND 1000 /* RFC 2683 guideline for cmd line length */
48 #define IDLETIMEOUT (long) 30 /* defined in RFC 3501 */
49 #define MAXSERVERLIT 0x7ffffffe /* maximum server literal size
50 * must be smaller than 4294967295
54 /* Parsed reply message from imap_reply */
56 typedef struct imap_parsed_reply
{
57 unsigned char *line
; /* original reply string pointer */
58 unsigned char *tag
; /* command tag this reply is for */
59 unsigned char *key
; /* reply keyword */
60 unsigned char *text
; /* subsequent text */
64 #define IMAPTMPLEN 16*MAILTMPLEN
67 /* IMAP4 I/O stream local data */
69 typedef struct imap_local
{
70 NETSTREAM
*netstream
; /* TCP I/O stream */
71 IMAPPARSEDREPLY reply
; /* last parsed reply */
72 MAILSTATUS
*stat
; /* status to fill in */
73 IMAPCAP cap
; /* server capabilities */
74 char *appendmailbox
; /* mailbox being appended to */
75 unsigned int uidsearch
: 1; /* UID searching */
76 unsigned int byeseen
: 1; /* saw a BYE response */
77 /* got implicit capabilities */
78 unsigned int gotcapability
: 1;
79 unsigned int sensitive
: 1; /* sensitive data in progress */
80 unsigned int tlsflag
: 1; /* TLS session */
81 unsigned int tlssslv23
: 1; /* TLS using SSLv23 client method */
82 unsigned int notlsflag
: 1; /* TLS not used in session */
83 unsigned int sslflag
: 1; /* SSL session */
84 unsigned int tls1
: 1; /* using TLSv1 over SSL */
85 unsigned int tls1_1
: 1; /* using TLSv1_1 over SSL */
86 unsigned int tls1_2
: 1; /* using TLSv1_2 over SSL */
87 unsigned int dtls1
: 1; /* using DTLSv1 over SSL */
88 unsigned int novalidate
: 1; /* certificate not validated */
89 unsigned int filter
: 1; /* filter SEARCH/SORT/THREAD results */
90 unsigned int loser
: 1; /* server is a loser */
91 unsigned int saslcancel
: 1; /* SASL cancelled by protocol */
92 long authflags
; /* required flags for authenticators */
93 unsigned long sortsize
; /* sort return data size */
94 unsigned long *sortdata
; /* sort return data */
96 unsigned long uid
; /* last UID returned */
97 unsigned long msgno
; /* last msgno returned */
99 NAMESPACE
**namespace; /* namespace return data */
100 THREADNODE
*threaddata
; /* thread return data */
101 char *referral
; /* last referral */
102 char *prefix
; /* find prefix */
103 char *user
; /* logged-in user */
104 char *reform
; /* reformed sequence */
105 char tmp
[IMAPTMPLEN
]; /* temporary buffer */
106 SEARCHSET
*lookahead
; /* fetch lookahead */
110 /* Convenient access to local data */
112 #define LOCAL ((IMAPLOCAL *) stream->local)
114 /* Arguments to imap_send() */
116 typedef struct imap_argument
{
117 int type
; /* argument type */
118 void *text
; /* argument text */
122 /* imap_send() argument types */
130 #define SEARCHPROGRAM 6
131 #define SORTPROGRAM 7
136 #define LISTMAILBOX 12
137 #define MULTIAPPEND 13
139 #define MULTIAPPENDREDO 15
144 typedef struct append_data
{
152 /* Function prototypes */
154 DRIVER
*imap_valid (char *name
);
155 void *imap_parameters (long function
,void *value
);
156 void imap_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
157 void imap_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
158 void imap_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
159 void imap_list_work (MAILSTREAM
*stream
,char *cmd
,char *ref
,char *pat
,
161 long imap_subscribe (MAILSTREAM
*stream
,char *mailbox
);
162 long imap_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
163 long imap_create (MAILSTREAM
*stream
,char *mailbox
);
164 long imap_delete (MAILSTREAM
*stream
,char *mailbox
);
165 long imap_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
166 long imap_manage (MAILSTREAM
*stream
,char *mailbox
,char *command
,char *arg2
);
167 long imap_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
168 MAILSTREAM
*imap_open (MAILSTREAM
*stream
);
169 IMAPPARSEDREPLY
*imap_rimap (MAILSTREAM
*stream
,char *service
,NETMBX
*mb
,
170 char *usr
,char *tmp
);
171 long imap_anon (MAILSTREAM
*stream
,char *tmp
);
172 long imap_auth (MAILSTREAM
*stream
,NETMBX
*mb
,char *tmp
,char *usr
);
173 long imap_login (MAILSTREAM
*stream
,NETMBX
*mb
,char *pwd
,char *usr
);
174 void *imap_challenge (void *stream
,unsigned long *len
);
175 long imap_response (void *stream
,char *s
,unsigned long size
);
176 void imap_close (MAILSTREAM
*stream
,long options
);
177 void imap_fast (MAILSTREAM
*stream
,char *sequence
,long flags
);
178 void imap_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
179 long imap_overview (MAILSTREAM
*stream
,overview_t ofn
);
180 ENVELOPE
*imap_structure (MAILSTREAM
*stream
,unsigned long msgno
,BODY
**body
,
182 long imap_msgdata (MAILSTREAM
*stream
,unsigned long msgno
,char *section
,
183 unsigned long first
,unsigned long last
,STRINGLIST
*lines
,
185 unsigned long imap_uid (MAILSTREAM
*stream
,unsigned long msgno
);
186 unsigned long imap_msgno (MAILSTREAM
*stream
,unsigned long uid
);
187 void imap_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
188 long imap_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
);
189 unsigned long *imap_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
190 SORTPGM
*pgm
,long flags
);
191 THREADNODE
*imap_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
192 SEARCHPGM
*spg
,long flags
);
193 THREADNODE
*imap_thread_work (MAILSTREAM
*stream
,char *type
,char *charset
,
194 SEARCHPGM
*spg
,long flags
);
195 long imap_ping (MAILSTREAM
*stream
);
196 void imap_check (MAILSTREAM
*stream
);
197 long imap_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
198 long imap_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
199 long imap_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
200 long imap_append_referral (char *mailbox
,char *tmp
,append_t af
,void *data
,
201 char *flags
,char *date
,STRING
*message
,
202 APPENDDATA
*map
,long options
);
203 IMAPPARSEDREPLY
*imap_append_single (MAILSTREAM
*stream
,char *mailbox
,
204 char *flags
,char *date
,STRING
*message
);
206 void imap_gc (MAILSTREAM
*stream
,long gcflags
);
207 void imap_gc_body (BODY
*body
);
208 void imap_capability (MAILSTREAM
*stream
);
209 long imap_acl_work (MAILSTREAM
*stream
,char *command
,IMAPARG
*args
[]);
211 IMAPPARSEDREPLY
*imap_send (MAILSTREAM
*stream
,char *cmd
,IMAPARG
*args
[]);
212 IMAPPARSEDREPLY
*imap_sout (MAILSTREAM
*stream
,char *tag
,char *base
,char **s
);
213 long imap_soutr (MAILSTREAM
*stream
,char *string
);
214 IMAPPARSEDREPLY
*imap_send_astring (MAILSTREAM
*stream
,char *tag
,char **s
,
215 SIZEDTEXT
*as
,long wildok
,char *limit
);
216 IMAPPARSEDREPLY
*imap_send_literal (MAILSTREAM
*stream
,char *tag
,char **s
,
218 IMAPPARSEDREPLY
*imap_send_spgm (MAILSTREAM
*stream
,char *tag
,char *base
,
219 char **s
,SEARCHPGM
*pgm
,char *limit
);
220 char *imap_send_spgm_trim (char *base
,char *s
,char *text
);
221 IMAPPARSEDREPLY
*imap_send_sset (MAILSTREAM
*stream
,char *tag
,char *base
,
222 char **s
,SEARCHSET
*set
,char *prefix
,
224 IMAPPARSEDREPLY
*imap_send_slist (MAILSTREAM
*stream
,char *tag
,char *base
,
225 char **s
,char *name
,STRINGLIST
*list
,
227 void imap_send_sdate (char **s
,char *name
,unsigned short date
);
228 IMAPPARSEDREPLY
*imap_reply (MAILSTREAM
*stream
,char *tag
);
229 IMAPPARSEDREPLY
*imap_parse_reply (MAILSTREAM
*stream
,char *text
);
230 IMAPPARSEDREPLY
*imap_fake (MAILSTREAM
*stream
,char *tag
,char *text
);
231 long imap_OK (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
);
232 void imap_parse_unsolicited (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
);
233 void imap_parse_response (MAILSTREAM
*stream
,char *text
,long errflg
,long ntfy
);
234 NAMESPACE
*imap_parse_namespace (MAILSTREAM
*stream
,unsigned char **txtptr
,
235 IMAPPARSEDREPLY
*reply
);
236 THREADNODE
*imap_parse_thread (MAILSTREAM
*stream
,unsigned char **txtptr
);
237 void imap_parse_header (MAILSTREAM
*stream
,ENVELOPE
**env
,SIZEDTEXT
*hdr
,
239 void imap_parse_envelope (MAILSTREAM
*stream
,ENVELOPE
**env
,
240 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
241 ADDRESS
*imap_parse_adrlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
242 IMAPPARSEDREPLY
*reply
);
243 ADDRESS
*imap_parse_address (MAILSTREAM
*stream
,unsigned char **txtptr
,
244 IMAPPARSEDREPLY
*reply
);
245 void imap_parse_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
,
246 unsigned char **txtptr
);
247 unsigned long imap_parse_user_flag (MAILSTREAM
*stream
,char *flag
);
248 unsigned char *imap_parse_astring (MAILSTREAM
*stream
,unsigned char **txtptr
,
249 IMAPPARSEDREPLY
*reply
,unsigned long *len
);
250 unsigned char *imap_parse_string (MAILSTREAM
*stream
,unsigned char **txtptr
,
251 IMAPPARSEDREPLY
*reply
,GETS_DATA
*md
,
252 unsigned long *len
,long flags
);
253 void imap_parse_body (GETS_DATA
*md
,char *seg
,unsigned char **txtptr
,
254 IMAPPARSEDREPLY
*reply
);
255 void imap_parse_body_structure (MAILSTREAM
*stream
,BODY
*body
,
256 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
257 PARAMETER
*imap_parse_body_parameter (MAILSTREAM
*stream
,
258 unsigned char **txtptr
,
259 IMAPPARSEDREPLY
*reply
);
260 void imap_parse_disposition (MAILSTREAM
*stream
,BODY
*body
,
261 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
);
262 STRINGLIST
*imap_parse_language (MAILSTREAM
*stream
,unsigned char **txtptr
,
263 IMAPPARSEDREPLY
*reply
);
264 STRINGLIST
*imap_parse_stringlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
265 IMAPPARSEDREPLY
*reply
);
266 void imap_parse_extension (MAILSTREAM
*stream
,unsigned char **txtptr
,
267 IMAPPARSEDREPLY
*reply
);
268 void imap_parse_capabilities (MAILSTREAM
*stream
,char *t
);
269 IMAPPARSEDREPLY
*imap_fetch (MAILSTREAM
*stream
,char *sequence
,long flags
);
270 char *imap_reform_sequence (MAILSTREAM
*stream
,char *sequence
,long flags
);
272 /* Driver dispatch used by MAIL */
274 DRIVER imapdriver
= {
275 "imap", /* driver name */
277 DR_MAIL
|DR_NEWS
|DR_NAMESPACE
|DR_CRLF
|DR_RECYCLE
|DR_HALFOPEN
,
278 (DRIVER
*) NIL
, /* next driver */
279 imap_valid
, /* mailbox is valid for us */
280 imap_parameters
, /* manipulate parameters */
281 imap_scan
, /* scan mailboxes */
282 imap_list
, /* find mailboxes */
283 imap_lsub
, /* find subscribed mailboxes */
284 imap_subscribe
, /* subscribe to mailbox */
285 imap_unsubscribe
, /* unsubscribe from mailbox */
286 imap_create
, /* create mailbox */
287 imap_delete
, /* delete mailbox */
288 imap_rename
, /* rename mailbox */
289 imap_status
, /* status of mailbox */
290 imap_open
, /* open mailbox */
291 imap_close
, /* close mailbox */
292 imap_fast
, /* fetch message "fast" attributes */
293 imap_flags
, /* fetch message flags */
294 imap_overview
, /* fetch overview */
295 imap_structure
, /* fetch message envelopes */
296 NIL
, /* fetch message header */
297 NIL
, /* fetch message body */
298 imap_msgdata
, /* fetch partial message */
299 imap_uid
, /* unique identifier */
300 imap_msgno
, /* message number */
301 imap_flag
, /* modify flags */
302 NIL
, /* per-message modify flags */
303 imap_search
, /* search for message based on criteria */
304 imap_sort
, /* sort messages */
305 imap_thread
, /* thread messages */
306 imap_ping
, /* ping mailbox to see if still alive */
307 imap_check
, /* check for new messages */
308 imap_expunge
, /* expunge deleted messages */
309 imap_copy
, /* copy messages to another mailbox */
310 imap_append
, /* append string message to mailbox */
311 imap_gc
/* garbage collect stream */
314 /* prototype stream */
315 MAILSTREAM imapproto
= {&imapdriver
};
317 /* driver parameters */
318 static unsigned long imap_maxlogintrials
= MAXLOGINTRIALS
;
319 static long imap_lookahead
= IMAPLOOKAHEAD
;
320 static long imap_uidlookahead
= IMAPUIDLOOKAHEAD
;
321 static long imap_fetchlookaheadlimit
= IMAPLOOKAHEAD
;
322 static long imap_defaultport
= 0;
323 static long imap_sslport
= 0;
324 static long imap_tryssl
= NIL
;
325 static long imap_prefetch
= IMAPLOOKAHEAD
;
326 static long imap_closeonerror
= NIL
;
327 static imapenvelope_t imap_envelope
= NIL
;
328 static imapreferral_t imap_referral
= NIL
;
329 static char *imap_extrahdrs
= NIL
;
332 static char *hdrheader
[] = {
333 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
334 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
335 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
336 "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
337 "BODY.PEEK[HEADER.FIELDS (Newsgroups"
339 static char *hdrtrailer
="Followup-To References)]";
341 /* IMAP validate mailbox
342 * Accepts: mailbox name
343 * Returns: our driver if name is valid, NIL otherwise
346 DRIVER
*imap_valid (char *name
)
348 return mail_valid_net (name
,&imapdriver
,NIL
,NIL
);
352 /* IMAP manipulate driver parameters
353 * Accepts: function code
354 * function-dependent value
355 * Returns: function-dependent return value
358 void *imap_parameters (long function
,void *value
)
360 switch ((int) function
) {
362 if (((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->cap
.namespace &&
363 !((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->namespace)
364 imap_send (((MAILSTREAM
*) value
),"NAMESPACE",NIL
);
365 value
= (void *) &((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->namespace;
369 ((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->cap
.threader
;
371 case SET_FETCHLOOKAHEAD
: /* must use pointer from GET_FETCHLOOKAHEAD */
372 fatal ("SET_FETCHLOOKAHEAD not permitted");
373 case GET_FETCHLOOKAHEAD
:
374 value
= (void *) &((IMAPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->lookahead
;
376 case SET_MAXLOGINTRIALS
:
377 imap_maxlogintrials
= (long) value
;
379 case GET_MAXLOGINTRIALS
:
380 value
= (void *) imap_maxlogintrials
;
383 imap_lookahead
= (long) value
;
386 value
= (void *) imap_lookahead
;
388 case SET_UIDLOOKAHEAD
:
389 imap_uidlookahead
= (long) value
;
391 case GET_UIDLOOKAHEAD
:
392 value
= (void *) imap_uidlookahead
;
396 imap_defaultport
= (long) value
;
399 value
= (void *) imap_defaultport
;
401 case SET_SSLIMAPPORT
:
402 imap_sslport
= (long) value
;
404 case GET_SSLIMAPPORT
:
405 value
= (void *) imap_sslport
;
408 imap_prefetch
= (long) value
;
411 value
= (void *) imap_prefetch
;
413 case SET_CLOSEONERROR
:
414 imap_closeonerror
= (long) value
;
416 case GET_CLOSEONERROR
:
417 value
= (void *) imap_closeonerror
;
419 case SET_IMAPENVELOPE
:
420 imap_envelope
= (imapenvelope_t
) value
;
422 case GET_IMAPENVELOPE
:
423 value
= (void *) imap_envelope
;
425 case SET_IMAPREFERRAL
:
426 imap_referral
= (imapreferral_t
) value
;
428 case GET_IMAPREFERRAL
:
429 value
= (void *) imap_referral
;
431 case SET_IMAPEXTRAHEADERS
:
432 imap_extrahdrs
= (char *) value
;
434 case GET_IMAPEXTRAHEADERS
:
435 value
= (void *) imap_extrahdrs
;
438 imap_tryssl
= (long) value
;
441 value
= (void *) imap_tryssl
;
443 case SET_FETCHLOOKAHEADLIMIT
:
444 imap_fetchlookaheadlimit
= (long) value
;
446 case GET_FETCHLOOKAHEADLIMIT
:
447 value
= (void *) imap_fetchlookaheadlimit
;
450 case SET_IDLETIMEOUT
:
451 fatal ("SET_IDLETIMEOUT not permitted");
452 case GET_IDLETIMEOUT
:
453 value
= (void *) IDLETIMEOUT
;
456 value
= NIL
; /* error case */
462 /* IMAP scan mailboxes
463 * Accepts: mail stream
469 void imap_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
471 imap_list_work (stream
,"SCAN",ref
,pat
,contents
);
475 /* IMAP list mailboxes
476 * Accepts: mail stream
481 void imap_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
483 imap_list_work (stream
,"LIST",ref
,pat
,NIL
);
487 /* IMAP list subscribed mailboxes
488 * Accepts: mail stream
493 void imap_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
496 char *s
,mbx
[MAILTMPLEN
];
497 /* do it on the server */
498 imap_list_work (stream
,"LSUB",ref
,pat
,NIL
);
499 if (*pat
== '{') { /* if remote pattern, must be IMAP */
500 if (!imap_valid (pat
)) return;
501 ref
= NIL
; /* good IMAP pattern, punt reference */
503 /* if remote reference, must be valid IMAP */
504 if (ref
&& (*ref
== '{') && !imap_valid (ref
)) return;
505 /* kludgy application of reference */
506 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
507 else strcpy (mbx
,pat
);
509 if (s
= sm_read (&sdb
)) do if (imap_valid (s
) && pmatch (s
,mbx
))
510 mm_lsub (stream
,NIL
,s
,NIL
);
511 while (s
= sm_read (&sdb
)); /* until no more subscriptions */
514 /* IMAP find list of mailboxes
515 * Accepts: mail stream
522 void imap_list_work (MAILSTREAM
*stream
,char *cmd
,char *ref
,char *pat
,
525 MAILSTREAM
*st
= stream
;
527 char *s
,prefix
[MAILTMPLEN
],mbx
[MAILTMPLEN
];
528 IMAPARG
*args
[4],aref
,apat
,acont
;
529 if (ref
&& *ref
) { /* have a reference? */
530 if (!(imap_valid (ref
) && /* make sure valid IMAP name and open stream */
531 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
532 (stream
= mail_open (NIL
,ref
,OP_HALFOPEN
|OP_SILENT
))))) return;
533 /* calculate prefix length */
534 pl
= strchr (ref
,'}') + 1 - ref
;
535 strncpy (prefix
,ref
,pl
); /* build prefix */
536 prefix
[pl
] = '\0'; /* tie off prefix */
537 ref
+= pl
; /* update reference */
540 if (!(imap_valid (pat
) && /* make sure valid IMAP name and open stream */
541 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
542 (stream
= mail_open (NIL
,pat
,OP_HALFOPEN
|OP_SILENT
))))) return;
543 /* calculate prefix length */
544 pl
= strchr (pat
,'}') + 1 - pat
;
545 strncpy (prefix
,pat
,pl
); /* build prefix */
546 prefix
[pl
] = '\0'; /* tie off prefix */
547 pat
+= pl
; /* update reference */
549 LOCAL
->prefix
= prefix
; /* note prefix */
550 if (contents
) { /* want to do a scan? */
551 if (LEVELSCAN (stream
)) { /* make sure permitted */
552 args
[0] = &aref
; args
[1] = &apat
; args
[2] = &acont
; args
[3] = NIL
;
553 aref
.type
= ASTRING
; aref
.text
= (void *) (ref
? ref
: "");
554 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) pat
;
555 acont
.type
= ASTRING
; acont
.text
= (void *) contents
;
556 imap_send (stream
,cmd
,args
);
558 else mm_log ("Scan not valid on this IMAP server",ERROR
);
561 else if (LEVELIMAP4 (stream
)){/* easy if IMAP4 */
562 args
[0] = &aref
; args
[1] = &apat
; args
[2] = NIL
;
563 aref
.type
= ASTRING
; aref
.text
= (void *) (ref
? ref
: "");
564 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) pat
;
565 /* referrals armed? */
566 if (LOCAL
->cap
.mbx_ref
&& mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
)) {
567 /* yes, convert LIST -> RLIST */
568 if (!compare_cstring (cmd
,"LIST")) cmd
= "RLIST";
569 /* and convert LSUB -> RLSUB */
570 else if (!compare_cstring (cmd
,"LSUB")) cmd
= "RLSUB";
572 imap_send (stream
,cmd
,args
);
574 else if (LEVEL1176 (stream
)) {/* convert to IMAP2 format wildcard */
575 /* kludgy application of reference */
576 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
577 else strcpy (mbx
,pat
);
578 for (s
= mbx
; *s
; s
++) if (*s
== '%') *s
= '*';
579 args
[0] = &apat
; args
[1] = NIL
;
580 apat
.type
= LISTMAILBOX
; apat
.text
= (void *) mbx
;
581 if (!(strstr (cmd
,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
582 strcmp (imap_send (stream
,"FIND ALL.MAILBOXES",args
)->key
,"BAD")) &&
583 !strcmp (imap_send (stream
,"FIND MAILBOXES",args
)->key
,"BAD"))
584 LOCAL
->cap
.rfc1176
= NIL
; /* must be RFC-1064 */
586 LOCAL
->prefix
= NIL
; /* no more prefix */
587 /* close temporary stream if we made one */
588 if (stream
!= st
) mail_close (stream
);
591 /* IMAP subscribe to mailbox
592 * Accepts: mail stream
593 * mailbox to add to subscription list
594 * Returns: T on success, NIL on failure
597 long imap_subscribe (MAILSTREAM
*stream
,char *mailbox
)
599 MAILSTREAM
*st
= stream
;
600 long ret
= ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
601 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
))) ?
602 imap_manage (stream
,mailbox
,LEVELIMAP4 (stream
) ?
603 "Subscribe" : "Subscribe Mailbox",NIL
) : NIL
;
604 /* toss out temporary stream */
605 if (st
!= stream
) mail_close (stream
);
610 /* IMAP unsubscribe to mailbox
611 * Accepts: mail stream
612 * mailbox to delete from manage list
613 * Returns: T on success, NIL on failure
616 long imap_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
618 MAILSTREAM
*st
= stream
;
619 long ret
= ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
620 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
))) ?
621 imap_manage (stream
,mailbox
,LEVELIMAP4 (stream
) ?
622 "Unsubscribe" : "Unsubscribe Mailbox",NIL
) : NIL
;
623 /* toss out temporary stream */
624 if (st
!= stream
) mail_close (stream
);
628 /* IMAP create mailbox
629 * Accepts: mail stream
630 * mailbox name to create
631 * Returns: T on success, NIL on failure
634 long imap_create (MAILSTREAM
*stream
,char *mailbox
)
636 return imap_manage (stream
,mailbox
,"Create",NIL
);
640 /* IMAP delete mailbox
641 * Accepts: mail stream
642 * mailbox name to delete
643 * Returns: T on success, NIL on failure
646 long imap_delete (MAILSTREAM
*stream
,char *mailbox
)
648 return imap_manage (stream
,mailbox
,"Delete",NIL
);
652 /* IMAP rename mailbox
653 * Accepts: mail stream
656 * Returns: T on success, NIL on failure
659 long imap_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
661 return imap_manage (stream
,old
,"Rename",newname
);
664 /* IMAP manage a mailbox
665 * Accepts: mail stream
666 * mailbox to manipulate
668 * optional second argument
669 * Returns: T on success, NIL on failure
672 long imap_manage (MAILSTREAM
*stream
,char *mailbox
,char *command
,char *arg2
)
674 MAILSTREAM
*st
= stream
;
675 IMAPPARSEDREPLY
*reply
;
677 char mbx
[MAILTMPLEN
],mbx2
[MAILTMPLEN
];
678 IMAPARG
*args
[3],ambx
,amb2
;
680 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
681 ambx
.type
= amb2
.type
= ASTRING
; ambx
.text
= (void *) mbx
;
682 amb2
.text
= (void *) mbx2
;
683 args
[0] = &ambx
; args
[1] = args
[2] = NIL
;
684 /* require valid names and open stream */
685 if (mail_valid_net (mailbox
,&imapdriver
,NIL
,mbx
) &&
686 (arg2
? mail_valid_net (arg2
,&imapdriver
,NIL
,mbx2
) : &imapdriver
) &&
687 ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
688 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
)))) {
689 if (arg2
) args
[1] = &amb2
; /* second arg present? */
690 if (!(ret
= (imap_OK (stream
,reply
= imap_send (stream
,command
,args
)))) &&
691 ir
&& LOCAL
->referral
) {
693 switch (*command
) { /* which command was it? */
694 case 'S': code
= REFSUBSCRIBE
; break;
695 case 'U': code
= REFUNSUBSCRIBE
; break;
696 case 'C': code
= REFCREATE
; break;
697 case 'D': code
= REFDELETE
; break;
698 case 'R': code
= REFRENAME
; break;
700 fatal ("impossible referral command");
702 if ((code
>= 0) && (mailbox
= (*ir
) (stream
,LOCAL
->referral
,code
)))
703 ret
= imap_manage (NIL
,mailbox
,command
,(*command
== 'R') ?
704 (mailbox
+ strlen (mailbox
) + 1) : NIL
);
706 mm_log (reply
->text
,ret
? NIL
: ERROR
);
707 /* toss out temporary stream */
708 if (st
!= stream
) mail_close (stream
);
714 * Accepts: mail stream
717 * Returns: T on success, NIL on failure
720 long imap_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
722 IMAPARG
*args
[3],ambx
,aflg
;
723 char tmp
[MAILTMPLEN
];
727 MAILSTREAM
*tstream
= NIL
;
728 /* use given stream if (rev1 or halfopen) and
730 if (!((stream
&& (LEVELIMAP4rev1 (stream
) || stream
->halfopen
) &&
731 mail_usable_network_stream (stream
,mbx
)) ||
732 (stream
= tstream
= mail_open (NIL
,mbx
,OP_HALFOPEN
|OP_SILENT
))))
734 /* parse mailbox name */
735 mail_valid_net_parse (mbx
,&mb
);
736 args
[0] = &ambx
;args
[1] = NIL
;/* set up first argument as mailbox */
737 ambx
.type
= ASTRING
; ambx
.text
= (void *) mb
.mailbox
;
738 if (LEVELIMAP4rev1 (stream
)) {/* have STATUS command? */
740 aflg
.type
= FLAGS
; aflg
.text
= (void *) tmp
;
741 args
[1] = &aflg
; args
[2] = NIL
;
742 tmp
[0] = tmp
[1] = '\0'; /* build flag list */
743 if (flags
& SA_MESSAGES
) strcat (tmp
," MESSAGES");
744 if (flags
& SA_RECENT
) strcat (tmp
," RECENT");
745 if (flags
& SA_UNSEEN
) strcat (tmp
," UNSEEN");
746 if (flags
& SA_UIDNEXT
) strcat (tmp
," UIDNEXT");
747 if (flags
& SA_UIDVALIDITY
) strcat (tmp
," UIDVALIDITY");
750 /* send "STATUS mailbox flag" */
751 if (imap_OK (stream
,imap_send (stream
,"STATUS",args
))) ret
= T
;
752 else if ((ir
= (imapreferral_t
)
753 mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
)) &&
755 (mbx
= (*ir
) (stream
,LOCAL
->referral
,REFSTATUS
)))
756 ret
= imap_status (NIL
,mbx
,flags
| (stream
->debug
? SA_DEBUG
: NIL
));
760 else if (imap_OK (stream
,imap_send (stream
,"EXAMINE",args
))) {
762 status
.flags
= flags
& ~ (SA_UIDNEXT
| SA_UIDVALIDITY
);
763 status
.messages
= stream
->nmsgs
;
764 status
.recent
= stream
->recent
;
766 if (flags
& SA_UNSEEN
) { /* must search to get unseen messages */
767 /* clear search vector */
768 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mail_elt (stream
,i
)->searched
= NIL
;
769 if (imap_OK (stream
,imap_send (stream
,"SEARCH UNSEEN",NIL
)))
770 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
771 if (mail_elt (stream
,i
)->searched
) status
.unseen
++;
773 strcpy (strchr (strcpy (tmp
,stream
->mailbox
),'}') + 1,mb
.mailbox
);
774 /* pass status to main program */
775 mm_status (stream
,tmp
,&status
);
776 ret
= T
; /* note success */
778 if (tstream
) mail_close (tstream
);
779 return ret
; /* success */
783 * Accepts: stream to open
784 * Returns: stream to use on success, NIL on failure
787 MAILSTREAM
*imap_open (MAILSTREAM
*stream
)
790 char *s
,tmp
[MAILTMPLEN
],usr
[MAILTMPLEN
];
792 IMAPPARSEDREPLY
*reply
= NIL
;
794 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
795 /* return prototype for OP_PROTOTYPE call */
796 if (!stream
) return &imapproto
;
797 mail_valid_net_parse (stream
->mailbox
,&mb
);
798 usr
[0] = '\0'; /* initially no user name */
799 if (LOCAL
) { /* if stream opened earlier by us */
800 /* recycle if still alive */
801 if (LOCAL
->netstream
&& (!stream
->halfopen
|| LOCAL
->cap
.unselect
)) {
802 i
= stream
->silent
; /* temporarily mark silent */
803 stream
->silent
= T
; /* don't give mm_exists() events */
804 j
= imap_ping (stream
); /* learn if stream still alive */
805 stream
->silent
= i
; /* restore prior state */
806 if (j
) { /* was stream still alive? */
807 sprintf (tmp
,"Reusing connection to %s",net_host (LOCAL
->netstream
));
808 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",
810 if (!stream
->silent
) mm_log (tmp
,(long) NIL
);
811 /* unselect if now want halfopen */
812 if (stream
->halfopen
) imap_send (stream
,"UNSELECT",NIL
);
814 else imap_close (stream
,NIL
);
816 else imap_close (stream
,NIL
);
818 /* copy flags from name */
819 if (mb
.dbgflag
) stream
->debug
= T
;
820 if (mb
.readonlyflag
) stream
->rdonly
= T
;
821 if (mb
.anoflag
) stream
->anonymous
= T
;
822 if (mb
.secflag
) stream
->secure
= T
;
823 if (mb
.trysslflag
|| imap_tryssl
) stream
->tryssl
= T
;
825 if (!LOCAL
) { /* open new connection if no recycle */
826 NETDRIVER
*ssld
= (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
);
827 unsigned long defprt
= imap_defaultport
? imap_defaultport
: IMAPTCPPORT
;
828 unsigned long sslport
= imap_sslport
? imap_sslport
: IMAPSSLPORT
;
829 stream
->local
= /* instantiate localdata */
830 (void *) memset (fs_get (sizeof (IMAPLOCAL
)),0,sizeof (IMAPLOCAL
));
831 /* assume IMAP2bis server */
832 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
833 /* in case server is a loser */
834 if (mb
.loser
) LOCAL
->loser
= T
;
835 /* desirable authenticators */
836 LOCAL
->authflags
= (stream
->secure
? AU_SECURE
: NIL
) |
837 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
);
838 /* IMAP connection open logic is more complex than net_open() normally
839 * deals with, because of the simap and rimap hacks.
840 * If the session is anonymous, a specific port is given, or if /ssl or
841 * /tls is set, do net_open() since those conditions override everything
844 if (stream
->anonymous
|| mb
.port
|| mb
.sslflag
|| mb
.tlsflag
)
845 reply
= (LOCAL
->netstream
= net_open (&mb
,NIL
,defprt
,ssld
,"*imaps",
847 imap_reply (stream
,NIL
) : NIL
;
849 * No overriding conditions, so get the best connection that we can. In
850 * order, attempt to open via simap, tryssl, rimap, and finally TCP.
853 else if (reply
= imap_rimap (stream
,"*imap",&mb
,usr
,tmp
));
854 else if (ssld
&& /* try tryssl if enabled */
855 (stream
->tryssl
|| mail_parameters (NIL
,GET_TRYSSLFIRST
,NIL
)) &&
857 net_open_work (ssld
,mb
.host
,"*imaps",sslport
,mb
.port
,
858 (mb
.novalidate
? NET_NOVALIDATECERT
: 0) |
859 NET_SILENT
| NET_TRYSSL
))) {
860 if (net_sout (LOCAL
->netstream
,"",0)) {
862 reply
= imap_reply (stream
,NIL
);
864 else { /* flush fake SSL stream */
865 net_close (LOCAL
->netstream
);
866 LOCAL
->netstream
= NIL
;
869 /* try rimap first, then TCP */
870 else if (!(reply
= imap_rimap (stream
,"imap",&mb
,usr
,tmp
)) &&
871 (LOCAL
->netstream
= net_open (&mb
,NIL
,defprt
,NIL
,NIL
,NIL
)))
872 reply
= imap_reply (stream
,NIL
);
873 /* make sure greeting is good */
874 if (!reply
|| strcmp (reply
->tag
,"*") ||
875 (strcmp (reply
->key
,"OK") && strcmp (reply
->key
,"PREAUTH"))) {
876 if (reply
) mm_log (reply
->text
,ERROR
);
877 return NIL
; /* lost during greeting */
880 /* if connected and not preauthenticated */
881 if (LOCAL
->netstream
&& strcmp (reply
->key
,"PREAUTH")) {
882 sslstart_t stls
= (sslstart_t
) mail_parameters (NIL
,GET_SSLSTART
,NIL
);
883 /* get server capabilities */
884 if (!LOCAL
->gotcapability
) imap_capability (stream
);
885 if (LOCAL
->netstream
&& /* does server support STARTTLS? */
886 stls
&& LOCAL
->cap
.starttls
&& !mb
.sslflag
&& !mb
.notlsflag
&&
887 imap_OK (stream
,imap_send (stream
,"STARTTLS",NIL
))) {
888 mb
.tlsflag
= T
; /* TLS OK, get into TLS at this end */
889 LOCAL
->netstream
->dtb
= ssld
;
890 if (!(LOCAL
->netstream
->stream
=
891 (*stls
) (LOCAL
->netstream
->stream
,mb
.host
,
892 SSL_MTHD(mb
) | (mb
.novalidate
? NET_NOVALIDATECERT
: NIL
)))) {
893 /* drat, drop this connection */
894 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
895 LOCAL
->netstream
= NIL
;
897 /* get capabilities now that TLS in effect */
898 if (LOCAL
->netstream
) imap_capability (stream
);
900 else if (mb
.tlsflag
) { /* user specified /tls but can't do it */
901 mm_log ("Unable to negotiate TLS with this server",ERROR
);
904 if (LOCAL
->netstream
) { /* still in the land of the living? */
905 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
906 /* remote name for authentication */
907 strncpy (mb
.host
,(long) mail_parameters(NIL
,GET_SASLUSESPTRNAME
,NIL
)?
908 net_remotehost (LOCAL
->netstream
) :
909 net_host (LOCAL
->netstream
),NETMAXHOST
-1);
910 mb
.host
[NETMAXHOST
-1] = '\0';
912 /* need new capabilities after login */
913 LOCAL
->gotcapability
= NIL
;
914 if (!(stream
->anonymous
? imap_anon (stream
,tmp
) :
915 (LOCAL
->cap
.auth
? imap_auth (stream
,&mb
,tmp
,usr
) :
916 imap_login (stream
,&mb
,tmp
,usr
)))) {
917 /* failed, is there a referral? */
918 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
919 if (ir
&& LOCAL
->referral
&&
920 (s
= (*ir
) (stream
,LOCAL
->referral
,REFAUTHFAILED
))) {
921 imap_close (stream
,NIL
);
922 fs_give ((void **) &stream
->mailbox
);
923 /* set as new mailbox name to open */
925 return imap_open (stream
);
927 return NIL
; /* authentication failed */
929 else if (ir
&& LOCAL
->referral
&&
930 (s
= (*ir
) (stream
,LOCAL
->referral
,REFAUTH
))) {
931 imap_close (stream
,NIL
);
932 fs_give ((void **) &stream
->mailbox
);
933 stream
->mailbox
= s
; /* set as new mailbox name to open */
934 /* recurse to log in on real site */
935 return imap_open (stream
);
939 /* get server capabilities again */
940 if (LOCAL
->netstream
&& !LOCAL
->gotcapability
) imap_capability (stream
);
941 /* save state for future recycling */
942 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
943 if (mb
.tls1
) LOCAL
->tls1
= T
;
944 if (mb
.dtls1
) LOCAL
->dtls1
= T
;
945 if (mb
.tls1_1
) LOCAL
->tls1_1
= T
;
946 if (mb
.tls1_2
) LOCAL
->tls1_2
= T
;
947 if (mb
.tlssslv23
) LOCAL
->tlssslv23
= T
;
948 if (mb
.notlsflag
) LOCAL
->notlsflag
= T
;
949 if (mb
.sslflag
) LOCAL
->sslflag
= T
;
950 if (mb
.novalidate
) LOCAL
->novalidate
= T
;
951 if (mb
.loser
) LOCAL
->loser
= T
;
954 if (LOCAL
->netstream
) { /* still have a connection? */
955 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_answered
=
956 stream
->perm_draft
= LEVELIMAP4 (stream
) ? NIL
: T
;
957 stream
->perm_user_flags
= LEVELIMAP4 (stream
) ? NIL
: 0xffffffff;
958 stream
->sequence
++; /* bump sequence number */
959 sprintf (tmp
,"{%s",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
960 net_host (LOCAL
->netstream
) : mb
.host
);
961 if (!((i
= net_port (LOCAL
->netstream
)) & 0xffff0000))
962 sprintf (tmp
+ strlen (tmp
),":%lu",i
);
963 strcat (tmp
,"/imap");
964 if (LOCAL
->tlsflag
) strcat (tmp
,"/tls");
965 if (LOCAL
->tls1
) strcat (tmp
,"/tls1");
966 if (LOCAL
->tls1_1
) strcat (tmp
,"/tls1_1");
967 if (LOCAL
->tls1_2
) strcat (tmp
,"/tls1_2");
968 if (LOCAL
->dtls1
) strcat (tmp
,"/dtls");
969 if (LOCAL
->tlssslv23
) strcat (tmp
,"/tls-sslv23");
970 if (LOCAL
->notlsflag
) strcat (tmp
,"/notls");
971 if (LOCAL
->sslflag
) strcat (tmp
,"/ssl");
972 if (LOCAL
->novalidate
) strcat (tmp
,"/novalidate-cert");
973 if (LOCAL
->loser
) strcat (tmp
,"/loser");
974 if (stream
->secure
) strcat (tmp
,"/secure");
975 if (stream
->rdonly
) strcat (tmp
,"/readonly");
976 if (stream
->anonymous
) strcat (tmp
,"/anonymous");
977 else { /* record user name */
978 if (!LOCAL
->user
&& usr
[0]) LOCAL
->user
= cpystr (usr
);
979 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",
984 if (!stream
->halfopen
) { /* wants to open a mailbox? */
988 ambx
.text
= (void *) mb
.mailbox
;
989 args
[0] = &ambx
; args
[1] = NIL
;
991 if (imap_OK (stream
,reply
= imap_send (stream
,stream
->rdonly
?
992 "EXAMINE": "SELECT",args
))) {
993 strcat (tmp
,mb
.mailbox
);/* mailbox name */
994 if (!stream
->nmsgs
&& !stream
->silent
)
995 mm_log ("Mailbox is empty",(long) NIL
);
996 /* note if an INBOX or not */
997 stream
->inbox
= !compare_cstring (mb
.mailbox
,"INBOX");
999 else if (ir
&& LOCAL
->referral
&&
1000 (s
= (*ir
) (stream
,LOCAL
->referral
,REFSELECT
))) {
1001 imap_close (stream
,NIL
);
1002 fs_give ((void **) &stream
->mailbox
);
1003 stream
->mailbox
= s
; /* set as new mailbox name to open */
1004 return imap_open (stream
);
1007 mm_log (reply
->text
,ERROR
);
1008 if (imap_closeonerror
) return NIL
;
1009 stream
->halfopen
= T
; /* let him keep it half-open */
1012 if (stream
->halfopen
) { /* half-open connection? */
1013 strcat (tmp
,"<no_mailbox>");
1014 /* make sure dummy message counts */
1015 mail_exists (stream
,(long) 0);
1016 mail_recent (stream
,(long) 0);
1018 fs_give ((void **) &stream
->mailbox
);
1019 stream
->mailbox
= cpystr (tmp
);
1021 /* success if stream open */
1022 return LOCAL
->netstream
? stream
: NIL
;
1025 /* IMAP rimap connect
1026 * Accepts: MAIL stream
1027 * NETMBX specification
1031 * Returns: parsed reply if success, else NIL
1034 IMAPPARSEDREPLY
*imap_rimap (MAILSTREAM
*stream
,char *service
,NETMBX
*mb
,
1035 char *usr
,char *tmp
)
1040 IMAPPARSEDREPLY
*reply
= NIL
;
1041 /* try rimap open */
1042 if (!mb
->norsh
&& (tstream
= net_aopen (NIL
,mb
,service
,usr
))) {
1043 /* if success, see if reasonable banner */
1044 if (net_getbuffer (tstream
,(long) 1,c
) && (*c
== '*')) {
1045 i
= 0; /* copy to buffer */
1047 while (net_getbuffer (tstream
,(long) 1,c
) && (*c
!= '\015') &&
1048 (*c
!= '\012') && (i
< (MAILTMPLEN
-1)));
1049 tmp
[i
] = '\0'; /* tie off */
1050 /* snarfed a valid greeting? */
1051 if ((*c
== '\015') && net_getbuffer (tstream
,(long) 1,c
) &&
1053 !strcmp ((reply
= imap_parse_reply (stream
,cpystr (tmp
)))->tag
,"*")){
1054 /* parse line as IMAP */
1055 imap_parse_unsolicited (stream
,reply
);
1056 /* make sure greeting is good */
1057 if (!strcmp (reply
->key
,"OK") || !strcmp (reply
->key
,"PREAUTH")) {
1058 LOCAL
->netstream
= tstream
;
1059 return reply
; /* return success */
1063 net_close (tstream
); /* failed, punt the temporary netstream */
1068 /* IMAP log in as anonymous
1069 * Accepts: stream to authenticate
1071 * Returns: T on success, NIL on failure
1074 long imap_anon (MAILSTREAM
*stream
,char *tmp
)
1076 IMAPPARSEDREPLY
*reply
;
1077 char *s
= net_localhost (LOCAL
->netstream
);
1078 if (LOCAL
->cap
.authanon
) {
1081 char *broken
= "[CLOSED] IMAP connection broken (anonymous auth)";
1082 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
1084 sprintf (tmp
,"%s AUTHENTICATE ANONYMOUS",tag
);
1085 if (!imap_soutr (stream
,tmp
)) {
1086 mm_log (broken
,ERROR
);
1089 if (imap_challenge (stream
,&i
)) imap_response (stream
,s
,strlen (s
));
1091 if (!(reply
= &LOCAL
->reply
)->tag
) reply
= imap_fake (stream
,tag
,broken
);
1092 /* what we wanted? */
1093 if (compare_cstring (reply
->tag
,tag
)) {
1094 /* abort if don't have tagged response */
1095 while (compare_cstring ((reply
= imap_reply (stream
,tag
))->tag
,tag
))
1096 imap_soutr (stream
,"*");
1102 ausr
.type
= ASTRING
;
1103 ausr
.text
= (void *) s
;
1104 args
[0] = &ausr
; args
[1] = NIL
;
1105 /* send "LOGIN anonymous <host>" */
1106 reply
= imap_send (stream
,"LOGIN ANONYMOUS",args
);
1108 /* success if reply OK */
1109 if (imap_OK (stream
,reply
)) return T
;
1110 mm_log (reply
->text
,ERROR
);
1114 /* IMAP authenticate
1115 * Accepts: stream to authenticate
1116 * parsed network mailbox structure
1118 * place to return user name
1119 * Returns: T on success, NIL on failure
1122 long imap_auth (MAILSTREAM
*stream
,NETMBX
*mb
,char *tmp
,char *usr
)
1124 unsigned long trial
,ua
;
1129 IMAPPARSEDREPLY
*reply
;
1130 for (ua
= LOCAL
->cap
.auth
, LOCAL
->saslcancel
= NIL
; LOCAL
->netstream
&& ua
&&
1131 (at
= mail_lookup_auth (find_rightmost_bit (&ua
) + 1));) {
1132 if (lsterr
) { /* previous authenticator failed? */
1133 sprintf (tmp
,"Retrying using %s authentication after %.80s",
1136 fs_give ((void **) &lsterr
);
1138 trial
= 0; /* initial trial count */
1139 tmp
[0] = '\0'; /* no error */
1140 do { /* gensym a new tag */
1141 if (lsterr
) { /* previous attempt with this one failed? */
1142 sprintf (tmp
,"Retrying %s authentication after %.80s",at
->name
,lsterr
);
1144 fs_give ((void **) &lsterr
);
1146 LOCAL
->saslcancel
= NIL
;
1147 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
1149 sprintf (tmp
,"%s AUTHENTICATE %s",tag
,at
->name
);
1150 if (imap_soutr (stream
,tmp
)) {
1151 /* hide client authentication responses */
1152 if (!(at
->flags
& AU_SECURE
)) LOCAL
->sensitive
= T
;
1153 ok
= (*at
->client
) (imap_challenge
,imap_response
,"imap",mb
,stream
,
1155 LOCAL
->sensitive
= NIL
; /* unhide */
1156 /* make sure have a response */
1157 if (!(reply
= &LOCAL
->reply
)->tag
)
1158 reply
= imap_fake (stream
,tag
,
1159 "[CLOSED] IMAP connection broken (authenticate)");
1160 else if (compare_cstring (reply
->tag
,tag
))
1161 while (compare_cstring ((reply
= imap_reply (stream
,tag
))->tag
,tag
))
1162 imap_soutr (stream
,"*");
1163 /* good if SASL ok and success response */
1164 if (ok
&& imap_OK (stream
,reply
)) return T
;
1165 if (!trial
) { /* if main program requested cancellation */
1166 mm_log ("IMAP Authentication cancelled",ERROR
);
1169 /* no error if protocol-initiated cancel */
1170 lsterr
= cpystr (reply
->text
);
1173 while (LOCAL
->netstream
&& !LOCAL
->byeseen
&& trial
&&
1174 (trial
< imap_maxlogintrials
));
1176 if (lsterr
) { /* previous authenticator failed? */
1177 if (!LOCAL
->saslcancel
) { /* don't do this if a cancel */
1178 sprintf (tmp
,"Can not authenticate to IMAP server: %.80s",lsterr
);
1181 fs_give ((void **) &lsterr
);
1183 return NIL
; /* ran out of authenticators */
1187 * Accepts: stream to login
1188 * parsed network mailbox structure
1189 * scratch buffer of length MAILTMPLEN
1190 * place to return user name
1191 * Returns: T on success, NIL on failure
1194 long imap_login (MAILSTREAM
*stream
,NETMBX
*mb
,char *pwd
,char *usr
)
1196 unsigned long trial
= 0;
1197 IMAPPARSEDREPLY
*reply
;
1201 if (stream
->secure
) /* never do LOGIN if want security */
1202 mm_log ("Can't do secure authentication with this server",ERROR
);
1203 /* never do LOGIN if server disabled it */
1204 else if (LOCAL
->cap
.logindisabled
)
1205 mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR
);
1206 else if (mb
->authuser
[0]) /* never do LOGIN with /authuser */
1207 mm_log ("Can't do /authuser with this server",ERROR
);
1208 else { /* OK to try login */
1209 ausr
.type
= apwd
.type
= ASTRING
;
1210 ausr
.text
= (void *) usr
;
1211 apwd
.text
= (void *) pwd
;
1212 args
[0] = &ausr
; args
[1] = &apwd
; args
[2] = NIL
;
1214 pwd
[0] = 0; /* prompt user for password */
1215 mm_login (mb
,usr
,pwd
,trial
++);
1216 if (pwd
[0]) { /* send login command if have password */
1217 LOCAL
->sensitive
= T
; /* hide this command */
1218 /* send "LOGIN usr pwd" */
1219 if (imap_OK (stream
,reply
= imap_send (stream
,"LOGIN",args
)))
1220 ret
= LONGT
; /* success */
1222 mm_log (reply
->text
,WARN
);
1223 if (!LOCAL
->referral
&& (trial
== imap_maxlogintrials
))
1224 mm_log ("Too many login failures",ERROR
);
1226 LOCAL
->sensitive
= NIL
; /* unhide */
1228 /* user refused to give password */
1229 else mm_log ("Login aborted",ERROR
);
1230 } while (!ret
&& pwd
[0] && (trial
< imap_maxlogintrials
) &&
1231 LOCAL
->netstream
&& !LOCAL
->byeseen
&& !LOCAL
->referral
);
1233 memset (pwd
,0,MAILTMPLEN
); /* erase password */
1237 /* Get challenge to authenticator in binary
1239 * pointer to returned size
1240 * Returns: challenge or NIL if not challenge
1243 void *imap_challenge (void *s
,unsigned long *len
)
1245 char tmp
[MAILTMPLEN
];
1247 MAILSTREAM
*stream
= (MAILSTREAM
*) s
;
1248 IMAPPARSEDREPLY
*reply
= NIL
;
1249 /* get tagged response or challenge */
1250 while (stream
&& LOCAL
->netstream
&&
1251 (reply
= imap_parse_reply (stream
,net_getline (LOCAL
->netstream
))) &&
1252 !strcmp (reply
->tag
,"*")) imap_parse_unsolicited (stream
,reply
);
1253 /* parse challenge if have one */
1254 if (stream
&& LOCAL
->netstream
&& reply
&& reply
->tag
&&
1255 (*reply
->tag
== '+') && !reply
->tag
[1] && reply
->text
&&
1256 !(ret
= rfc822_base64 ((unsigned char *) reply
->text
,
1257 strlen (reply
->text
),len
))) {
1258 sprintf (tmp
,"IMAP SERVER BUG (invalid challenge): %.80s",
1259 (char *) reply
->text
);
1266 /* Send authenticator response in BASE64
1267 * Accepts: MAIL stream
1270 * Returns: T if successful, else NIL
1273 long imap_response (void *s
,char *response
,unsigned long size
)
1275 MAILSTREAM
*stream
= (MAILSTREAM
*) s
;
1276 unsigned long i
,j
,ret
;
1278 if (response
) { /* make CRLFless BASE64 string */
1280 for (t
= (char *) rfc822_binary ((void *) response
,size
,&i
),u
= t
,j
= 0;
1281 j
< i
; j
++) if (t
[j
] > ' ') *u
++ = t
[j
];
1282 *u
= '\0'; /* tie off string for mm_dlog() */
1283 if (stream
->debug
) mail_dlog (t
,LOCAL
->sensitive
);
1285 *u
++ = '\015'; *u
++ = '\012';
1286 ret
= net_sout (LOCAL
->netstream
,t
,u
- t
);
1287 fs_give ((void **) &t
);
1289 else ret
= imap_soutr (stream
,"");
1291 else { /* abort requested */
1292 ret
= imap_soutr (stream
,"*");
1293 LOCAL
->saslcancel
= T
; /* mark protocol-requested SASL cancel */
1299 * Accepts: MAIL stream
1303 void imap_close (MAILSTREAM
*stream
,long options
)
1306 IMAPPARSEDREPLY
*reply
;
1307 if (stream
&& LOCAL
) { /* send "LOGOUT" */
1308 if (!LOCAL
->byeseen
) { /* don't even think of doing it if saw a BYE */
1309 /* expunge silently if requested */
1310 if (options
& CL_EXPUNGE
)
1311 imap_send (stream
,LEVELIMAP4 (stream
) ? "CLOSE" : "EXPUNGE",NIL
);
1312 if (LOCAL
->netstream
&&
1313 !imap_OK (stream
,reply
= imap_send (stream
,"LOGOUT",NIL
)))
1314 mm_log (reply
->text
,WARN
);
1316 /* close NET connection if still open */
1317 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
1318 LOCAL
->netstream
= NIL
;
1319 /* free up memory */
1320 if (LOCAL
->sortdata
) fs_give ((void **) &LOCAL
->sortdata
);
1321 if (LOCAL
->namespace) {
1322 mail_free_namespace (&LOCAL
->namespace[0]);
1323 mail_free_namespace (&LOCAL
->namespace[1]);
1324 mail_free_namespace (&LOCAL
->namespace[2]);
1325 fs_give ((void **) &LOCAL
->namespace);
1327 if (LOCAL
->threaddata
) mail_free_threadnode (&LOCAL
->threaddata
);
1328 /* flush threaders */
1329 if (thr
= LOCAL
->cap
.threader
) while (t
= thr
) {
1330 fs_give ((void **) &t
->name
);
1332 fs_give ((void **) &t
);
1334 if (LOCAL
->referral
) fs_give ((void **) &LOCAL
->referral
);
1335 if (LOCAL
->user
) fs_give ((void **) &LOCAL
->user
);
1336 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
1337 if (LOCAL
->reform
) fs_give ((void **) &LOCAL
->reform
);
1338 /* nuke the local data */
1339 fs_give ((void **) &stream
->local
);
1343 /* IMAP fetch fast information
1344 * Accepts: MAIL stream
1348 * Generally, imap_structure is preferred
1351 void imap_fast (MAILSTREAM
*stream
,char *sequence
,long flags
)
1353 IMAPPARSEDREPLY
*reply
= imap_fetch (stream
,sequence
,flags
& FT_UID
);
1354 if (!imap_OK (stream
,reply
)) mm_log (reply
->text
,ERROR
);
1359 * Accepts: MAIL stream
1364 void imap_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
1365 { /* send "FETCH sequence FLAGS" */
1366 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ? "UID FETCH":"FETCH";
1367 IMAPPARSEDREPLY
*reply
;
1368 IMAPARG
*args
[3],aseq
,aatt
;
1369 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
1371 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
1372 aatt
.type
= ATOM
; aatt
.text
= (void *) "FLAGS";
1373 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1374 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
)))
1375 mm_log (reply
->text
,ERROR
);
1378 /* IMAP fetch overview
1379 * Accepts: MAIL stream, sequence bits set
1380 * pointer to overview return function
1381 * Returns: T if successful, NIL otherwise
1384 long imap_overview (MAILSTREAM
*stream
,overview_t ofn
)
1390 unsigned long i
,start
,last
,len
,slen
;
1391 if (!LOCAL
->netstream
) return NIL
;
1392 /* build overview sequence */
1393 for (i
= 1,len
= start
= last
= 0,s
= t
= NIL
; i
<= stream
->nmsgs
; ++i
)
1394 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1395 if (!elt
->private.msg
.env
) {
1396 if (s
) { /* continuing a sequence */
1397 if (i
== last
+ 1) last
= i
;
1398 else { /* end of range */
1399 if (last
!= start
) sprintf (t
,":%lu,%lu",last
,i
);
1400 else sprintf (t
,",%lu",i
);
1401 if ((len
- (slen
= (t
+= strlen (t
)) - s
)) < 20) {
1402 fs_resize ((void **) &s
,len
+= MAILTMPLEN
);
1403 t
= s
+ slen
; /* relocate current pointer */
1405 start
= last
= i
; /* begin a new range */
1408 else { /* first time, start new buffer */
1409 s
= (char *) fs_get (len
= MAILTMPLEN
);
1410 sprintf (s
,"%lu",start
= last
= i
);
1411 t
= s
+ strlen (s
); /* end of buffer */
1416 if (last
!= start
) sprintf (t
,":%lu",last
);
1417 if (s
) { /* prefetch as needed */
1418 imap_fetch (stream
,s
,FT_NEEDENV
);
1419 fs_give ((void **) &s
);
1421 ov
.optional
.lines
= 0; /* now overview each message */
1422 ov
.optional
.xref
= NIL
;
1423 if (ofn
) for (i
= 1; i
<= stream
->nmsgs
; i
++)
1424 if (((elt
= mail_elt (stream
,i
))->sequence
) &&
1425 (env
= mail_fetch_structure (stream
,i
,NIL
,NIL
)) && ofn
) {
1426 ov
.subject
= env
->subject
;
1427 ov
.from
= env
->from
;
1428 ov
.date
= env
->date
;
1429 ov
.message_id
= env
->message_id
;
1430 ov
.references
= env
->references
;
1431 ov
.optional
.octets
= elt
->rfc822_size
;
1432 (*ofn
) (stream
,mail_uid (stream
,i
),&ov
,i
);
1437 /* IMAP fetch structure
1438 * Accepts: MAIL stream
1439 * message # to fetch
1440 * pointer to return body
1442 * Returns: envelope of this message, body returned in body value
1444 * Fetches the "fast" information as well
1447 ENVELOPE
*imap_structure (MAILSTREAM
*stream
,unsigned long msgno
,BODY
**body
,
1450 unsigned long i
,j
,k
,x
;
1451 char *s
,seq
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
1455 IMAPPARSEDREPLY
*reply
= NIL
;
1456 IMAPARG
*args
[3],aseq
,aatt
;
1457 SEARCHSET
*set
= LOCAL
->lookahead
;
1458 LOCAL
->lookahead
= NIL
;
1459 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1460 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1461 aatt
.type
= ATOM
; aatt
.text
= NIL
;
1462 if (flags
& FT_UID
) /* see if can find msgno from UID */
1463 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1464 if ((elt
= mail_elt (stream
,i
))->private.uid
== msgno
) {
1465 msgno
= i
; /* found msgno, use it from now on */
1466 flags
&= ~FT_UID
; /* no longer a UID fetch */
1468 sprintf (s
= seq
,"%lu",msgno
);/* initial sequence */
1469 if (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) {
1470 /* UID fetching is requested and we can't map the UID to a message sequence
1471 * number. Assume that the message isn't cached at all.
1473 if (!imap_OK (stream
,reply
= imap_fetch (stream
,seq
,FT_NEEDENV
+
1474 (body
? FT_NEEDBODY
: NIL
) +
1475 (flags
& (FT_UID
+ FT_NOHDRS
)))))
1476 mm_log (reply
->text
,ERROR
);
1477 /* now hunt for this UID */
1478 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1479 if ((elt
= mail_elt (stream
,i
))->private.uid
== msgno
) {
1480 if (body
) *body
= elt
->private.msg
.body
;
1481 return elt
->private.msg
.env
;
1483 if (body
) *body
= NIL
; /* can't find the UID */
1486 elt
= mail_elt (stream
,msgno
);/* get cache pointer */
1487 if (stream
->scache
) { /* short caching? */
1488 env
= &stream
->env
; /* use temporaries on the stream */
1490 if (msgno
!= stream
->msgno
){/* flush old poop if a different message */
1491 mail_free_envelope (env
);
1493 stream
->msgno
= msgno
; /* this is now the current short cache msg */
1497 else { /* normal cache */
1498 env
= &elt
->private.msg
.env
;/* get envelope and body pointers */
1499 b
= &elt
->private.msg
.body
;
1500 /* prefetch if don't have envelope */
1501 if (!(flags
& FT_NOLOOKAHEAD
) &&
1502 ((!*env
|| (*env
)->incomplete
) ||
1503 (body
&& !*b
&& LEVELIMAP2bis (stream
)))) {
1504 if (set
) { /* have a lookahead list? */
1506 for (k
= imap_fetchlookaheadlimit
;
1507 k
&& set
&& (((s
+= strlen (s
)) - seq
) < (MAXCOMMAND
- 30));
1509 i
= (set
->first
== 0xffffffff) ? stream
->nmsgs
:
1510 min (set
->first
,stream
->nmsgs
);
1511 if (j
= (set
->last
== 0xffffffff) ? stream
->nmsgs
:
1512 min (set
->last
,stream
->nmsgs
)) {
1513 if (i
> j
) { /* swap the range if backwards */
1514 x
= i
; i
= j
; j
= x
;
1516 /* find first message not msgno or in cache */
1517 while (((i
== msgno
) ||
1518 ((msg
= &(mail_elt (stream
,i
)->private.msg
))->env
&&
1519 (!body
|| msg
->body
))) && (i
++ < j
));
1520 /* until range or lookahead finished */
1521 while (k
&& (i
<= j
)) {
1522 /* find first cached message in range */
1523 for (x
= i
+ 1; (x
<= j
) &&
1524 !((msg
= &(mail_elt (stream
,x
)->private.msg
))->env
&&
1525 (!body
|| msg
->body
)); x
++);
1526 if (i
== --x
) { /* only one message? */
1527 sprintf (s
+= strlen (s
),",%lu",i
++);
1528 k
--; /* prefetching one message */
1530 else { /* a range to prefetch */
1531 sprintf (s
+= strlen (s
),",%lu:%lu",i
,x
);
1532 i
= 1 + x
- i
; /* number of messages in this range */
1533 /* still can look ahead some more? */
1534 if (k
= (k
> i
) ? k
- i
: 0)
1535 /* yes, scan further in this range */
1536 for (i
= x
+ 2; (i
<= j
) &&
1538 ((msg
= &(mail_elt (stream
,i
)->private.msg
))->env
&&
1539 (!body
|| msg
->body
)));
1544 else if ((i
!= msgno
) && !mail_elt (stream
,i
)->private.msg
.env
) {
1545 sprintf (s
+= strlen (s
),",%lu",i
);
1546 k
--; /* prefetching one message */
1550 /* build message number list */
1551 else for (i
= msgno
+1,k
= imap_lookahead
; k
&& (i
<= stream
->nmsgs
); i
++)
1552 if (!mail_elt (stream
,i
)->private.msg
.env
) {
1553 s
+= strlen (s
); /* find string end, see if nearing end */
1554 if ((s
- seq
) > (MAILTMPLEN
- 20)) break;
1555 sprintf (s
,",%lu",i
); /* append message */
1556 for (j
= i
+ 1, k
--; /* hunt for last message without an envelope */
1557 k
&& (j
<= stream
->nmsgs
) &&
1558 !mail_elt (stream
,j
)->private.msg
.env
; j
++, k
--);
1559 /* if different, make a range */
1560 if (i
!= --j
) sprintf (s
+ strlen (s
),":%lu",i
= j
);
1565 if (!stream
->lock
) { /* no-op if stream locked */
1566 /* Build the fetch attributes. Unlike imap_fetch(), this tries not to
1567 * fetch data that is already cached. However, since it is based on the
1568 * message requested and not on any of the prefetched messages, it can
1569 * goof, either by fetching data already cached or not prefetching data
1570 * that isn't cached (but was cached in the message requested).
1571 * Fortunately, no great harm is done. If it doesn't prefetch the data,
1572 * it will get it when the affected message(s) are requested.
1574 if (!elt
->private.uid
&& LEVELIMAP4 (stream
)) strcpy (tmp
," UID");
1575 else tmp
[0] = '\0'; /* initialize command */
1576 /* need envelope? */
1577 if (!*env
|| (*env
)->incomplete
) {
1578 strcat (tmp
," ENVELOPE"); /* yes, get it and possible extra poop */
1579 if (!(flags
& FT_NOHDRS
) && LEVELIMAP4rev1 (stream
)) {
1580 if (imap_extrahdrs
) sprintf (tmp
+ strlen (tmp
)," %s %s %s",
1581 hdrheader
[LOCAL
->cap
.extlevel
],
1582 imap_extrahdrs
,hdrtrailer
);
1583 else sprintf (tmp
+ strlen (tmp
)," %s %s",
1584 hdrheader
[LOCAL
->cap
.extlevel
],hdrtrailer
);
1588 if (body
&& !*b
&& LEVELIMAP2bis (stream
))
1589 strcat (tmp
,LEVELIMAP4 (stream
) ? " BODYSTRUCTURE" : " BODY");
1590 if (!elt
->day
) strcat (tmp
," INTERNALDATE");
1591 if (!elt
->rfc822_size
) strcat (tmp
," RFC822.SIZE");
1592 if (tmp
[0]) { /* anything to do? */
1593 tmp
[0] = '('; /* make into a list */
1594 strcat (tmp
," FLAGS)"); /* always get current flags */
1595 aatt
.text
= (void *) tmp
; /* do the built command */
1596 if (!imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
))) {
1597 /* failed, probably RFC-1176 server */
1598 if (!LEVELIMAP4 (stream
) && LEVELIMAP2bis (stream
) && body
&& !*b
){
1599 aatt
.text
= (void *) "ALL";
1600 if (imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
)))
1601 /* doesn't have body capabilities */
1602 LOCAL
->cap
.imap2bis
= NIL
;
1603 else mm_log (reply
->text
,ERROR
);
1605 else mm_log (reply
->text
,ERROR
);
1609 if (body
) { /* wants to return body */
1610 if (!*b
&& !LEVELIMAP2bis (stream
)) {
1611 /* simulate body structure fetch for IMAP2 */
1612 *b
= mail_initbody (mail_newbody ());
1613 (*b
)->subtype
= cpystr (rfc822_default_subtype ((*b
)->type
));
1614 ((*b
)->parameter
= mail_newbody_parameter ())->attribute
=
1616 (*b
)->parameter
->value
= cpystr ("US-ASCII");
1617 s
= mail_fetch_text (stream
,msgno
,NIL
,&i
,flags
);
1618 (*b
)->size
.bytes
= i
;
1619 while (i
--) if (*s
++ == '\n') (*b
)->size
.lines
++;
1621 *body
= *b
; /* return the body */
1623 return *env
; /* return the envelope */
1626 /* IMAP fetch message data
1627 * Accepts: MAIL stream
1630 * offset of first designated byte or 0 to start at beginning
1631 * maximum number of bytes or 0 for all bytes
1632 * lines to fetch if header
1634 * Returns: T on success, NIL on failure
1637 long imap_msgdata (MAILSTREAM
*stream
,unsigned long msgno
,char *section
,
1638 unsigned long first
,unsigned long last
,STRINGLIST
*lines
,
1642 char *t
,tmp
[MAILTMPLEN
],partial
[40],seq
[40];
1643 char *noextend
,*nopartial
,*nolines
,*nopeek
,*nononpeek
;
1644 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ? "UID FETCH":"FETCH";
1645 IMAPPARSEDREPLY
*reply
;
1646 IMAPARG
*args
[5],*auxargs
[3],aseq
,aatt
,alns
,acls
,aflg
;
1647 noextend
= nopartial
= nolines
= nopeek
= nononpeek
= NIL
;
1648 /* does searching desire a lookahead? */
1649 if ((flags
& FT_SEARCHLOOKAHEAD
) && (msgno
< stream
->nmsgs
) &&
1651 sprintf (seq
,"%lu:%lu",msgno
,
1652 (unsigned long) min (msgno
+ IMAPLOOKAHEAD
,stream
->nmsgs
));
1653 aseq
.type
= SEQUENCE
;
1654 aseq
.text
= (void *) seq
;
1656 else { /* no, do it the easy way */
1658 aseq
.text
= (void *) msgno
;
1660 aatt
.type
= ATOM
; /* assume atomic attribute */
1661 alns
.type
= LIST
; alns
.text
= (void *) lines
;
1662 acls
.type
= BODYCLOSE
; acls
.text
= (void *) partial
;
1663 aflg
.type
= ATOM
; aflg
.text
= (void *) "FLAGS";
1664 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = args
[3] = args
[4] = NIL
;
1665 auxargs
[0] = &aseq
; auxargs
[1] = &aflg
; auxargs
[2] = NIL
;
1666 partial
[0] = '\0'; /* initially no partial specifier */
1667 if (LEVELIMAP4rev1 (stream
)) {/* easy if IMAP4rev1 server */
1668 /* HEADER fetching with special handling? */
1669 if (!strcmp (section
,"HEADER") && (lines
|| (flags
& FT_PREFETCHTEXT
))) {
1670 if (lines
) { /* want specific header lines? */
1671 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1672 aatt
.text
= (void *) ((flags
& FT_NOT
) ?
1673 "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
1674 args
[2] = &alns
; args
[3] = &acls
;
1676 /* must be prefetching */
1677 else aatt
.text
= (void *) ((flags
& FT_PEEK
) ?
1678 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
1679 "(BODY[HEADER] BODY[TEXT])");
1681 else { /* simple case */
1682 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1683 aatt
.text
= (void *) section
;
1686 if (first
|| last
) sprintf (partial
,"<%lu.%lu>",first
,last
? last
:-1);
1689 /* IMAP4 did not have:
1690 * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
1691 * . TEXT body part (can simulate top-level with RFC822.TEXT or
1694 * . (usable) partial fetching
1695 * . (usable) selective header line fetching
1697 else if (LEVEL1730 (stream
)) {/* IMAP4 (RFC 1730) compatibility */
1698 /* BODY[HEADER] becomes BODY.PEEK[0] */
1699 if (!strcmp (section
,"HEADER"))
1700 aatt
.text
= (void *)
1701 ((flags
& FT_PREFETCHTEXT
) ?
1702 ((flags
& FT_PEEK
) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
1703 "(BODY[0] RFC822.TEXT)") :
1704 ((flags
& FT_PEEK
) ? "BODY.PEEK[0]" : "BODY[0]"));
1705 /* BODY[TEXT] becomes RFC822.TEXT */
1706 else if (!strcmp (section
,"TEXT"))
1707 aatt
.text
= (void *) ((flags
& FT_PEEK
) ? "RFC822.TEXT.PEEK" :
1709 else if (!section
[0]) /* BODY[] becomes RFC822 */
1710 aatt
.text
= (void *) ((flags
& FT_PEEK
) ? "RFC822.PEEK" : "RFC822");
1712 else if (t
= strstr (section
,".HEADER")) {
1713 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1714 args
[2] = &acls
; /* will need to close section */
1715 aatt
.text
= (void *) tmp
; /* convert .HEADER to .0 */
1716 strncpy (tmp
,section
,t
-section
);
1717 strcpy (tmp
+(t
-section
),".0");
1719 else { /* IMAP4 body part */
1720 aatt
.type
= (flags
& FT_PEEK
) ? BODYPEEK
: BODYTEXT
;
1721 args
[2] = &acls
; /* will need to close section */
1722 aatt
.text
= (void *) section
;
1724 if (strstr (section
,".MIME") || strstr (section
,".TEXT")) noextend
= "4";
1725 if (first
|| last
) nopartial
= "4";
1726 if (lines
) nolines
= "4";
1729 /* IMAP2bis did not have:
1730 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1731 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1733 * . partial fetching
1734 * . selective header line fetching
1735 * . non-peeking header fetching
1736 * . peeking body fetching
1738 /* IMAP2bis compatibility */
1739 else if (LEVELIMAP2bis (stream
)) {
1740 /* BODY[HEADER] becomes RFC822.HEADER */
1741 if (!strcmp (section
,"HEADER")) {
1742 aatt
.text
= (void *)
1743 ((flags
& FT_PREFETCHTEXT
) ?
1744 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1745 if (flags
& FT_PEEK
) flags
&= ~FT_PEEK
;
1746 else nononpeek
= "2bis";
1748 /* BODY[TEXT] becomes RFC822.TEXT */
1749 else if (!strcmp (section
,"TEXT")) aatt
.text
= (void *) "RFC822.TEXT";
1750 /* BODY[] becomes RFC822 */
1751 else if (!section
[0]) aatt
.text
= (void *) "RFC822";
1752 else { /* IMAP2bis body part */
1753 aatt
.type
= BODYTEXT
;
1754 args
[2] = &acls
; /* will need to close section */
1755 aatt
.text
= (void *) section
;
1757 if (strstr (section
,".HEADER") || strstr (section
,".MIME") ||
1758 strstr (section
,".TEXT")) noextend
= "2bis";
1759 if (first
|| last
) nopartial
= "2bis";
1760 if (lines
) nolines
= "2bis";
1761 if (flags
& FT_PEEK
) nopeek
= "2bis";
1764 /* IMAP2 did not have:
1765 * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
1766 * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
1768 * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
1769 * . partial fetching
1770 * . selective header line fetching
1771 * . non-peeking header fetching
1772 * . peeking body fetching
1774 else { /* IMAP2 (RFC 1176/1064) compatibility */
1776 if (!strcmp (section
,"HEADER")) {
1777 aatt
.text
= (void *) ((flags
& FT_PREFETCHTEXT
) ?
1778 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
1779 if (flags
& FT_PEEK
) flags
&= ~FT_PEEK
;
1782 /* BODY[TEXT] becomes RFC822.TEXT */
1783 else if (!strcmp (section
,"TEXT")) aatt
.text
= (void *) "RFC822.TEXT";
1784 /* BODY[1] treated like RFC822.TEXT */
1785 else if (!strcmp (section
,"1")) {
1787 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1788 /* have a cached RFC822.TEXT? */
1789 if (elt
->private.msg
.text
.text
.data
) {
1790 text
.size
= elt
->private.msg
.text
.text
.size
;
1791 /* should move instead of copy */
1792 text
.data
= memcpy (fs_get (text
.size
+1),
1793 elt
->private.msg
.text
.text
.data
,text
.size
);
1794 (t
= (char *) text
.data
)[text
.size
] = '\0';
1795 imap_cache (stream
,msgno
,"1",NIL
,&text
);
1796 return LONGT
; /* don't have to do any fetches */
1798 /* otherwise do RFC822.TEXT */
1799 aatt
.text
= (void *) "RFC822.TEXT";
1801 /* BODY[] becomes RFC822 */
1802 else if (!section
[0]) aatt
.text
= (void *) "RFC822";
1803 else noextend
= "2"; /* how did we get here? */
1804 if (flags
& FT_PEEK
) nopeek
= "2";
1805 if (first
|| last
) nopartial
= "2";
1806 if (lines
) nolines
= "2";
1809 /* Report unavailable functionalities. The application can use the helpful
1810 * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
1811 * imap4r1.h to avoid triggering these errors. There aren't any workarounds
1812 * for these restrictions.
1815 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
1818 return NIL
; /* can't do anything close either */
1821 sprintf (tmp
,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
1823 mm_notify (stream
,tmp
,WARN
);
1826 sprintf(tmp
,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
1828 mm_notify (stream
,tmp
,WARN
);
1831 /* trying to do unsupported peek behavior? */
1832 if ((t
= nopeek
) || (t
= nononpeek
)) {
1833 /* get most recent \Seen setting */
1834 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,auxargs
)))
1835 mm_log (reply
->text
,WARN
);
1836 /* note current setting of \Seen flag */
1837 if (!(i
= mail_elt (stream
,msgno
)->seen
)) {
1838 sprintf (tmp
,nopeek
? /* only babble if \Seen not set */
1839 "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
1840 "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t
);
1841 mm_notify (stream
,tmp
,NIL
);
1843 /* send the fetch command */
1844 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
))) {
1845 mm_log (reply
->text
,ERROR
);
1846 return NIL
; /* failure */
1848 /* send command if need to reset \Seen */
1849 if (((nopeek
&& !i
&& mail_elt (stream
,msgno
)->seen
&&
1850 (aflg
.text
= "-FLAGS \\Seen")) ||
1851 ((nononpeek
&& !mail_elt (stream
,msgno
)->seen
) &&
1852 (aflg
.text
= "+FLAGS \\Seen"))) &&
1853 !imap_OK (stream
,reply
= imap_send (stream
,"STORE",auxargs
)))
1854 mm_log (reply
->text
,WARN
);
1856 /* simple case if traditional behavior */
1857 else if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
))) {
1858 mm_log (reply
->text
,ERROR
);
1859 return NIL
; /* failure */
1861 /* simulate BODY[1] return for RFC 1064/1176 */
1862 if (!LEVELIMAP2bis (stream
) && !strcmp (section
,"1")) {
1864 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1865 text
.size
= elt
->private.msg
.text
.text
.size
;
1866 /* should move instead of copy */
1867 text
.data
= memcpy (fs_get (text
.size
+1),elt
->private.msg
.text
.text
.data
,
1869 (t
= (char *) text
.data
)[text
.size
] = '\0';
1870 imap_cache (stream
,msgno
,"1",NIL
,&text
);
1876 * Accepts: MAIL stream
1881 unsigned long imap_uid (MAILSTREAM
*stream
,unsigned long msgno
)
1884 IMAPPARSEDREPLY
*reply
;
1885 IMAPARG
*args
[3],aseq
,aatt
;
1886 char *s
,seq
[MAILTMPLEN
];
1887 unsigned long i
,j
,k
;
1888 /* IMAP2 didn't have UIDs */
1889 if (!LEVELIMAP4 (stream
)) return msgno
;
1890 /* do we know its UID yet? */
1891 if (!(elt
= mail_elt (stream
,msgno
))->private.uid
) {
1892 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1893 aatt
.type
= ATOM
; aatt
.text
= (void *) "UID";
1894 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1895 sprintf (seq
,"%lu",msgno
);
1896 if (k
= imap_uidlookahead
) {/* build UID list */
1897 for (i
= msgno
+ 1, s
= seq
; k
&& (i
<= stream
->nmsgs
); i
++)
1898 if (!mail_elt (stream
,i
)->private.uid
) {
1899 s
+= strlen (s
); /* find string end, see if nearing end */
1900 if ((s
- seq
) > (MAILTMPLEN
- 20)) break;
1901 sprintf (s
,",%lu",i
); /* append message */
1902 for (j
= i
+ 1, k
--; /* hunt for last message without a UID */
1903 k
&& (j
<= stream
->nmsgs
) && !mail_elt (stream
,j
)->private.uid
;
1905 /* if different, make a range */
1906 if (i
!= --j
) sprintf (s
+ strlen (s
),":%lu",i
= j
);
1909 /* send "FETCH msgno UID" */
1910 if (!imap_OK (stream
,reply
= imap_send (stream
,"FETCH",args
)))
1911 mm_log (reply
->text
,ERROR
);
1913 return elt
->private.uid
; /* return our UID now */
1916 /* IMAP fetch message number from UID
1917 * Accepts: MAIL stream
1919 * Returns: message number
1922 unsigned long imap_msgno (MAILSTREAM
*stream
,unsigned long uid
)
1924 IMAPPARSEDREPLY
*reply
;
1925 IMAPARG
*args
[3],aseq
,aatt
;
1926 char seq
[MAILTMPLEN
];
1928 unsigned long i
,msgno
;
1929 /* IMAP2 didn't have UIDs */
1930 if (!LEVELIMAP4 (stream
)) return uid
;
1931 /* This really should be a binary search, but since there are likely to be
1932 * holes in the msgno->UID map it's hard to do.
1934 for (msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++) {
1935 if (!(i
= mail_elt (stream
,msgno
)->private.uid
)) holes
= T
;
1936 else if (i
== uid
) return msgno
;
1938 if (holes
) { /* have holes in cache? */
1939 /* yes, have server hunt for UID */
1940 LOCAL
->lastuid
.uid
= LOCAL
->lastuid
.msgno
= 0;
1941 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) seq
;
1942 aatt
.type
= ATOM
; aatt
.text
= (void *) "UID";
1943 args
[0] = &aseq
; args
[1] = &aatt
; args
[2] = NIL
;
1944 sprintf (seq
,"%lu",uid
);
1945 /* send "UID FETCH uid UID" */
1946 if (!imap_OK (stream
,reply
= imap_send (stream
,"UID FETCH",args
)))
1947 mm_log (reply
->text
,ERROR
);
1948 if (LOCAL
->lastuid
.uid
) { /* got any results from FETCH? */
1949 if ((LOCAL
->lastuid
.uid
== uid
) &&
1950 /* what, me paranoid? */
1951 (LOCAL
->lastuid
.msgno
<= stream
->nmsgs
) &&
1952 (mail_elt (stream
,LOCAL
->lastuid
.msgno
)->private.uid
== uid
))
1953 /* got it the easy way */
1954 return LOCAL
->lastuid
.msgno
;
1955 /* sigh, do another linear search... */
1956 for (msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++)
1957 if (mail_elt (stream
,msgno
)->private.uid
== uid
) return msgno
;
1960 return 0; /* didn't find the UID anywhere */
1963 /* IMAP modify flags
1964 * Accepts: MAIL stream
1970 void imap_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
1972 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& ST_UID
)) ? "UID STORE":"STORE";
1973 IMAPPARSEDREPLY
*reply
;
1974 IMAPARG
*args
[4],aseq
,ascm
,aflg
;
1975 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
1977 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
1978 ascm
.type
= ATOM
; ascm
.text
= (void *)
1980 ((LEVELIMAP4 (stream
) && (flags
& ST_SILENT
)) ?
1981 "+Flags.silent" : "+Flags") :
1982 ((LEVELIMAP4 (stream
) && (flags
& ST_SILENT
)) ?
1983 "-Flags.silent" : "-Flags"));
1984 aflg
.type
= FLAGS
; aflg
.text
= (void *) flag
;
1985 args
[0] = &aseq
; args
[1] = &ascm
; args
[2] = &aflg
; args
[3] = NIL
;
1986 /* send "STORE sequence +Flags flag" */
1987 if (!imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
)))
1988 mm_log (reply
->text
,ERROR
);
1991 /* IMAP search for messages
1992 * Accepts: MAIL stream
1996 * Returns: T on success, NIL on failure
1999 long imap_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
)
2001 unsigned long i
,j
,k
;
2003 IMAPPARSEDREPLY
*reply
;
2005 if ((flags
& SE_NOSERVER
) || /* if want to do local search */
2006 LOCAL
->loser
|| /* or loser */
2007 (!LEVELIMAP4 (stream
) && /* or old server but new functions... */
2008 (charset
|| (flags
& SE_UID
) || pgm
->msgno
|| pgm
->uid
|| pgm
->or ||
2009 pgm
->not || pgm
->header
|| pgm
->larger
|| pgm
->smaller
||
2010 pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
|| pgm
->draft
||
2011 pgm
->undraft
|| pgm
->return_path
|| pgm
->sender
|| pgm
->reply_to
||
2012 pgm
->message_id
|| pgm
->in_reply_to
|| pgm
->newsgroups
||
2013 pgm
->followup_to
|| pgm
->references
)) ||
2014 (!LEVELWITHIN (stream
) && (pgm
->older
|| pgm
->younger
))) {
2015 if ((flags
& SE_NOLOCAL
) ||
2016 !mail_search_default (stream
,charset
,pgm
,flags
| SE_NOSERVER
))
2019 /* do silly ALL or seq-only search locally */
2020 else if (!(flags
& (SE_NOLOCAL
|SE_SILLYOK
)) &&
2021 !(pgm
->uid
|| pgm
->or || pgm
->not ||
2022 pgm
->header
|| pgm
->from
|| pgm
->to
|| pgm
->cc
|| pgm
->bcc
||
2023 pgm
->subject
|| pgm
->body
|| pgm
->text
||
2024 pgm
->larger
|| pgm
->smaller
||
2025 pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
||
2026 pgm
->before
|| pgm
->on
|| pgm
->since
||
2027 pgm
->answered
|| pgm
->unanswered
||
2028 pgm
->deleted
|| pgm
->undeleted
|| pgm
->draft
|| pgm
->undraft
||
2029 pgm
->flagged
|| pgm
->unflagged
|| pgm
->recent
|| pgm
->old
||
2030 pgm
->seen
|| pgm
->unseen
||
2031 pgm
->keyword
|| pgm
->unkeyword
||
2032 pgm
->return_path
|| pgm
->sender
||
2033 pgm
->reply_to
|| pgm
->in_reply_to
|| pgm
->message_id
||
2034 pgm
->newsgroups
|| pgm
->followup_to
|| pgm
->references
)) {
2035 if (!mail_search_default (stream
,NIL
,pgm
,flags
| SE_NOSERVER
))
2036 fatal ("impossible mail_search_default() failure");
2039 else { /* do server-based SEARCH */
2040 char *cmd
= (flags
& SE_UID
) ? "UID SEARCH" : "SEARCH";
2041 IMAPARG
*args
[4],apgm
,aatt
,achs
;
2043 args
[1] = args
[2] = args
[3] = NIL
;
2044 apgm
.type
= SEARCHPROGRAM
; apgm
.text
= (void *) pgm
;
2045 if (charset
) { /* optional charset argument requested */
2046 args
[0] = &aatt
; args
[1] = &achs
; args
[2] = &apgm
;
2047 aatt
.type
= ATOM
; aatt
.text
= (void *) "CHARSET";
2048 achs
.type
= ASTRING
; achs
.text
= (void *) charset
;
2050 else args
[0] = &apgm
; /* no charset argument */
2051 /* tell receiver that these will be UIDs */
2052 LOCAL
->uidsearch
= (flags
& SE_UID
) ? T
: NIL
;
2053 reply
= imap_send (stream
,cmd
,args
);
2054 /* did server barf with that searchpgm? */
2055 if (!(flags
& SE_UID
) && pgm
&& (ss
= pgm
->msgno
) &&
2056 !strcmp (reply
->key
,"BAD")) {
2057 LOCAL
->filter
= T
; /* retry, filtering SEARCH results */
2058 for (i
= 1; i
<= stream
->nmsgs
; i
++)
2059 mail_elt (stream
,i
)->private.filter
= NIL
;
2060 for (set
= ss
; set
; set
= set
->next
) if (i
= set
->first
) {
2061 /* single message becomes one-message range */
2062 if (!(j
= set
->last
)) j
= i
;
2063 else if (j
< i
) { /* swap reversed range */
2064 i
= set
->last
; j
= set
->first
;
2066 while (i
<= j
) mail_elt (stream
,i
++)->private.filter
= T
;
2068 pgm
->msgno
= NIL
; /* and without the searchset */
2069 reply
= imap_send (stream
,cmd
,args
);
2070 pgm
->msgno
= ss
; /* restore searchset */
2071 LOCAL
->filter
= NIL
; /* turn off filtering */
2073 LOCAL
->uidsearch
= NIL
;
2074 /* do locally if server won't grok */
2075 if (!strcmp (reply
->key
,"BAD")) {
2076 if ((flags
& SE_NOLOCAL
) ||
2077 !mail_search_default (stream
,charset
,pgm
,flags
| SE_NOSERVER
))
2080 else if (!imap_OK (stream
,reply
)) {
2081 mm_log (reply
->text
,ERROR
);
2086 /* can never pre-fetch with a short cache */
2087 if ((k
= imap_prefetch
) && !(flags
& (SE_NOPREFETCH
| SE_UID
)) &&
2088 !stream
->scache
) { /* only if prefetching permitted */
2089 s
= LOCAL
->tmp
; /* build sequence in temporary buffer */
2090 *s
= '\0'; /* initially nothing */
2091 /* search through mailbox */
2092 for (i
= 1; k
&& (i
<= stream
->nmsgs
); ++i
)
2093 /* for searched messages with no envelope */
2094 if ((elt
= mail_elt (stream
,i
)) && elt
->searched
&&
2095 !mail_elt (stream
,i
)->private.msg
.env
) {
2096 /* prepend with comma if not first time */
2097 if (LOCAL
->tmp
[0]) *s
++ = ',';
2098 sprintf (s
,"%lu",j
= i
);/* output message number */
2099 s
+= strlen (s
); /* point at end of string */
2100 k
--; /* count one up */
2101 /* search for possible end of range */
2102 while (k
&& (i
< stream
->nmsgs
) &&
2103 (elt
= mail_elt (stream
,i
+1))->searched
&&
2104 !elt
->private.msg
.env
) i
++,k
--;
2105 if (i
!= j
) { /* if a range */
2106 sprintf (s
,":%lu",i
); /* output delimiter and end of range */
2107 s
+= strlen (s
); /* point at end of string */
2109 if ((s
- LOCAL
->tmp
) > (IMAPTMPLEN
- 50)) break;
2111 if (LOCAL
->tmp
[0]) { /* anything to pre-fetch? */
2112 /* pre-fetch envelopes for the first imap_prefetch number of messages */
2113 if (!imap_OK (stream
,reply
=
2114 imap_fetch (stream
,s
= cpystr (LOCAL
->tmp
),FT_NEEDENV
+
2115 ((flags
& SE_NOHDRS
) ? FT_NOHDRS
: NIL
) +
2116 ((flags
& SE_NEEDBODY
) ? FT_NEEDBODY
: NIL
))))
2117 mm_log (reply
->text
,ERROR
);
2118 fs_give ((void **) &s
); /* flush copy of sequence */
2124 /* IMAP sort messages
2125 * Accepts: mail stream
2130 * Returns: vector of sorted message sequences or NIL if error
2133 unsigned long *imap_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
2134 SORTPGM
*pgm
,long flags
)
2136 unsigned long i
,j
,start
,last
;
2137 unsigned long *ret
= NIL
;
2138 pgm
->nmsgs
= 0; /* start off with no messages */
2139 /* can use server-based sort? */
2140 if (LEVELSORT (stream
) && !(flags
& SE_NOSERVER
) &&
2141 (!spg
|| (LEVELWITHIN (stream
) || !(spg
->older
|| spg
->younger
)))) {
2142 char *cmd
= (flags
& SE_UID
) ? "UID SORT" : "SORT";
2143 IMAPARG
*args
[4],apgm
,achs
,aspg
;
2144 IMAPPARSEDREPLY
*reply
;
2145 SEARCHSET
*ss
= NIL
;
2146 SEARCHPGM
*tsp
= NIL
;
2147 apgm
.type
= SORTPROGRAM
; apgm
.text
= (void *) pgm
;
2148 achs
.type
= ASTRING
; achs
.text
= (void *) (charset
? charset
: "US-ASCII");
2149 aspg
.type
= SEARCHPROGRAM
;
2150 /* did he provide a searchpgm? */
2151 if (!(aspg
.text
= (void *) spg
)) {
2152 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
2153 if (mail_elt (stream
,i
)->searched
) {
2154 if (ss
) { /* continuing a sequence */
2155 if (i
== last
+ 1) last
= i
;
2156 else { /* end of range */
2157 if (last
!= start
) ss
->last
= last
;
2158 (ss
= ss
->next
= mail_newsearchset ())->first
= i
;
2159 start
= last
= i
; /* begin a new range */
2162 else { /* first time, start new searchpgm */
2163 (tsp
= mail_newsearchpgm ())->msgno
= ss
= mail_newsearchset ();
2164 ss
->first
= start
= last
= i
;
2167 /* nothing to sort if no messages */
2168 if (!(aspg
.text
= (void *) tsp
)) return NIL
;
2169 /* else install last sequence */
2170 if (last
!= start
) ss
->last
= last
;
2173 args
[0] = &apgm
; args
[1] = &achs
; args
[2] = &aspg
; args
[3] = NIL
;
2174 /* ask server to do it */
2175 reply
= imap_send (stream
,cmd
,args
);
2176 if (tsp
) { /* was there a temporary searchpgm? */
2177 aspg
.text
= NIL
; /* yes, flush it */
2178 mail_free_searchpgm (&tsp
);
2179 /* did server barf with that searchpgm? */
2180 if (!(flags
& SE_UID
) && !strcmp (reply
->key
,"BAD")) {
2181 LOCAL
->filter
= T
; /* retry, filtering SORT/THREAD results */
2182 reply
= imap_send (stream
,cmd
,args
);
2183 LOCAL
->filter
= NIL
; /* turn off filtering */
2186 /* do locally if server barfs */
2187 if (!strcmp (reply
->key
,"BAD"))
2188 return (flags
& SE_NOLOCAL
) ? NIL
:
2189 imap_sort (stream
,charset
,spg
,pgm
,flags
| SE_NOSERVER
);
2190 /* server sorted OK? */
2191 else if (imap_OK (stream
,reply
)) {
2192 pgm
->nmsgs
= LOCAL
->sortsize
;
2193 ret
= LOCAL
->sortdata
;
2194 LOCAL
->sortdata
= NIL
; /* mail program is responsible for flushing */
2196 else mm_log (reply
->text
,ERROR
);
2199 /* not much can do if short caching */
2200 else if (stream
->scache
) ret
= mail_sort_msgs (stream
,charset
,spg
,pgm
,flags
);
2201 else { /* try to be a bit more clever */
2208 /* see if need envelopes */
2209 for (sp
= pgm
; sp
&& !ftflags
; sp
= sp
->next
) switch (sp
->function
) {
2210 case SORTDATE
: case SORTFROM
: case SORTSUBJECT
: case SORTTO
: case SORTCC
:
2211 ftflags
= FT_NEEDENV
+ ((flags
& SE_NOHDRS
) ? FT_NOHDRS
: NIL
);
2213 if (spg
) { /* only if a search needs to be done */
2214 int silent
= stream
->silent
;
2215 stream
->silent
= T
; /* don't pass up mm_searched() events */
2216 /* search for messages */
2217 mail_search_full (stream
,charset
,spg
,flags
& SE_NOSERVER
);
2218 stream
->silent
= silent
; /* restore silence state */
2220 /* initialize progress counters */
2221 pgm
->nmsgs
= pgm
->progress
.cached
= 0;
2222 /* pass 1: count messages to sort */
2223 for (i
= 1,len
= start
= last
= 0,s
= t
= NIL
; i
<= stream
->nmsgs
; ++i
)
2224 if ((elt
= mail_elt (stream
,i
))->searched
) {
2226 if (ftflags
? !elt
->private.msg
.env
: !elt
->day
) {
2227 if (s
) { /* continuing a sequence */
2228 if (i
== last
+ 1) last
= i
;
2229 else { /* end of range */
2230 if (last
!= start
) sprintf (t
,":%lu,%lu",last
,i
);
2231 else sprintf (t
,",%lu",i
);
2232 start
= last
= i
; /* begin a new range */
2233 if ((len
- (j
= ((t
+= strlen (t
)) - s
)) < 20)) {
2234 fs_resize ((void **) &s
,len
+= MAILTMPLEN
);
2235 t
= s
+ j
; /* relocate current pointer */
2239 else { /* first time, start new buffer */
2240 s
= (char *) fs_get (len
= MAILTMPLEN
);
2241 sprintf (s
,"%lu",start
= last
= i
);
2242 t
= s
+ strlen (s
); /* end of buffer */
2247 if (last
!= start
) sprintf (t
,":%lu",last
);
2248 if (s
) { /* load cache for all messages being sorted */
2249 imap_fetch (stream
,s
,ftflags
);
2250 fs_give ((void **) &s
);
2252 if (pgm
->nmsgs
) { /* pass 2: sort cache */
2253 sortresults_t sr
= (sortresults_t
)
2254 mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
2255 sc
= mail_sort_loadcache (stream
,pgm
);
2256 /* pass 3: sort messages */
2257 if (!pgm
->abort
) ret
= mail_sort_cache (stream
,pgm
,sc
,flags
);
2258 fs_give ((void **) &sc
); /* don't need sort vector any more */
2259 /* also return via callback if requested */
2260 if (sr
) (*sr
) (stream
,ret
,pgm
->nmsgs
);
2266 /* IMAP thread messages
2267 * Accepts: mail stream
2272 * Returns: thread node tree or NIL if error
2275 THREADNODE
*imap_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
2276 SEARCHPGM
*spg
,long flags
)
2279 if (!(flags
& SE_NOSERVER
) &&
2280 (!spg
|| (LEVELWITHIN (stream
) || !(spg
->older
|| spg
->younger
))))
2281 /* does server have this threader type? */
2282 for (thr
= LOCAL
->cap
.threader
; thr
; thr
= thr
->next
)
2283 if (!compare_cstring (thr
->name
,type
))
2284 return imap_thread_work (stream
,type
,charset
,spg
,flags
);
2285 /* server doesn't support it, do locally */
2286 return (flags
& SE_NOLOCAL
) ? NIL
:
2287 mail_thread_msgs (stream
,type
,charset
,spg
,flags
| SE_NOSERVER
,imap_sort
);
2290 /* IMAP thread messages worker routine
2291 * Accepts: mail stream
2296 * Returns: thread node tree
2299 THREADNODE
*imap_thread_work (MAILSTREAM
*stream
,char *type
,char *charset
,
2300 SEARCHPGM
*spg
,long flags
)
2302 unsigned long i
,start
,last
;
2303 char *cmd
= (flags
& SE_UID
) ? "UID THREAD" : "THREAD";
2304 IMAPARG
*args
[4],apgm
,achs
,aspg
;
2305 IMAPPARSEDREPLY
*reply
;
2306 THREADNODE
*ret
= NIL
;
2307 SEARCHSET
*ss
= NIL
;
2308 SEARCHPGM
*tsp
= NIL
;
2309 apgm
.type
= ATOM
; apgm
.text
= (void *) type
;
2310 achs
.type
= ASTRING
;
2311 achs
.text
= (void *) (charset
? charset
: "US-ASCII");
2312 aspg
.type
= SEARCHPROGRAM
;
2313 /* did he provide a searchpgm? */
2314 if (!(aspg
.text
= (void *) spg
)) {
2315 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
2316 if (mail_elt (stream
,i
)->searched
) {
2317 if (ss
) { /* continuing a sequence */
2318 if (i
== last
+ 1) last
= i
;
2319 else { /* end of range */
2320 if (last
!= start
) ss
->last
= last
;
2321 (ss
= ss
->next
= mail_newsearchset ())->first
= i
;
2322 start
= last
=i
; /* begin a new range */
2325 else { /* first time, start new searchpgm */
2326 (tsp
= mail_newsearchpgm ())->msgno
= ss
= mail_newsearchset ();
2327 ss
->first
= start
= last
= i
;
2330 /* nothing to sort if no messages */
2331 if (!(aspg
.text
= (void *) tsp
)) return NIL
;
2332 /* else install last sequence */
2333 if (last
!= start
) ss
->last
= last
;
2336 args
[0] = &apgm
; args
[1] = &achs
; args
[2] = &aspg
; args
[3] = NIL
;
2337 /* ask server to do it */
2338 reply
= imap_send (stream
,cmd
,args
);
2339 if (tsp
) { /* was there a temporary searchpgm? */
2340 aspg
.text
= NIL
; /* yes, flush it */
2341 mail_free_searchpgm (&tsp
);
2342 /* did server barf with that searchpgm? */
2343 if (!(flags
& SE_UID
) && !strcmp (reply
->key
,"BAD")) {
2344 LOCAL
->filter
= T
; /* retry, filtering SORT/THREAD results */
2345 reply
= imap_send (stream
,cmd
,args
);
2346 LOCAL
->filter
= NIL
; /* turn off filtering */
2349 /* do locally if server barfs */
2350 if (!strcmp (reply
->key
,"BAD"))
2351 ret
= (flags
& SE_NOLOCAL
) ? NIL
:
2352 mail_thread_msgs (stream
,type
,charset
,spg
,flags
| SE_NOSERVER
,imap_sort
);
2353 /* server threaded OK? */
2354 else if (imap_OK (stream
,reply
)) {
2355 ret
= LOCAL
->threaddata
;
2356 LOCAL
->threaddata
= NIL
; /* mail program is responsible for flushing */
2358 else mm_log (reply
->text
,ERROR
);
2362 /* IMAP ping mailbox
2363 * Accepts: MAIL stream
2364 * Returns: T if stream still alive, else NIL
2367 long imap_ping (MAILSTREAM
*stream
)
2369 return (LOCAL
->netstream
&& /* send "NOOP" */
2370 imap_OK (stream
,imap_send (stream
,"NOOP",NIL
))) ? T
: NIL
;
2374 /* IMAP check mailbox
2375 * Accepts: MAIL stream
2378 void imap_check (MAILSTREAM
*stream
)
2381 IMAPPARSEDREPLY
*reply
= imap_send (stream
,"CHECK",NIL
);
2382 mm_log (reply
->text
,imap_OK (stream
,reply
) ? (long) NIL
: ERROR
);
2385 /* IMAP expunge mailbox
2386 * Accepts: MAIL stream
2387 * sequence to expunge if non-NIL
2389 * Returns: T if success, NIL if failure
2392 long imap_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
2395 IMAPPARSEDREPLY
*reply
= NIL
;
2396 if (sequence
) { /* wants selective expunging? */
2397 if (options
& EX_UID
) { /* UID EXPUNGE form? */
2398 if (LEVELUIDPLUS (stream
)) {/* server support UIDPLUS? */
2399 IMAPARG
*args
[2],aseq
;
2400 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
2401 args
[0] = &aseq
; args
[1] = NIL
;
2402 ret
= imap_OK (stream
,reply
= imap_send (stream
,"UID EXPUNGE",args
));
2404 else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR
);
2406 /* otherwise try to make into UID EXPUNGE */
2407 else if (mail_sequence (stream
,sequence
)) {
2409 char *t
= (char *) fs_get (IMAPTMPLEN
);
2411 /* search through mailbox */
2412 for (*s
= '\0', i
= 1; i
<= stream
->nmsgs
; ++i
)
2413 if (mail_elt (stream
,i
)->sequence
) {
2414 if (t
[0]) *s
++ = ','; /* prepend with comma if not first time */
2415 sprintf (s
,"%lu",mail_uid (stream
,j
= i
));
2416 s
+= strlen (s
); /* point at end of string */
2417 /* search for possible end of range */
2418 while ((i
< stream
->nmsgs
) && mail_elt (stream
,i
+1)->sequence
) i
++;
2419 if (i
!= j
) { /* output end of range */
2420 sprintf (s
,":%lu",mail_uid (stream
,i
));
2421 s
+= strlen (s
); /* point at end of string */
2423 if ((s
- t
) > (IMAPTMPLEN
- 50)) {
2424 mm_log ("Excessively complex sequence",ERROR
);
2428 /* now do as UID EXPUNGE */
2429 ret
= imap_expunge (stream
,t
,EX_UID
);
2430 fs_give ((void **) &t
);
2433 /* ordinary EXPUNGE */
2434 else ret
= imap_OK (stream
,reply
= imap_send (stream
,"EXPUNGE",NIL
));
2435 if (reply
) mm_log (reply
->text
,ret
? (long) NIL
: ERROR
);
2439 /* IMAP copy message(s)
2440 * Accepts: MAIL stream
2442 * destination mailbox
2444 * Returns: T if successful else NIL
2447 long imap_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long flags
)
2449 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& CP_UID
)) ? "UID COPY" : "COPY";
2452 IMAPPARSEDREPLY
*reply
;
2453 IMAPARG
*args
[3],aseq
,ambx
;
2455 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
2456 mailproxycopy_t pc
=
2457 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
2458 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
2460 aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
2461 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2462 args
[0] = &aseq
; args
[1] = &ambx
; args
[2] = NIL
;
2463 /* note mailbox in case APPENDUID */
2464 LOCAL
->appendmailbox
= mailbox
;
2465 /* send "COPY sequence mailbox" */
2466 ret
= imap_OK (stream
,reply
= imap_send (stream
,cmd
,args
));
2467 LOCAL
->appendmailbox
= NIL
; /* no longer appending */
2468 if (ret
) { /* success, delete messages if move */
2469 if (flags
& CP_MOVE
) imap_flag (stream
,sequence
,"\\Deleted",
2470 ST_SET
+ ((flags
&CP_UID
) ? ST_UID
: NIL
));
2472 /* failed, do referral action if any */
2473 else if (ir
&& pc
&& LOCAL
->referral
&& mail_sequence (stream
,sequence
) &&
2474 (s
= (*ir
) (stream
,LOCAL
->referral
,REFCOPY
)))
2475 ret
= (*pc
) (stream
,sequence
,s
,flags
| (stream
->debug
? CP_DEBUG
: NIL
));
2476 /* otherwise issue error message */
2477 else mm_log (reply
->text
,ERROR
);
2481 /* IMAP mail append message from stringstruct
2482 * Accepts: MAIL stream
2483 * destination mailbox
2486 * Returns: T if append successful, else NIL
2489 long imap_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
2491 MAILSTREAM
*st
= stream
;
2492 IMAPARG
*args
[3],ambx
,amap
;
2493 IMAPPARSEDREPLY
*reply
= NIL
;
2495 char tmp
[MAILTMPLEN
];
2496 long debug
= stream
? stream
->debug
: NIL
;
2499 (imapreferral_t
) mail_parameters (stream
,GET_IMAPREFERRAL
,NIL
);
2500 /* mailbox must be good */
2501 if (mail_valid_net (mailbox
,&imapdriver
,NIL
,tmp
)) {
2502 /* create a stream if given one no good */
2503 if ((stream
&& LOCAL
&& LOCAL
->netstream
) ||
2504 (stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
|
2505 (debug
? OP_DEBUG
: NIL
)))) {
2506 /* note mailbox in case APPENDUID */
2507 LOCAL
->appendmailbox
= mailbox
;
2508 /* use multi-append? */
2509 if (LEVELMULTIAPPEND (stream
)) {
2510 ambx
.type
= ASTRING
; ambx
.text
= (void *) tmp
;
2511 amap
.type
= MULTIAPPEND
; amap
.text
= (void *) &map
;
2512 map
.af
= af
; map
.data
= data
;
2513 args
[0] = &ambx
; args
[1] = &amap
; args
[2] = NIL
;
2515 ret
= imap_OK (stream
,reply
= imap_send (stream
,"APPEND",args
));
2516 LOCAL
->appendmailbox
= NIL
;
2518 /* do succession of single appends */
2519 else while ((*af
) (stream
,data
,&map
.flags
,&map
.date
,&map
.message
) &&
2521 (ret
= imap_OK (stream
,reply
=
2522 imap_append_single (stream
,tmp
,map
.flags
,
2523 map
.date
,map
.message
))));
2524 LOCAL
->appendmailbox
= NIL
;
2525 /* don't do referrals if success or no reply */
2526 if (ret
|| !reply
) mailbox
= NIL
;
2527 /* otherwise generate referral */
2528 else if (!(mailbox
= (ir
&& LOCAL
->referral
) ?
2529 (*ir
) (stream
,LOCAL
->referral
,REFAPPEND
) : NIL
))
2530 mm_log (reply
->text
,ERROR
);
2531 /* close temporary stream */
2532 if (st
!= stream
) stream
= mail_close (stream
);
2533 if (mailbox
) /* chase referral if any */
2534 ret
= imap_append_referral (mailbox
,tmp
,af
,data
,map
.flags
,map
.date
,
2535 map
.message
,&map
,debug
);
2537 else mm_log ("Can't access server for append",ERROR
);
2539 return ret
; /* return */
2542 /* IMAP mail append message referral retry
2543 * Accepts: destination mailbox
2547 * flags from previous attempt
2548 * date from previous attempt
2549 * message stringstruct from previous attempt
2550 * options (currently non-zero to set OP_DEBUG)
2551 * Returns: T if append successful, else NIL
2554 long imap_append_referral (char *mailbox
,char *tmp
,append_t af
,void *data
,
2555 char *flags
,char *date
,STRING
*message
,
2556 APPENDDATA
*map
,long options
)
2559 IMAPARG
*args
[3],ambx
,amap
;
2560 IMAPPARSEDREPLY
*reply
;
2562 (imapreferral_t
) mail_parameters (NIL
,GET_IMAPREFERRAL
,NIL
);
2563 /* barf if bad mailbox */
2564 while (mailbox
&& mail_valid_net (mailbox
,&imapdriver
,NIL
,tmp
)) {
2565 /* create a stream if given one no good */
2566 if (!(stream
= mail_open (NIL
,mailbox
,OP_HALFOPEN
|OP_SILENT
|
2567 (options
? OP_DEBUG
: NIL
)))) {
2568 sprintf (tmp
,"Can't access referral server: %.80s",mailbox
);
2572 /* got referral server, use multi-append? */
2573 if (LEVELMULTIAPPEND (stream
)) {
2574 ambx
.type
= ASTRING
; ambx
.text
= (void *) tmp
;
2575 amap
.type
= MULTIAPPENDREDO
; amap
.text
= (void *) map
;
2576 args
[0] = &ambx
; args
[1] = &amap
; args
[2] = NIL
;
2577 /* do multiappend on referral site */
2578 if (imap_OK (stream
,reply
= imap_send (stream
,"APPEND",args
))) {
2579 mail_close (stream
); /* multiappend OK, close stream */
2580 return LONGT
; /* all done */
2583 /* do multiple single appends */
2584 else while (imap_OK (stream
,reply
=
2585 imap_append_single (stream
,tmp
,flags
,date
,message
)))
2586 if (!((*af
) (stream
,data
,&flags
,&date
,&message
) && message
)) {
2587 mail_close (stream
); /* last message, close stream */
2588 return LONGT
; /* all done */
2590 /* generate error if no nested referral */
2591 if (!(mailbox
= (ir
&& LOCAL
->referral
) ?
2592 (*ir
) (stream
,LOCAL
->referral
,REFAPPEND
) : NIL
))
2593 mm_log (reply
->text
,ERROR
);
2594 mail_close (stream
); /* close previous referral stream */
2596 return NIL
; /* bogus mailbox */
2599 /* IMAP append single message
2600 * Accepts: mail stream
2601 * destination mailbox
2604 * stringstruct of message to append
2605 * Returns: reply from append
2608 IMAPPARSEDREPLY
*imap_append_single (MAILSTREAM
*stream
,char *mailbox
,
2609 char *flags
,char *date
,STRING
*message
)
2612 IMAPARG
*args
[5],ambx
,aflg
,adat
,amsg
;
2613 IMAPPARSEDREPLY
*reply
;
2614 char tmp
[MAILTMPLEN
];
2616 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2617 args
[i
= 0] = &ambx
;
2619 aflg
.type
= FLAGS
; aflg
.text
= (void *) flags
;
2622 if (date
) { /* ensure date in INTERNALDATE format */
2623 if (!mail_parse_date (&elt
,date
)) {
2624 /* flush previous reply */
2625 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
2626 /* build new fake reply */
2627 LOCAL
->reply
.tag
= LOCAL
->reply
.line
= cpystr ("*");
2628 LOCAL
->reply
.key
= "BAD";
2629 LOCAL
->reply
.text
= "Bad date in append";
2630 return &LOCAL
->reply
;
2632 adat
.type
= ASTRING
;
2633 adat
.text
= (void *) (date
= mail_date (tmp
,&elt
));
2636 amsg
.type
= LITERAL
; amsg
.text
= (void *) message
;
2639 /* easy if IMAP4[rev1] */
2640 if (LEVELIMAP4 (stream
)) reply
= imap_send (stream
,"APPEND",args
);
2641 else { /* try the IMAP2bis way */
2642 args
[1] = &amsg
; args
[2] = NIL
;
2643 reply
= imap_send (stream
,"APPEND",args
);
2648 /* IMAP garbage collect stream
2649 * Accepts: Mail stream
2650 * garbage collection flags
2653 void imap_gc (MAILSTREAM
*stream
,long gcflags
)
2657 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2658 /* make sure the cache is large enough */
2659 (*mc
) (stream
,stream
->nmsgs
,CH_SIZE
);
2660 if (gcflags
& GC_TEXTS
) { /* garbage collect texts? */
2661 if (!stream
->scache
) for (i
= 1; i
<= stream
->nmsgs
; ++i
)
2662 if (elt
= (MESSAGECACHE
*) (*mc
) (stream
,i
,CH_ELT
))
2663 imap_gc_body (elt
->private.msg
.body
);
2664 imap_gc_body (stream
->body
);
2666 /* gc cache if requested and unlocked */
2667 if (gcflags
& GC_ELT
) for (i
= 1; i
<= stream
->nmsgs
; ++i
)
2668 if ((elt
= (MESSAGECACHE
*) (*mc
) (stream
,i
,CH_ELT
)) &&
2669 (elt
->lockcount
== 1)) (*mc
) (stream
,i
,CH_FREE
);
2672 /* IMAP garbage collect body texts
2673 * Accepts: body to GC
2676 void imap_gc_body (BODY
*body
)
2679 if (body
) { /* have a body? */
2680 if (body
->mime
.text
.data
) /* flush MIME data */
2681 fs_give ((void **) &body
->mime
.text
.data
);
2682 /* flush text contents */
2683 if (body
->contents
.text
.data
)
2684 fs_give ((void **) &body
->contents
.text
.data
);
2685 body
->mime
.text
.size
= body
->contents
.text
.size
= 0;
2687 if (body
->type
== TYPEMULTIPART
)
2688 for (part
= body
->nested
.part
; part
; part
= part
->next
)
2689 imap_gc_body (&part
->body
);
2690 /* MESSAGE/RFC822? */
2691 else if ((body
->type
== TYPEMESSAGE
) && !strcmp (body
->subtype
,"RFC822")) {
2692 imap_gc_body (body
->nested
.msg
->body
);
2693 if (body
->nested
.msg
->full
.text
.data
)
2694 fs_give ((void **) &body
->nested
.msg
->full
.text
.data
);
2695 if (body
->nested
.msg
->header
.text
.data
)
2696 fs_give ((void **) &body
->nested
.msg
->header
.text
.data
);
2697 if (body
->nested
.msg
->text
.text
.data
)
2698 fs_give ((void **) &body
->nested
.msg
->text
.text
.data
);
2699 body
->nested
.msg
->full
.text
.size
= body
->nested
.msg
->header
.text
.size
=
2700 body
->nested
.msg
->text
.text
.size
= 0;
2705 /* IMAP get capabilities
2706 * Accepts: mail stream
2709 void imap_capability (MAILSTREAM
*stream
)
2712 LOCAL
->gotcapability
= NIL
; /* flush any previous capabilities */
2713 /* request new capabilities */
2714 imap_send (stream
,"CAPABILITY",NIL
);
2715 if (!LOCAL
->gotcapability
) { /* did server get any? */
2716 /* no, flush threaders just in case */
2717 if (thr
= LOCAL
->cap
.threader
) while (t
= thr
) {
2718 fs_give ((void **) &t
->name
);
2720 fs_give ((void **) &t
);
2722 /* zap most capabilities */
2723 memset (&LOCAL
->cap
,0,sizeof (LOCAL
->cap
));
2724 /* assume IMAP2bis server if failure */
2725 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
2730 * Accepts: mail stream
2732 * authentication identifer
2734 * Returns: T on success, NIL on failure
2737 long imap_setacl (MAILSTREAM
*stream
,char *mailbox
,char *id
,char *rights
)
2739 IMAPARG
*args
[4],ambx
,aid
,art
;
2740 ambx
.type
= aid
.type
= art
.type
= ASTRING
;
2741 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2742 art
.text
= (void *) rights
;
2743 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = &art
; args
[3] = NIL
;
2744 return imap_acl_work (stream
,"SETACL",args
);
2749 * Accepts: mail stream
2751 * authentication identifer
2752 * Returns: T on success, NIL on failure
2755 long imap_deleteacl (MAILSTREAM
*stream
,char *mailbox
,char *id
)
2757 IMAPARG
*args
[3],ambx
,aid
;
2758 ambx
.type
= aid
.type
= ASTRING
;
2759 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2760 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = NIL
;
2761 return imap_acl_work (stream
,"DELETEACL",args
);
2766 * Accepts: mail stream
2768 * Returns: T on success with data returned via callback, NIL on failure
2771 long imap_getacl (MAILSTREAM
*stream
,char *mailbox
)
2773 IMAPARG
*args
[2],ambx
;
2774 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2775 args
[0] = &ambx
; args
[1] = NIL
;
2776 return imap_acl_work (stream
,"GETACL",args
);
2780 * Accepts: mail stream
2782 * authentication identifer
2783 * Returns: T on success with data returned via callback, NIL on failure
2786 long imap_listrights (MAILSTREAM
*stream
,char *mailbox
,char *id
)
2788 IMAPARG
*args
[3],ambx
,aid
;
2789 ambx
.type
= aid
.type
= ASTRING
;
2790 ambx
.text
= (void *) mailbox
; aid
.text
= (void *) id
;
2791 args
[0] = &ambx
; args
[1] = &aid
; args
[2] = NIL
;
2792 return imap_acl_work (stream
,"LISTRIGHTS",args
);
2797 * Accepts: mail stream
2799 * Returns: T on success with data returned via callback, NIL on failure
2802 long imap_myrights (MAILSTREAM
*stream
,char *mailbox
)
2804 IMAPARG
*args
[2],ambx
;
2805 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2806 args
[0] = &ambx
; args
[1] = NIL
;
2807 return imap_acl_work (stream
,"MYRIGHTS",args
);
2811 /* IMAP ACL worker routine
2812 * Accepts: mail stream
2815 * Returns: T on success, NIL on failure
2818 long imap_acl_work (MAILSTREAM
*stream
,char *command
,IMAPARG
*args
[])
2821 if (LEVELACL (stream
)) { /* send command */
2822 IMAPPARSEDREPLY
*reply
;
2823 if (imap_OK (stream
,reply
= imap_send (stream
,command
,args
)))
2825 else mm_log (reply
->text
,ERROR
);
2827 else mm_log ("ACL not available on this IMAP server",ERROR
);
2832 * Accepts: mail stream
2834 * resource limit list as a stringlist
2835 * Returns: T on success with data returned via callback, NIL on failure
2838 long imap_setquota (MAILSTREAM
*stream
,char *qroot
,STRINGLIST
*limits
)
2841 if (LEVELQUOTA (stream
)) { /* send "SETQUOTA" */
2842 IMAPPARSEDREPLY
*reply
;
2843 IMAPARG
*args
[3],aqrt
,alim
;
2844 aqrt
.type
= ASTRING
; aqrt
.text
= (void *) qroot
;
2845 alim
.type
= SNLIST
; alim
.text
= (void *) limits
;
2846 args
[0] = &aqrt
; args
[1] = &alim
; args
[2] = NIL
;
2847 if (imap_OK (stream
,reply
= imap_send (stream
,"SETQUOTA",args
)))
2849 else mm_log (reply
->text
,ERROR
);
2851 else mm_log ("Quota not available on this IMAP server",ERROR
);
2856 * Accepts: mail stream
2858 * Returns: T on success with data returned via callback, NIL on failure
2861 long imap_getquota (MAILSTREAM
*stream
,char *qroot
)
2864 if (LEVELQUOTA (stream
)) { /* send "GETQUOTA" */
2865 IMAPPARSEDREPLY
*reply
;
2866 IMAPARG
*args
[2],aqrt
;
2867 aqrt
.type
= ASTRING
; aqrt
.text
= (void *) qroot
;
2868 args
[0] = &aqrt
; args
[1] = NIL
;
2869 if (imap_OK (stream
,reply
= imap_send (stream
,"GETQUOTA",args
)))
2871 else mm_log (reply
->text
,ERROR
);
2873 else mm_log ("Quota not available on this IMAP server",ERROR
);
2878 /* IMAP get quota root
2879 * Accepts: mail stream
2881 * Returns: T on success with data returned via callback, NIL on failure
2884 long imap_getquotaroot (MAILSTREAM
*stream
,char *mailbox
)
2887 if (LEVELQUOTA (stream
)) { /* send "GETQUOTAROOT" */
2888 IMAPPARSEDREPLY
*reply
;
2889 IMAPARG
*args
[2],ambx
;
2890 ambx
.type
= ASTRING
; ambx
.text
= (void *) mailbox
;
2891 args
[0] = &ambx
; args
[1] = NIL
;
2892 if (imap_OK (stream
,reply
= imap_send (stream
,"GETQUOTAROOT",args
)))
2894 else mm_log (reply
->text
,ERROR
);
2896 else mm_log ("Quota not available on this IMAP server",ERROR
);
2900 /* Internal routines */
2903 /* IMAP send command
2904 * Accepts: MAIL stream
2907 * Returns: parsed reply
2910 #define CMDBASE LOCAL->tmp /* command base */
2912 IMAPPARSEDREPLY
*imap_send (MAILSTREAM
*stream
,char *cmd
,IMAPARG
*args
[])
2914 IMAPPARSEDREPLY
*reply
;
2915 IMAPARG
*arg
,**arglst
;
2920 sendcommand_t sc
= (sendcommand_t
) mail_parameters (NIL
,GET_SENDCOMMAND
,NIL
);
2923 char c
,*s
,*t
,tag
[10];
2924 stream
->unhealthy
= NIL
; /* make stream healthy again */
2925 /* gensym a new tag */
2926 sprintf (tag
,"%08lx",0xffffffff & (stream
->gensym
++));
2927 if (!LOCAL
->netstream
) /* make sure have a session */
2928 return imap_fake (stream
,tag
,"[CLOSED] IMAP connection lost");
2929 mail_lock (stream
); /* lock up the stream */
2930 if (sc
) /* tell client sending a command */
2931 (*sc
) (stream
,cmd
,((compare_cstring (cmd
,"FETCH") &&
2932 compare_cstring (cmd
,"STORE") &&
2933 compare_cstring (cmd
,"SEARCH")) ?
2934 NIL
: SC_EXPUNGEDEFERRED
));
2935 /* ignore referral from previous command */
2936 if (LOCAL
->referral
) fs_give ((void **) &LOCAL
->referral
);
2937 sprintf (CMDBASE
,"%s %s",tag
,cmd
);
2938 s
= CMDBASE
+ strlen (CMDBASE
);
2939 if (arglst
= args
) while (arg
= *arglst
++) {
2940 *s
++ = ' '; /* delimit argument with space */
2941 switch (arg
->type
) {
2942 case ATOM
: /* atom */
2943 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
2945 case NUMBER
: /* number */
2946 sprintf (s
,"%lu",(unsigned long) arg
->text
);
2949 case FLAGS
: /* flag list as a single string */
2950 if (*(t
= (char *) arg
->text
) != '(') {
2951 *s
++ = '('; /* wrap parens around string */
2952 while (*t
) *s
++ = *t
++;
2953 *s
++ = ')'; /* wrap parens around string */
2955 else while (*t
) *s
++ = *t
++;
2957 case ASTRING
: /* atom or string, must be literal? */
2958 st
.size
= strlen ((char *) (st
.data
= (unsigned char *) arg
->text
));
2959 if (reply
= imap_send_astring (stream
,tag
,&s
,&st
,NIL
,CMDBASE
+MAXCOMMAND
))
2962 case LITERAL
: /* literal, as a stringstruct */
2963 if (reply
= imap_send_literal (stream
,tag
,&s
,arg
->text
)) return reply
;
2966 case LIST
: /* list of strings */
2967 list
= (STRINGLIST
*) arg
->text
;
2968 c
= '('; /* open paren */
2969 do { /* for each list item */
2970 *s
++ = c
; /* write prefix character */
2971 if (reply
= imap_send_astring (stream
,tag
,&s
,&list
->text
,NIL
,
2972 CMDBASE
+MAXCOMMAND
)) return reply
;
2973 c
= ' '; /* prefix character for subsequent strings */
2975 while (list
= list
->next
);
2976 *s
++ = ')'; /* close list */
2978 case SEARCHPROGRAM
: /* search program */
2979 if (reply
= imap_send_spgm (stream
,tag
,CMDBASE
,&s
,arg
->text
,
2980 CMDBASE
+MAXCOMMAND
))
2983 case SORTPROGRAM
: /* search program */
2984 c
= '('; /* open paren */
2985 for (spg
= (SORTPGM
*) arg
->text
; spg
; spg
= spg
->next
) {
2986 *s
++ = c
; /* write prefix */
2987 if (spg
->reverse
) for (t
= "REVERSE "; *t
; *s
++ = *t
++);
2988 switch (spg
->function
) {
2990 for (t
= "DATE"; *t
; *s
++ = *t
++);
2993 for (t
= "ARRIVAL"; *t
; *s
++ = *t
++);
2996 for (t
= "FROM"; *t
; *s
++ = *t
++);
2999 for (t
= "SUBJECT"; *t
; *s
++ = *t
++);
3002 for (t
= "TO"; *t
; *s
++ = *t
++);
3005 for (t
= "CC"; *t
; *s
++ = *t
++);
3008 for (t
= "SIZE"; *t
; *s
++ = *t
++);
3011 fatal ("Unknown sort program function in imap_send()!");
3013 c
= ' '; /* prefix character for subsequent items */
3015 *s
++ = ')'; /* close list */
3018 case BODYTEXT
: /* body section */
3019 for (t
= "BODY["; *t
; *s
++ = *t
++);
3020 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3022 case BODYPEEK
: /* body section */
3023 for (t
= "BODY.PEEK["; *t
; *s
++ = *t
++);
3024 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3026 case BODYCLOSE
: /* close bracket and possible length */
3027 s
[-1] = ']'; /* no leading space */
3028 for (t
= (char *) arg
->text
; *t
; *s
++ = *t
++);
3030 case SEQUENCE
: /* sequence */
3031 if ((i
= strlen (t
= (char *) arg
->text
)) <= (size_t) MAXCOMMAND
)
3032 while (*t
) *s
++ = *t
++; /* easy case */
3034 mail_unlock (stream
); /* unlock stream */
3035 a
= arg
->text
; /* save original sequence pointer */
3036 arg
->type
= ATOM
; /* make recursive call be faster */
3037 do { /* break up into multiple commands */
3038 if (i
<= MAXCOMMAND
) {/* final part? */
3039 reply
= imap_send (stream
,cmd
,args
);
3040 i
= 0; /* and mark as done */
3042 else { /* still needs to be split further */
3043 if (!(t
= strchr (t
+ MAXCOMMAND
- 30,',')) ||
3044 ((t
- (char *) arg
->text
) > MAXCOMMAND
))
3045 fatal ("impossible over-long sequence");
3046 *t
= '\0'; /* tie off sequence at point of split*/
3047 /* recurse to do this part */
3048 reply
= imap_send (stream
,cmd
,args
);
3049 *t
++ = ','; /* restore the comma in case something cares */
3051 if (!imap_OK (stream
,reply
)) break;
3052 /* calculate size of remaining sequence */
3053 i
-= (t
- (char *) arg
->text
);
3054 /* point to new remaining sequence */
3055 arg
->text
= (void *) t
;
3058 arg
->type
= SEQUENCE
; /* restore in case something cares */
3060 return reply
; /* return result */
3063 case LISTMAILBOX
: /* astring with wildcards */
3064 st
.size
= strlen ((char *) (st
.data
= (unsigned char *) arg
->text
));
3065 if (reply
= imap_send_astring (stream
,tag
,&s
,&st
,T
,CMDBASE
+MAXCOMMAND
))
3069 case MULTIAPPEND
: /* append multiple messages */
3070 /* get package pointer */
3071 map
= (APPENDDATA
*) arg
->text
;
3072 if (!(*map
->af
) (stream
,map
->data
,&map
->flags
,&map
->date
,&map
->message
)||
3075 INIT (&es
,mail_string
,"",0);
3076 return (reply
= imap_send_literal (stream
,tag
,&s
,&es
)) ?
3077 reply
: imap_fake (stream
,tag
,"Server zero-length literal error");
3079 case MULTIAPPENDREDO
: /* redo multiappend */
3080 /* get package pointer */
3081 map
= (APPENDDATA
*) arg
->text
;
3082 do { /* make sure date valid if given */
3083 char datetmp
[MAILTMPLEN
];
3086 if (!map
->date
|| mail_parse_date (&elt
,map
->date
)) {
3087 if (t
= map
->flags
) { /* flags given? */
3089 *s
++ = '('; /* wrap parens around string */
3090 while (*t
) *s
++ = *t
++;
3091 *s
++ = ')'; /* wrap parens around string */
3093 else while (*t
) *s
++ = *t
++;
3094 *s
++ = ' '; /* delimit with space */
3096 if (map
->date
) { /* date given? */
3097 st
.size
= strlen ((char *) (st
.data
= (unsigned char *)
3098 mail_date (datetmp
,&elt
)));
3099 if (reply
= imap_send_astring (stream
,tag
,&s
,&st
,NIL
,
3100 CMDBASE
+MAXCOMMAND
)) return reply
;
3101 *s
++ = ' '; /* delimit with space */
3103 if (reply
= imap_send_literal (stream
,tag
,&s
,map
->message
))
3105 /* get next message */
3106 if ((*map
->af
) (stream
,map
->data
,&map
->flags
,&map
->date
,
3108 /* have a message, delete next in command */
3109 if (map
->message
) *s
++ = ' ';
3110 continue; /* loop back for next message */
3113 /* bad date or need to abort */
3114 INIT (&es
,mail_string
,"",0);
3115 return (reply
= imap_send_literal (stream
,tag
,&s
,&es
)) ?
3116 reply
: imap_fake (stream
,tag
,"Server zero-length literal error");
3117 break; /* exit the loop */
3118 } while (map
->message
);
3121 case SNLIST
: /* list of string/number pairs */
3122 list
= (STRINGLIST
*) arg
->text
;
3123 c
= '('; /* open paren */
3124 do { /* for each list item */
3125 *s
++ = c
; /* write prefix character */
3126 if (list
) { /* sigh, QUOTA has bizarre syntax! */
3127 for (t
= (char *) list
->text
.data
; *t
; *s
++ = *t
++);
3128 sprintf (s
," %lu",list
->text
.size
);
3130 c
= ' '; /* prefix character for subsequent strings */
3133 while (list
= list
->next
);
3134 *s
++ = ')'; /* close list */
3137 fatal ("Unknown argument type in imap_send()!");
3140 /* send the command */
3141 reply
= imap_sout (stream
,tag
,CMDBASE
,&s
);
3142 mail_unlock (stream
); /* unlock stream */
3146 /* IMAP send atom-string
3147 * Accepts: MAIL stream
3149 * pointer to current position pointer of output bigbuf
3150 * atom-string to output
3151 * flag if list_wildcards allowed
3152 * maximum to write as atom or qstring
3153 * Returns: error reply or NIL if success
3156 IMAPPARSEDREPLY
*imap_send_astring (MAILSTREAM
*stream
,char *tag
,char **s
,
3157 SIZEDTEXT
*as
,long wildok
,char *limit
)
3162 /* default to atom unless empty or loser */
3163 int qflag
= (as
->size
&& !LOCAL
->loser
) ? NIL
: T
;
3164 /* in case needed */
3165 INIT (&st
,mail_string
,(void *) as
->data
,as
->size
);
3166 /* always write literal if no space */
3167 if ((*s
+ as
->size
) > limit
) return imap_send_literal (stream
,tag
,s
,&st
);
3168 for (j
= 0; j
< as
->size
; j
++) switch (c
= as
->data
[j
]) {
3169 default: /* all other characters */
3170 if (!(c
& 0x80)) { /* must not be 8bit */
3171 if (c
<= ' ') qflag
= T
; /* must quote if a CTL */
3174 case '\0': /* not a CHAR */
3175 case '\012': case '\015': /* not a TEXT-CHAR */
3176 case '"': case '\\': /* quoted-specials (IMAP2 required this) */
3177 return imap_send_literal (stream
,tag
,s
,&st
);
3178 case '*': case '%': /* list_wildcards */
3179 if (wildok
) break; /* allowed if doing the wild thing */
3181 case '(': case ')': case '{': case ' ': case 0x7f:
3183 case '"': case '\\': /* quoted-specials (could work in IMAP4) */
3185 qflag
= T
; /* must use quoted string format */
3188 if (qflag
) *(*s
)++ = '"'; /* write open quote */
3189 for (j
= 0; j
< as
->size
; j
++) *(*s
)++ = as
->data
[j
];
3190 if (qflag
) *(*s
)++ = '"'; /* write close quote */
3194 /* IMAP send literal
3195 * Accepts: MAIL stream
3197 * pointer to current position pointer of output bigbuf
3198 * literal to output as stringstruct
3199 * Returns: error reply or NIL if success
3202 IMAPPARSEDREPLY
*imap_send_literal (MAILSTREAM
*stream
,char *tag
,char **s
,
3205 IMAPPARSEDREPLY
*reply
;
3206 unsigned long i
= SIZE (st
);
3208 sprintf (*s
,"{%lu}",i
); /* write literal count */
3209 *s
+= strlen (*s
); /* size of literal count */
3210 /* send the command */
3211 reply
= imap_sout (stream
,tag
,CMDBASE
,s
);
3212 if (strcmp (reply
->tag
,"+")) {/* prompt for more data? */
3213 mail_unlock (stream
); /* no, give up */
3216 while (i
) { /* dump the text */
3217 if (st
->cursize
) { /* if text to do in this chunk */
3218 /* RFC 3501 technically forbids NULs in literals. Normally, the
3219 * delivering MTA would take care of MIME converting the message text
3220 * so that it is NUL-free. If it doesn't, then we have the choice of
3221 * either violating IMAP by sending NULs, corrupting the data, or going
3222 * to lots of work to do MIME conversion in the IMAP server.
3224 * No current stringstruct driver objects to having its buffer patched.
3225 * If this ever changes, it will be necessary to change this kludge.
3227 /* patch NULs to C1 control */
3228 for (j
= 0; j
< st
->cursize
; ++j
)
3229 if (!st
->curpos
[j
]) st
->curpos
[j
] = 0x80;
3230 if (!net_sout (LOCAL
->netstream
,st
->curpos
,st
->cursize
)) {
3231 mail_unlock (stream
);
3232 return imap_fake (stream
,tag
,"[CLOSED] IMAP connection broken (data)");
3234 i
-= st
->cursize
; /* note that we wrote out this much */
3235 st
->curpos
+= (st
->cursize
- 1);
3238 (*st
->dtb
->next
) (st
); /* advance to next buffer's worth */
3240 return NIL
; /* success */
3243 /* IMAP send search program
3244 * Accepts: MAIL stream
3246 * base pointer if trimming needed
3247 * pointer to current position pointer of output bigbuf
3248 * search program to output
3249 * pointer to limit guideline
3250 * Returns: error reply or NIL if success
3254 IMAPPARSEDREPLY
*imap_send_spgm (MAILSTREAM
*stream
,char *tag
,char *base
,
3255 char **s
,SEARCHPGM
*pgm
,char *limit
)
3257 IMAPPARSEDREPLY
*reply
;
3262 /* trim if called recursively */
3263 if (base
) *s
= imap_send_spgm_trim (base
,*s
,NIL
);
3264 base
= *s
; /* this is the new base */
3265 /* default searchpgm */
3266 for (t
= "ALL"; *t
; *(*s
)++ = *t
++);
3267 if (!pgm
) return NIL
; /* done if NIL searchpgm */
3268 if ((pgm
->msgno
&& /* message sequences */
3269 (pgm
->msgno
->next
|| /* trim away first:last */
3270 (pgm
->msgno
->first
!= 1) || (pgm
->msgno
->last
!= stream
->nmsgs
)) &&
3271 (reply
= imap_send_sset (stream
,tag
,base
,s
,pgm
->msgno
," ",limit
))) ||
3273 (reply
= imap_send_sset (stream
,tag
,base
,s
,pgm
->uid
," UID ",limit
))))
3277 sprintf (*s
," LARGER %lu",pgm
->larger
);
3281 sprintf (*s
," SMALLER %lu",pgm
->smaller
);
3286 if (pgm
->answered
) for (t
= " ANSWERED"; *t
; *(*s
)++ = *t
++);
3287 if (pgm
->unanswered
) for (t
=" UNANSWERED"; *t
; *(*s
)++ = *t
++);
3288 if (pgm
->deleted
) for (t
=" DELETED"; *t
; *(*s
)++ = *t
++);
3289 if (pgm
->undeleted
) for (t
=" UNDELETED"; *t
; *(*s
)++ = *t
++);
3290 if (pgm
->draft
) for (t
=" DRAFT"; *t
; *(*s
)++ = *t
++);
3291 if (pgm
->undraft
) for (t
=" UNDRAFT"; *t
; *(*s
)++ = *t
++);
3292 if (pgm
->flagged
) for (t
=" FLAGGED"; *t
; *(*s
)++ = *t
++);
3293 if (pgm
->unflagged
) for (t
=" UNFLAGGED"; *t
; *(*s
)++ = *t
++);
3294 if (pgm
->recent
) for (t
=" RECENT"; *t
; *(*s
)++ = *t
++);
3295 if (pgm
->old
) for (t
=" OLD"; *t
; *(*s
)++ = *t
++);
3296 if (pgm
->seen
) for (t
=" SEEN"; *t
; *(*s
)++ = *t
++);
3297 if (pgm
->unseen
) for (t
=" UNSEEN"; *t
; *(*s
)++ = *t
++);
3298 if ((pgm
->keyword
&& /* keywords */
3299 (reply
= imap_send_slist (stream
,tag
,base
,s
," KEYWORD ",pgm
->keyword
,
3302 (reply
= imap_send_slist (stream
,tag
,base
,s
," UNKEYWORD ",
3303 pgm
->unkeyword
,limit
))))
3305 /* sent date ranges */
3306 if (pgm
->sentbefore
) imap_send_sdate (s
,"SENTBEFORE",pgm
->sentbefore
);
3307 if (pgm
->senton
) imap_send_sdate (s
,"SENTON",pgm
->senton
);
3308 if (pgm
->sentsince
) imap_send_sdate (s
,"SENTSINCE",pgm
->sentsince
);
3309 /* internal date ranges */
3310 if (pgm
->before
) imap_send_sdate (s
,"BEFORE",pgm
->before
);
3311 if (pgm
->on
) imap_send_sdate (s
,"ON",pgm
->on
);
3312 if (pgm
->since
) imap_send_sdate (s
,"SINCE",pgm
->since
);
3314 sprintf (*s
," OLDER %lu",pgm
->older
);
3318 sprintf (*s
," YOUNGER %lu",pgm
->younger
);
3322 if ((pgm
->bcc
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," BCC ",
3323 pgm
->bcc
,limit
))) ||
3324 (pgm
->cc
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," CC ",pgm
->cc
,
3326 (pgm
->from
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," FROM ",
3327 pgm
->from
,limit
))) ||
3328 (pgm
->to
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," TO ",pgm
->to
,
3331 if ((pgm
->subject
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," SUBJECT ",
3332 pgm
->subject
,limit
))) ||
3333 (pgm
->body
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," BODY ",
3334 pgm
->body
,limit
))) ||
3335 (pgm
->text
&& (reply
= imap_send_slist (stream
,tag
,base
,s
," TEXT ",
3339 /* Note that these criteria are not supported by IMAP and have to be
3341 if ((pgm
->return_path
&&
3342 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Return-Path ",
3343 pgm
->return_path
,limit
))) ||
3345 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Sender ",
3346 pgm
->sender
,limit
))) ||
3348 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Reply-To ",
3349 pgm
->reply_to
,limit
))) ||
3350 (pgm
->in_reply_to
&&
3351 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER In-Reply-To ",
3352 pgm
->in_reply_to
,limit
))) ||
3354 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Message-ID ",
3355 pgm
->message_id
,limit
))) ||
3357 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Newsgroups ",
3358 pgm
->newsgroups
,limit
))) ||
3359 (pgm
->followup_to
&&
3360 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER Followup-To ",
3361 pgm
->followup_to
,limit
))) ||
3363 (reply
= imap_send_slist (stream
,tag
,base
,s
," HEADER References ",
3364 pgm
->references
,limit
)))) return reply
;
3366 /* all other headers */
3367 if (hdr
= pgm
->header
) do {
3368 *s
= imap_send_spgm_trim (base
,*s
," HEADER ");
3369 if (reply
= imap_send_astring (stream
,tag
,s
,&hdr
->line
,NIL
,limit
))
3372 if (reply
= imap_send_astring (stream
,tag
,s
,&hdr
->text
,NIL
,limit
))
3374 } while (hdr
= hdr
->next
);
3375 for (pgo
= pgm
->or; pgo
; pgo
= pgo
->next
) {
3376 *s
= imap_send_spgm_trim (base
,*s
," OR (");
3377 if (reply
= imap_send_spgm (stream
,tag
,base
,s
,pgo
->first
,limit
))
3379 for (t
= ") ("; *t
; *(*s
)++ = *t
++);
3380 if (reply
= imap_send_spgm (stream
,tag
,base
,s
,pgo
->second
,limit
))
3384 for (pgl
= pgm
->not; pgl
; pgl
= pgl
->next
) {
3385 *s
= imap_send_spgm_trim (base
,*s
," NOT (");
3386 if (reply
= imap_send_spgm (stream
,tag
,base
,s
,pgl
->pgm
,limit
))
3390 /* trim if needed */
3391 *s
= imap_send_spgm_trim (base
,*s
,NIL
);
3392 return NIL
; /* search program written OK */
3396 /* Write new text and trim extraneous "ALL" from searchpgm
3397 * Accepts: pointer to start of searchpgm or NIL
3398 * current end pointer
3399 * new text to write or NIL
3400 * Returns: new end pointer, trimmed if needed
3403 char *imap_send_spgm_trim (char *base
,char *s
,char *text
)
3406 /* write new text */
3407 if (text
) for (t
= text
; *t
; *s
++ = *t
++);
3409 if (base
&& (s
> (t
= (base
+ 4))) && (*base
== 'A') && (base
[1] == 'L') &&
3410 (base
[2] == 'L') && (base
[3] == ' ')) {
3411 memmove (base
,t
,s
- t
); /* yes, blat down remaining text */
3412 s
-= 4; /* and reduce current pointer */
3414 return s
; /* return new end pointer */
3417 /* IMAP send search set
3418 * Accepts: MAIL stream
3419 * current command tag
3420 * base pointer if trimming needed
3421 * pointer to current position pointer of output bigbuf
3422 * search set to output
3424 * maximum output pointer
3425 * Returns: NIL if success, error reply if error
3428 IMAPPARSEDREPLY
*imap_send_sset (MAILSTREAM
*stream
,char *tag
,char *base
,
3429 char **s
,SEARCHSET
*set
,char *prefix
,
3432 IMAPPARSEDREPLY
*reply
;
3436 /* trim and write prefix */
3437 *s
= imap_send_spgm_trim (base
,*s
,prefix
);
3438 /* run down search list */
3439 for (c
= NIL
; set
&& (*s
< limit
); set
= set
->next
, c
= ',') {
3440 if (c
) *(*s
)++ = c
; /* write delimiter and first value */
3441 if (set
->first
== 0xffffffff) *(*s
)++ = '*';
3443 sprintf (*s
,"%lu",set
->first
);
3446 /* have a second value? */
3447 if (set
->last
&& (set
->first
!= set
->last
)) {
3448 *(*s
)++ = ':'; /* write delimiter and second value */
3449 if (set
->last
== 0xffffffff) *(*s
)++ = '*';
3451 sprintf (*s
,"%lu",set
->last
);
3456 if (set
) { /* insert "OR" in front of incomplete set */
3457 memmove (start
+ 3,start
,*s
- start
);
3458 memcpy (start
," OR",3);
3459 *s
+= 3; /* point to end of buffer */
3460 /* write glue that is equivalent to ALL */
3461 for (t
=" ((OR BCC FOO NOT BCC "; *t
; *(*s
)++ = *t
++);
3462 /* but broken by a literal */
3463 INIT (&st
,mail_string
,(void *) "FOO",3);
3464 if (reply
= imap_send_literal (stream
,tag
,s
,&st
)) return reply
;
3465 *(*s
)++ = ')'; /* close glue */
3466 if (reply
= imap_send_sset (stream
,tag
,NIL
,s
,set
,prefix
,limit
))
3468 *(*s
)++ = ')'; /* close second OR argument */
3473 /* IMAP send search list
3474 * Accepts: MAIL stream
3476 * base pointer if trimming needed
3477 * pointer to current position pointer of output bigbuf
3478 * name of search list
3479 * search list to output
3480 * maximum output pointer
3481 * Returns: NIL if success, error reply if error
3484 IMAPPARSEDREPLY
*imap_send_slist (MAILSTREAM
*stream
,char *tag
,char *base
,
3485 char **s
,char *name
,STRINGLIST
*list
,
3488 IMAPPARSEDREPLY
*reply
;
3490 *s
= imap_send_spgm_trim (base
,*s
,name
);
3491 base
= NIL
; /* no longer need trimming */
3492 reply
= imap_send_astring (stream
,tag
,s
,&list
->text
,NIL
,limit
);
3494 while (!reply
&& (list
= list
->next
));
3499 /* IMAP send search date
3500 * Accepts: pointer to current position pointer of output bigbuf
3502 * search date to output
3505 void imap_send_sdate (char **s
,char *name
,unsigned short date
)
3507 sprintf (*s
," %s %d-%s-%d",name
,date
& 0x1f,
3508 months
[((date
>> 5) & 0xf) - 1],BASEYEAR
+ (date
>> 9));
3512 /* IMAP send buffered command to sender
3513 * Accepts: MAIL stream
3516 * pointer to string tail pointer
3520 IMAPPARSEDREPLY
*imap_sout (MAILSTREAM
*stream
,char *tag
,char *base
,char **s
)
3522 IMAPPARSEDREPLY
*reply
;
3523 if (stream
->debug
) { /* output debugging telemetry */
3525 mail_dlog (base
,LOCAL
->sensitive
);
3527 *(*s
)++ = '\015'; /* append CRLF */
3530 reply
= net_sout (LOCAL
->netstream
,base
,*s
- base
) ?
3531 imap_reply (stream
,tag
) :
3532 imap_fake (stream
,tag
,"[CLOSED] IMAP connection broken (command)");
3533 *s
= base
; /* restart buffer */
3538 /* IMAP send null-terminated string to sender
3539 * Accepts: MAIL stream
3541 * Returns: T if success, else NIL
3544 long imap_soutr (MAILSTREAM
*stream
,char *string
)
3549 if (stream
->debug
) mm_dlog (string
);
3550 sprintf (s
= (char *) fs_get ((i
= strlen (string
) + 2) + 1),
3551 "%s\015\012",string
);
3552 ret
= net_sout (LOCAL
->netstream
,s
,i
);
3553 fs_give ((void **) &s
);
3558 * Accepts: MAIL stream
3559 * tag to search or NIL if want a greeting
3560 * Returns: parsed reply, never NIL
3563 IMAPPARSEDREPLY
*imap_reply (MAILSTREAM
*stream
,char *tag
)
3565 IMAPPARSEDREPLY
*reply
;
3566 while (LOCAL
->netstream
) { /* parse reply from server */
3567 if (reply
= imap_parse_reply (stream
,net_getline (LOCAL
->netstream
))) {
3568 /* continuation ready? */
3569 if (!strcmp (reply
->tag
,"+")) return reply
;
3570 /* untagged data? */
3571 else if (!strcmp (reply
->tag
,"*")) {
3572 imap_parse_unsolicited (stream
,reply
);
3573 if (!tag
) return reply
; /* return if just wanted greeting */
3575 else { /* tagged data */
3576 if (tag
&& !compare_cstring (tag
,reply
->tag
)) return reply
;
3578 sprintf (LOCAL
->tmp
,"Unexpected tagged response: %.80s %.80s %.80s",
3579 (char *) reply
->tag
,(char *) reply
->key
,(char *) reply
->text
);
3580 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3581 stream
->unhealthy
= T
;
3585 return imap_fake (stream
,tag
,
3586 "[CLOSED] IMAP connection broken (server response)");
3590 * Accepts: MAIL stream
3592 * Returns: parsed reply, or NIL if can't parse at least a tag and key
3596 IMAPPARSEDREPLY
*imap_parse_reply (MAILSTREAM
*stream
,char *text
)
3599 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
3600 /* init fields in case error */
3601 LOCAL
->reply
.key
= LOCAL
->reply
.text
= LOCAL
->reply
.tag
= NIL
;
3602 if (!(LOCAL
->reply
.line
= text
)) {
3603 /* NIL text means the stream died */
3604 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
3605 LOCAL
->netstream
= NIL
;
3608 if (stream
->debug
) mm_dlog (LOCAL
->reply
.line
);
3609 if (!(LOCAL
->reply
.tag
= strtok_r (LOCAL
->reply
.line
," ",&r
))) {
3610 mm_notify (stream
,"IMAP server sent a blank line",WARN
);
3611 stream
->unhealthy
= T
;
3614 /* non-continuation replies */
3615 if (strcmp (LOCAL
->reply
.tag
,"+")) {
3617 if (!(LOCAL
->reply
.key
= strtok_r (NIL
," ",&r
))) {
3618 /* determine what is missing */
3619 sprintf (LOCAL
->tmp
,"Missing IMAP reply key: %.80s",
3620 (char *) LOCAL
->reply
.tag
);
3621 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3622 stream
->unhealthy
= T
;
3623 return NIL
; /* can't parse this text */
3625 ucase (LOCAL
->reply
.key
); /* canonicalize key to upper */
3626 /* get text as well, allow empty text */
3627 if (!(LOCAL
->reply
.text
= strtok_r (NIL
,"\n",&r
)))
3628 LOCAL
->reply
.text
= LOCAL
->reply
.key
+ strlen (LOCAL
->reply
.key
);
3630 else { /* special handling of continuation */
3631 LOCAL
->reply
.key
= "BAD"; /* so it barfs if not expecting continuation */
3632 if (!(LOCAL
->reply
.text
= strtok_r (NIL
,"\n",&r
)))
3633 LOCAL
->reply
.text
= "";
3635 return &LOCAL
->reply
; /* return parsed reply */
3638 /* IMAP fake reply when stream determined to be dead
3639 * Accepts: MAIL stream
3641 * text of fake reply (must start with "[CLOSED]")
3642 * Returns: parsed reply
3645 IMAPPARSEDREPLY
*imap_fake (MAILSTREAM
*stream
,char *tag
,char *text
)
3647 mm_notify (stream
,text
,BYE
); /* send bye alert */
3648 if (LOCAL
->netstream
) net_close (LOCAL
->netstream
);
3649 LOCAL
->netstream
= NIL
; /* farewell, dear NET stream... */
3650 /* flush previous reply */
3651 if (LOCAL
->reply
.line
) fs_give ((void **) &LOCAL
->reply
.line
);
3652 /* build new fake reply */
3653 LOCAL
->reply
.tag
= LOCAL
->reply
.line
= cpystr (tag
? tag
: "*");
3654 LOCAL
->reply
.key
= "NO";
3655 LOCAL
->reply
.text
= text
;
3656 return &LOCAL
->reply
; /* return parsed reply */
3660 /* IMAP check for OK response in tagged reply
3661 * Accepts: MAIL stream
3663 * Returns: T if OK else NIL
3666 long imap_OK (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
3669 /* OK - operation succeeded */
3670 if (!strcmp (reply
->key
,"OK")) {
3671 imap_parse_response (stream
,reply
->text
,NIL
,NIL
);
3674 /* NO - operation failed */
3675 else if (!strcmp (reply
->key
,"NO"))
3676 imap_parse_response (stream
,reply
->text
,WARN
,NIL
);
3677 else { /* BAD - operation rejected */
3678 if (!strcmp (reply
->key
,"BAD")) {
3679 imap_parse_response (stream
,reply
->text
,ERROR
,NIL
);
3680 sprintf (LOCAL
->tmp
,"IMAP protocol error: %.80s",(char *) reply
->text
);
3682 /* bad protocol received */
3683 else sprintf (LOCAL
->tmp
,"Unexpected IMAP response: %.80s %.80s",
3684 (char *) reply
->key
,(char *) reply
->text
);
3685 mm_log (LOCAL
->tmp
,ERROR
); /* either way, this is not good */
3690 /* IMAP parse and act upon unsolicited reply
3691 * Accepts: MAIL stream
3695 void imap_parse_unsolicited (MAILSTREAM
*stream
,IMAPPARSEDREPLY
*reply
)
3697 unsigned long i
= 0;
3698 unsigned long j
,msgno
;
3699 unsigned char *s
,*t
;
3701 /* see if key is a number */
3702 if (isdigit (*reply
->key
)) {
3703 msgno
= strtoul (reply
->key
,(char **) &s
,10);
3704 if (*s
) { /* better be nothing after number */
3705 sprintf (LOCAL
->tmp
,"Unexpected untagged message: %.80s",
3706 (char *) reply
->key
);
3707 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3708 stream
->unhealthy
= T
;
3711 if (!reply
->text
) { /* better be some data */
3712 mm_notify (stream
,"Missing message data",WARN
);
3713 stream
->unhealthy
= T
;
3716 /* get message data type, canonicalize upper */
3717 s
= ucase (strtok_r (reply
->text
," ",&r
));
3718 /* and locate the text after it */
3719 t
= strtok_r (NIL
,"\n",&r
);
3720 /* now take the action */
3721 /* change in size of mailbox */
3722 if (!strcmp (s
,"EXISTS") && (msgno
>= stream
->nmsgs
))
3723 mail_exists (stream
,msgno
);
3724 else if (!strcmp (s
,"RECENT") && (msgno
<= stream
->nmsgs
))
3725 mail_recent (stream
,msgno
);
3726 else if (!strcmp (s
,"EXPUNGE") && msgno
&& (msgno
<= stream
->nmsgs
)) {
3727 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
3728 MESSAGECACHE
*elt
= (MESSAGECACHE
*) (*mc
) (stream
,msgno
,CH_ELT
);
3729 if (elt
) imap_gc_body (elt
->private.msg
.body
);
3730 /* notify upper level */
3731 mail_expunged (stream
,msgno
);
3734 else if ((!strcmp (s
,"FETCH") || !strcmp (s
,"STORE")) &&
3735 msgno
&& (msgno
<= stream
->nmsgs
)) {
3739 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
3740 ENVELOPE
*env
= NIL
;
3742 (imapenvelope_t
) mail_parameters (stream
,GET_IMAPENVELOPE
,NIL
);
3743 ++t
; /* skip past open parenthesis */
3744 /* parse Lisp-form property list */
3745 while (prop
= (strtok_r (t
," )",&r
))) {
3746 t
= strtok_r (NIL
,"\n",&r
);
3747 INIT_GETS (md
,stream
,elt
->msgno
,NIL
,0,0);
3748 e
= NIL
; /* not pointing at any envelope yet */
3749 /* canonicalize property, parse it */
3750 if (!strcmp (ucase (prop
),"FLAGS")) imap_parse_flags (stream
,elt
,&t
);
3751 else if (!strcmp (prop
,"INTERNALDATE") &&
3752 (s
= imap_parse_string (stream
,&t
,reply
,NIL
,NIL
,LONGT
))) {
3753 if (!mail_parse_date (elt
,s
)) {
3754 sprintf (LOCAL
->tmp
,"Bogus date: %.80s",(char *) s
);
3755 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3756 stream
->unhealthy
= T
;
3757 /* slam in default so we don't try again */
3758 mail_parse_date (elt
,"01-Jan-1970 00:00:00 +0000");
3760 fs_give ((void **) &s
);
3762 /* unique identifier */
3763 else if (!strcmp (prop
,"UID")) {
3764 LOCAL
->lastuid
.uid
= elt
->private.uid
= strtoul (t
,(char **) &t
,10);
3765 LOCAL
->lastuid
.msgno
= elt
->msgno
;
3767 else if (!strcmp (prop
,"ENVELOPE")) {
3768 if (stream
->scache
) { /* short cache, flush old stuff */
3769 mail_free_body (&stream
->body
);
3770 stream
->msgno
= elt
->msgno
;
3771 e
= &stream
->env
; /* get pointer to envelope */
3773 else e
= &elt
->private.msg
.env
;
3774 imap_parse_envelope (stream
,e
,&t
,reply
);
3776 else if (!strncmp (prop
,"BODY",4)) {
3777 if (!prop
[4] || !strcmp (prop
+4,"STRUCTURE")) {
3779 if (stream
->scache
){/* short cache, flush old stuff */
3780 if (stream
->msgno
!= msgno
) {
3781 mail_free_envelope (&stream
->env
);
3782 sprintf (LOCAL
->tmp
,"Body received for %lu but current is %lu",
3783 msgno
,stream
->msgno
);
3784 stream
->msgno
= msgno
;
3786 /* get pointer to body */
3787 body
= &stream
->body
;
3789 else body
= &elt
->private.msg
.body
;
3790 /* flush any prior body */
3791 mail_free_body (body
);
3792 /* instantiate and parse a new body */
3793 imap_parse_body_structure (stream
,*body
= mail_newbody(),&t
,reply
);
3796 else if (prop
[4] == '[') {
3797 STRINGLIST
*stl
= NIL
;
3799 /* will want to return envelope data */
3800 if (!strcmp (md
.what
= cpystr (prop
+ 5),"HEADER]") ||
3801 !strcmp (md
.what
,"0]"))
3802 e
= stream
->scache
? &stream
->env
: &elt
->private.msg
.env
;
3803 LOCAL
->tmp
[0] ='\0';/* no errors yet */
3804 /* found end of section? */
3805 if (!(s
= strchr (md
.what
,']'))) {
3806 /* skip leading nesting */
3807 for (s
= md
.what
; *s
&& (isdigit (*s
) || (*s
== '.')); s
++);
3808 /* better be one of these */
3809 if (strncmp (s
,"HEADER.FIELDS",13) &&
3810 (!s
[13] || strcmp (s
+13,".NOT")))
3811 sprintf (LOCAL
->tmp
,"Unterminated section: %.80s",md
.what
);
3812 /* get list of headers */
3813 else if (!(stl
= imap_parse_stringlist (stream
,&t
,reply
)))
3814 sprintf (LOCAL
->tmp
,"Bogus header field list: %.80s",
3817 sprintf (LOCAL
->tmp
,"Unterminated header section: %.80s",
3819 /* point after the text */
3820 else if (t
= strchr (s
= t
,' ')) *t
++ = '\0';
3822 if (s
&& !LOCAL
->tmp
[0]) {
3823 *s
++ = '\0'; /* tie off section specifier */
3824 if (*s
== '<') { /* partial specifier? */
3825 md
.first
= strtoul (s
+1,(char **) &s
,10) + 1;
3826 if (*s
++ != '>') /* make sure properly terminated */
3827 sprintf (LOCAL
->tmp
,"Unterminated partial data: %.80s",
3830 if (!LOCAL
->tmp
[0] && *s
)
3831 sprintf (LOCAL
->tmp
,"Junk after section: %.80s",(char *) s
);
3833 if (LOCAL
->tmp
[0]) { /* got any errors? */
3834 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3835 stream
->unhealthy
= T
;
3836 mail_free_stringlist (&stl
);
3838 else { /* parse text from server */
3839 text
.data
= (unsigned char *)
3840 imap_parse_string (stream
,&t
,reply
,
3841 ((md
.what
[0] && (md
.what
[0] != 'H')) ||
3842 md
.first
|| md
.last
) ? &md
: NIL
,
3844 /* all done if partial */
3845 if (md
.first
|| md
.last
) mail_free_stringlist (&stl
);
3846 /* otherwise register it in the cache */
3847 else imap_cache (stream
,msgno
,md
.what
,stl
,&text
);
3849 fs_give ((void **) &md
.what
);
3852 sprintf (LOCAL
->tmp
,"Unknown body message property: %.80s",prop
);
3853 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3854 stream
->unhealthy
= T
;
3858 /* one of the RFC822 props? */
3859 else if (!strncmp (prop
,"RFC822",6) && (!prop
[6] || (prop
[6] == '.'))){
3861 if (!prop
[6]) { /* cache full message */
3863 text
.data
= (unsigned char *)
3864 imap_parse_string (stream
,&t
,reply
,&md
,&text
.size
,NIL
);
3865 imap_cache (stream
,msgno
,md
.what
,NIL
,&text
);
3867 else if (!strcmp (prop
+7,"SIZE"))
3868 elt
->rfc822_size
= strtoul (t
,(char **) &t
,10);
3869 /* legacy properties */
3870 else if (!strcmp (prop
+7,"HEADER")) {
3871 text
.data
= (unsigned char *)
3872 imap_parse_string (stream
,&t
,reply
,NIL
,&text
.size
,NIL
);
3873 imap_cache (stream
,msgno
,"HEADER",NIL
,&text
);
3874 e
= stream
->scache
? &stream
->env
: &elt
->private.msg
.env
;
3876 else if (!strcmp (prop
+7,"TEXT")) {
3878 text
.data
= (unsigned char *)
3879 imap_parse_string (stream
,&t
,reply
,&md
,&text
.size
,NIL
);
3880 imap_cache (stream
,msgno
,md
.what
,NIL
,&text
);
3883 sprintf (LOCAL
->tmp
,"Unknown RFC822 message property: %.80s",prop
);
3884 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3885 stream
->unhealthy
= T
;
3889 sprintf (LOCAL
->tmp
,"Unknown message property: %.80s",prop
);
3890 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3891 stream
->unhealthy
= T
;
3893 if (e
&& *e
) env
= *e
; /* note envelope if we got one */
3895 /* do callback if requested */
3896 if (ie
&& env
) (*ie
) (stream
,msgno
,env
);
3898 /* obsolete response to COPY */
3899 else if (strcmp (s
,"COPY")) {
3900 sprintf (LOCAL
->tmp
,"Unknown message data: %lu %.80s",msgno
,(char *) s
);
3901 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3902 stream
->unhealthy
= T
;
3906 else if (!strcmp (reply
->key
,"FLAGS") && reply
->text
&&
3907 (*reply
->text
== '(') &&
3908 (s
= strtok_r (reply
->text
+1," )",&r
)))
3909 do if (*s
!= '\\') {
3910 for (i
= 0; (i
< NUSERFLAGS
) && stream
->user_flags
[i
] &&
3911 compare_cstring (s
,stream
->user_flags
[i
]); i
++);
3912 if (i
> NUSERFLAGS
) {
3913 sprintf (LOCAL
->tmp
,"Too many server flags, discarding: %.80s",
3915 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3917 else if (!stream
->user_flags
[i
]) stream
->user_flags
[i
++] = cpystr (s
);
3919 while (s
= strtok_r (NIL
," )",&r
));
3920 else if (!strcmp (reply
->key
,"SEARCH")) {
3921 /* only do something if have text */
3922 if (reply
->text
&& (t
= strtok_r (reply
->text
," ",&r
))) do
3923 if (i
= strtoul (t
,NIL
,10)) {
3924 /* UIDs always passed to main program */
3925 if (LOCAL
->uidsearch
) mm_searched (stream
,i
);
3926 /* should be a msgno then */
3927 else if ((i
<= stream
->nmsgs
) &&
3928 (!LOCAL
->filter
|| mail_elt (stream
,i
)->private.filter
)) {
3929 mail_elt (stream
,i
)->searched
= T
;
3930 if (!stream
->silent
) mm_searched (stream
,i
);
3932 } while (t
= strtok_r (NIL
," ",&r
));
3934 else if (!strcmp (reply
->key
,"SORT")) {
3935 sortresults_t sr
= (sortresults_t
)
3936 mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
3937 LOCAL
->sortsize
= 0; /* initialize sort data */
3938 if (LOCAL
->sortdata
) fs_give ((void **) &LOCAL
->sortdata
);
3939 LOCAL
->sortdata
= (unsigned long *)
3940 fs_get ((stream
->nmsgs
+ 1) * sizeof (unsigned long));
3941 /* only do something if have text */
3942 if (reply
->text
&& (t
= strtok_r (reply
->text
," ",&r
))) {
3943 do if ((i
= atol (t
)) && (LOCAL
->filter
?
3944 mail_elt (stream
,i
)->searched
: T
))
3945 LOCAL
->sortdata
[LOCAL
->sortsize
++] = i
;
3946 while ((t
= strtok_r (NIL
," ",&r
)) && (LOCAL
->sortsize
< stream
->nmsgs
));
3948 LOCAL
->sortdata
[LOCAL
->sortsize
] = 0;
3949 /* also return via callback if requested */
3950 if (sr
) (*sr
) (stream
,LOCAL
->sortdata
,LOCAL
->sortsize
);
3952 else if (!strcmp (reply
->key
,"THREAD")) {
3953 threadresults_t tr
= (threadresults_t
)
3954 mail_parameters (NIL
,GET_THREADRESULTS
,NIL
);
3955 if (LOCAL
->threaddata
) mail_free_threadnode (&LOCAL
->threaddata
);
3956 if (s
= reply
->text
) {
3957 LOCAL
->threaddata
= imap_parse_thread (stream
,&s
);
3958 if (tr
) (*tr
) (stream
,LOCAL
->threaddata
);
3960 sprintf (LOCAL
->tmp
,"Junk at end of thread: %.80s",(char *) s
);
3961 mm_notify (stream
,LOCAL
->tmp
,WARN
);
3962 stream
->unhealthy
= T
;
3967 else if (!strcmp (reply
->key
,"STATUS") && reply
->text
) {
3969 unsigned char *txt
= reply
->text
;
3970 if ((t
= imap_parse_astring (stream
,&txt
,reply
,&j
)) && txt
&&
3971 (*txt
++ == ' ') && (*txt
++ == '(') && (s
= strchr (txt
,')')) &&
3972 (s
- txt
) && !s
[1]) {
3973 *s
= '\0'; /* tie off status data */
3974 /* initialize data block */
3975 status
.flags
= status
.messages
= status
.recent
= status
.unseen
=
3976 status
.uidnext
= status
.uidvalidity
= 0;
3977 while (*txt
&& (s
= strchr (txt
,' '))) {
3978 *s
++ = '\0'; /* tie off status attribute name */
3979 /* get attribute value */
3980 i
= strtoul (s
,(char **) &s
,10);
3981 if (!compare_cstring (txt
,"MESSAGES")) {
3982 status
.flags
|= SA_MESSAGES
;
3983 status
.messages
= i
;
3985 else if (!compare_cstring (txt
,"RECENT")) {
3986 status
.flags
|= SA_RECENT
;
3989 else if (!compare_cstring (txt
,"UNSEEN")) {
3990 status
.flags
|= SA_UNSEEN
;
3993 else if (!compare_cstring (txt
,"UIDNEXT")) {
3994 status
.flags
|= SA_UIDNEXT
;
3997 else if (!compare_cstring (txt
,"UIDVALIDITY")) {
3998 status
.flags
|= SA_UIDVALIDITY
;
3999 status
.uidvalidity
= i
;
4001 /* next attribute */
4002 txt
= (*s
== ' ') ? s
+ 1 : s
;
4004 if (((i
= 1 + strchr (stream
->mailbox
,'}') - stream
->mailbox
) + j
) <
4006 strcpy (strncpy (LOCAL
->tmp
,stream
->mailbox
,i
) + i
,t
);
4007 /* pass status to main program */
4008 mm_status (stream
,LOCAL
->tmp
,&status
);
4011 if (t
) fs_give ((void **) &t
);
4014 else if ((!strcmp (reply
->key
,"LIST") || !strcmp (reply
->key
,"LSUB")) &&
4015 reply
->text
&& (*reply
->text
== '(') &&
4016 (s
= strchr (reply
->text
,')')) && (s
[1] == ' ')) {
4017 char delimiter
= '\0';
4018 *s
++ = '\0'; /* tie off attribute list */
4019 /* parse attribute list */
4020 if (t
= strtok_r (reply
->text
+1," ",&r
)) do {
4021 if (!compare_cstring (t
,"\\NoInferiors")) i
|= LATT_NOINFERIORS
;
4022 else if (!compare_cstring (t
,"\\NoSelect")) i
|= LATT_NOSELECT
;
4023 else if (!compare_cstring (t
,"\\Marked")) i
|= LATT_MARKED
;
4024 else if (!compare_cstring (t
,"\\Unmarked")) i
|= LATT_UNMARKED
;
4025 else if (!compare_cstring (t
,"\\HasChildren")) i
|= LATT_HASCHILDREN
;
4026 else if (!compare_cstring (t
,"\\HasNoChildren")) i
|= LATT_HASNOCHILDREN
;
4027 /* ignore extension flags */
4029 while (t
= strtok_r (NIL
," ",&r
));
4030 switch (*++s
) { /* process delimiter */
4033 s
+= 4; /* skip over NIL<space> */
4035 case '"': /* have a delimiter */
4036 delimiter
= (*++s
== '\\') ? *++s
: *s
;
4037 s
+= 3; /* skip over <delimiter><quote><space> */
4039 /* parse the mailbox name */
4040 if (t
= imap_parse_astring (stream
,&s
,reply
,&j
)) {
4041 /* prepend prefix if requested */
4042 if (LOCAL
->prefix
&& ((strlen (LOCAL
->prefix
) + j
) < IMAPTMPLEN
))
4043 sprintf (s
= LOCAL
->tmp
,"%s%s",LOCAL
->prefix
,(char *) t
);
4044 else s
= t
; /* otherwise just mailbox name */
4045 /* pass data to main program */
4046 if (reply
->key
[1] == 'S') mm_lsub (stream
,delimiter
,s
,i
);
4047 else mm_list (stream
,delimiter
,s
,i
);
4048 fs_give ((void **) &t
); /* flush mailbox name */
4051 else if (!strcmp (reply
->key
,"NAMESPACE")) {
4052 if (LOCAL
->namespace) {
4053 mail_free_namespace (&LOCAL
->namespace[0]);
4054 mail_free_namespace (&LOCAL
->namespace[1]);
4055 mail_free_namespace (&LOCAL
->namespace[2]);
4057 else LOCAL
->namespace = (NAMESPACE
**) fs_get (3 * sizeof (NAMESPACE
*));
4058 if (s
= reply
->text
) { /* parse namespace results */
4059 LOCAL
->namespace[0] = imap_parse_namespace (stream
,&s
,reply
);
4060 LOCAL
->namespace[1] = imap_parse_namespace (stream
,&s
,reply
);
4061 LOCAL
->namespace[2] = imap_parse_namespace (stream
,&s
,reply
);
4063 sprintf (LOCAL
->tmp
,"Junk after namespace list: %.80s",(char *) s
);
4064 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4065 stream
->unhealthy
= T
;
4069 mm_notify (stream
,"Missing namespace list",WARN
);
4070 stream
->unhealthy
= T
;
4074 else if (!strcmp (reply
->key
,"ACL") && (s
= reply
->text
) &&
4075 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4076 getacl_t ar
= (getacl_t
) mail_parameters (NIL
,GET_ACL
,NIL
);
4077 if (s
&& (*s
++ == ' ')) {
4078 ACLLIST
*al
= mail_newacllist ();
4080 do if ((ac
->identifier
= imap_parse_astring (stream
,&s
,reply
,NIL
)) &&
4082 ac
->rights
= imap_parse_astring (stream
,&s
,reply
,NIL
);
4083 while (ac
->rights
&& s
&& (*s
== ' ') && s
++ &&
4084 (ac
= ac
->next
= mail_newacllist ()));
4085 if (!ac
->rights
|| (s
&& *s
)) {
4086 sprintf (LOCAL
->tmp
,"Invalid ACL identifer/rights for %.80s",
4088 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4089 stream
->unhealthy
= T
;
4091 else if (ar
) (*ar
) (stream
,t
,al
);
4092 mail_free_acllist (&al
); /* clean up */
4094 /* no optional rights */
4095 else if (ar
) (*ar
) (stream
,t
,NIL
);
4096 fs_give ((void **) &t
); /* free mailbox name */
4099 else if (!strcmp (reply
->key
,"LISTRIGHTS") && (s
= reply
->text
) &&
4100 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4101 listrights_t lr
= (listrights_t
) mail_parameters (NIL
,GET_LISTRIGHTS
,NIL
);
4103 if (s
&& (*s
++ == ' ') && (id
= imap_parse_astring (stream
,&s
,reply
,NIL
))){
4104 if (s
&& (*s
++ == ' ') &&
4105 (r
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4106 if (s
&& (*s
++ == ' ')) {
4107 STRINGLIST
*rl
= mail_newstringlist ();
4108 STRINGLIST
*rc
= rl
;
4109 do rc
->text
.data
= (unsigned char *)
4110 imap_parse_astring (stream
,&s
,reply
,&rc
->text
.size
);
4111 while (rc
->text
.data
&& s
&& (*s
== ' ') && s
++ &&
4112 (rc
= rc
->next
= mail_newstringlist ()));
4113 if (!rc
->text
.data
|| (s
&& *s
)) {
4114 sprintf (LOCAL
->tmp
,"Invalid optional LISTRIGHTS for %.80s",
4116 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4117 stream
->unhealthy
= T
;
4119 else if (lr
) (*lr
) (stream
,t
,id
,r
,rl
);
4121 mail_free_stringlist (&rl
);
4123 /* no optional rights */
4124 else if (lr
) (*lr
) (stream
,t
,id
,r
,NIL
);
4125 fs_give ((void **) &r
); /* free rights */
4128 sprintf (LOCAL
->tmp
,"Missing LISTRIGHTS rights for %.80s",(char *) t
);
4129 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4130 stream
->unhealthy
= T
;
4132 fs_give ((void **) &id
); /* free identifier */
4135 sprintf (LOCAL
->tmp
,"Missing LISTRIGHTS identifer for %.80s",(char *) t
);
4136 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4137 stream
->unhealthy
= T
;
4139 fs_give ((void **) &t
); /* free mailbox name */
4142 else if (!strcmp (reply
->key
,"MYRIGHTS") && (s
= reply
->text
) &&
4143 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4144 myrights_t mr
= (myrights_t
) mail_parameters (NIL
,GET_MYRIGHTS
,NIL
);
4146 if (s
&& (*s
++ == ' ') && (r
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4148 sprintf (LOCAL
->tmp
,"Junk after MYRIGHTS for %.80s",(char *) t
);
4149 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4150 stream
->unhealthy
= T
;
4152 else if (mr
) (*mr
) (stream
,t
,r
);
4153 fs_give ((void **) &r
); /* free rights */
4156 sprintf (LOCAL
->tmp
,"Missing MYRIGHTS for %.80s",(char *) t
);
4157 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4158 stream
->unhealthy
= T
;
4160 fs_give ((void **) &t
); /* free mailbox name */
4163 /* this response has a bizarre syntax! */
4164 else if (!strcmp (reply
->key
,"QUOTA") && (s
= reply
->text
) &&
4165 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4167 sprintf (LOCAL
->tmp
,"Bad quota resource list for %.80s",(char *) t
);
4168 if (s
&& (*s
++ == ' ') && (*s
++ == '(') && *s
&& ((*s
!= ')') || !s
[1])) {
4169 quota_t qt
= (quota_t
) mail_parameters (NIL
,GET_QUOTA
,NIL
);
4170 QUOTALIST
*ql
= NIL
;
4172 /* parse non-empty quota resource list */
4173 if (*s
!= ')') for (ql
= qc
= mail_newquotalist (); T
;
4174 qc
= qc
->next
= mail_newquotalist ()) {
4175 if ((qc
->name
= imap_parse_astring (stream
,&s
,reply
,NIL
)) && s
&&
4176 (*s
++ == ' ') && (isdigit (*s
) || (LOCAL
->loser
&& (*s
== '-')))) {
4177 if (isdigit (*s
)) qc
->usage
= strtoul (s
,(char **) &s
,10);
4178 else if (t
= strchr (s
,' ')) t
= s
;
4179 if ((*s
++ == ' ') && (isdigit (*s
) || (LOCAL
->loser
&&(*s
== '-')))){
4180 if (isdigit (*s
)) qc
->limit
= strtoul (s
,(char **) &s
,10);
4181 else if (t
= strpbrk (s
," )")) t
= s
;
4182 /* another resource follows? */
4183 if (*s
== ' ') continue;
4184 /* end of resource list? */
4185 if ((*s
== ')') && !s
[1]) {
4186 if (qt
) (*qt
) (stream
,t
,ql
);
4187 break; /* all done */
4191 /* something bad happened */
4192 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4193 stream
->unhealthy
= T
;
4194 break; /* parse failed */
4196 /* all done with quota resource list now */
4197 if (ql
) mail_free_quotalist (&ql
);
4200 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4201 stream
->unhealthy
= T
;
4203 fs_give ((void **) &t
); /* free root name */
4205 else if (!strcmp (reply
->key
,"QUOTAROOT") && (s
= reply
->text
) &&
4206 (t
= imap_parse_astring (stream
,&s
,reply
,NIL
))) {
4207 sprintf (LOCAL
->tmp
,"Bad quota root list for %.80s",(char *) t
);
4208 if (s
&& (*s
++ == ' ')) {
4209 quotaroot_t qr
= (quotaroot_t
) mail_parameters (NIL
,GET_QUOTAROOT
,NIL
);
4210 STRINGLIST
*rl
= mail_newstringlist ();
4211 STRINGLIST
*rc
= rl
;
4212 do rc
->text
.data
= (unsigned char *)
4213 imap_parse_astring (stream
,&s
,reply
,&rc
->text
.size
);
4214 while (rc
->text
.data
&& *s
&& (*s
++ == ' ') &&
4215 (rc
= rc
->next
= mail_newstringlist ()));
4216 if (!rc
->text
.data
|| (s
&& *s
)) {
4217 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4218 stream
->unhealthy
= T
;
4220 else if (qr
) (*qr
) (stream
,t
,rl
);
4222 mail_free_stringlist (&rl
);
4225 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4226 stream
->unhealthy
= T
;
4228 fs_give ((void **) &t
);
4231 else if (!strcmp (reply
->key
,"OK") || !strcmp (reply
->key
,"PREAUTH"))
4232 imap_parse_response (stream
,reply
->text
,NIL
,T
);
4233 else if (!strcmp (reply
->key
,"NO"))
4234 imap_parse_response (stream
,reply
->text
,WARN
,T
);
4235 else if (!strcmp (reply
->key
,"BAD"))
4236 imap_parse_response (stream
,reply
->text
,ERROR
,T
);
4237 else if (!strcmp (reply
->key
,"BYE")) {
4238 LOCAL
->byeseen
= T
; /* note that a BYE seen */
4239 imap_parse_response (stream
,reply
->text
,BYE
,T
);
4241 else if (!strcmp (reply
->key
,"CAPABILITY") && reply
->text
)
4242 imap_parse_capabilities (stream
,reply
->text
);
4243 else if (!strcmp (reply
->key
,"MAILBOX") && reply
->text
) {
4244 if (LOCAL
->prefix
&&
4245 ((strlen (LOCAL
->prefix
) + strlen (reply
->text
)) < IMAPTMPLEN
))
4246 sprintf (t
= LOCAL
->tmp
,"%s%s",LOCAL
->prefix
,(char *) reply
->text
);
4247 else t
= reply
->text
;
4248 mm_list (stream
,NIL
,t
,NIL
);
4251 sprintf (LOCAL
->tmp
,"Unexpected untagged message: %.80s",
4252 (char *) reply
->key
);
4253 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4254 stream
->unhealthy
= T
;
4258 /* Parse human-readable response text
4259 * Accepts: mail stream
4261 * error level for mm_notify()
4262 * non-NIL if want mm_notify() event even if no response code
4265 void imap_parse_response (MAILSTREAM
*stream
,char *text
,long errflg
,long ntfy
)
4273 SEARCHSET
*source
= NIL
;
4274 SEARCHSET
*dest
= NIL
;
4275 if (text
&& (*text
== '[') && (t
= strchr (s
= text
+ 1,']')) &&
4276 ((i
= t
- s
) < IMAPTMPLEN
)) {
4277 LOCAL
->tmp
[i
] = '\0'; /* make mungable copy of text code */
4278 if (s
= strchr (strncpy (t
= LOCAL
->tmp
,s
,i
),' ')) *s
++ = '\0';
4279 if (s
) { /* have argument? */
4280 ntfy
= NIL
; /* suppress mm_notify if normal SELECT data */
4281 if (!compare_cstring (t
,"UIDVALIDITY") &&
4282 ((j
= strtoul (s
,NIL
,10)) != stream
->uid_validity
)) {
4283 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
4284 stream
->uid_validity
= j
;
4285 /* purge any UIDs in cache */
4286 for (j
= 1; j
<= stream
->nmsgs
; j
++)
4287 if (elt
= (MESSAGECACHE
*) (*mc
) (stream
,j
,CH_ELT
))
4288 elt
->private.uid
= 0;
4290 else if (!compare_cstring (t
,"UIDNEXT"))
4291 stream
->uid_last
= strtoul (s
,NIL
,10) - 1;
4292 else if (!compare_cstring (t
,"PERMANENTFLAGS") && (*s
== '(') &&
4294 t
[i
-1] = '\0'; /* tie off flags */
4295 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_answered
=
4296 stream
->perm_draft
= stream
->kwd_create
= NIL
;
4297 stream
->perm_user_flags
= NIL
;
4298 if (s
= strtok_r (s
+1," ",&r
)) do {
4299 if (*s
== '\\') { /* system flags */
4300 if (!compare_cstring (s
,"\\Seen")) stream
->perm_seen
= T
;
4301 else if (!compare_cstring (s
,"\\Deleted"))
4302 stream
->perm_deleted
= T
;
4303 else if (!compare_cstring (s
,"\\Flagged"))
4304 stream
->perm_flagged
= T
;
4305 else if (!compare_cstring (s
,"\\Answered"))
4306 stream
->perm_answered
= T
;
4307 else if (!compare_cstring (s
,"\\Draft")) stream
->perm_draft
= T
;
4308 else if (!strcmp (s
,"\\*")) stream
->kwd_create
= T
;
4310 else stream
->perm_user_flags
|= imap_parse_user_flag (stream
,s
);
4312 while (s
= strtok_r (NIL
," ",&r
));
4315 else if (!compare_cstring (t
,"CAPABILITY"))
4316 imap_parse_capabilities (stream
,s
);
4317 else if ((j
= LEVELUIDPLUS (stream
) && LOCAL
->appendmailbox
) &&
4318 !compare_cstring (t
,"COPYUID") &&
4319 (cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
)) &&
4320 isdigit (*s
) && (j
= strtoul (s
,&s
,10)) && (*s
++ == ' ') &&
4321 (source
= mail_parse_set (s
,&s
)) && (*s
++ == ' ') &&
4322 (dest
= mail_parse_set (s
,&s
)) && !*s
)
4323 (*cu
) (stream
,LOCAL
->appendmailbox
,j
,source
,dest
);
4324 else if (j
&& !compare_cstring (t
,"APPENDUID") &&
4325 (au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
)) &&
4326 isdigit (*s
) && (j
= strtoul (s
,&s
,10)) && (*s
++ == ' ') &&
4327 (dest
= mail_parse_set (s
,&s
)) && !*s
)
4328 (*au
) (LOCAL
->appendmailbox
,j
,dest
);
4329 else { /* all other response code events */
4330 ntfy
= T
; /* must mm_notify() */
4331 if (!compare_cstring (t
,"REFERRAL"))
4332 LOCAL
->referral
= cpystr (t
+ 9);
4334 mail_free_searchset (&source
);
4335 mail_free_searchset (&dest
);
4337 else { /* no arguments */
4338 if (!compare_cstring (t
,"UIDNOTSTICKY")) {
4340 stream
->uid_nosticky
= T
;
4342 else if (!compare_cstring (t
,"READ-ONLY")) stream
->rdonly
= T
;
4343 else if (!compare_cstring (t
,"READ-WRITE"))
4344 stream
->rdonly
= NIL
;
4345 else if (!compare_cstring (t
,"PARSE") && !errflg
)
4349 /* give event to main program */
4350 if (ntfy
&& !stream
->silent
) mm_notify (stream
,text
? text
: "",errflg
);
4353 /* Parse a namespace
4354 * Accepts: mail stream
4355 * current text pointer
4357 * Returns: namespace list, text pointer updated
4360 NAMESPACE
*imap_parse_namespace (MAILSTREAM
*stream
,unsigned char **txtptr
,
4361 IMAPPARSEDREPLY
*reply
)
4363 NAMESPACE
*ret
= NIL
;
4364 NAMESPACE
*nam
= NIL
;
4365 NAMESPACE
*prev
= NIL
;
4366 PARAMETER
*par
= NIL
;
4367 if (*txtptr
) { /* only if argument given */
4368 /* ignore leading space */
4369 while (**txtptr
== ' ') ++*txtptr
;
4371 case 'N': /* if NIL */
4373 ++*txtptr
; /* bump past "N" */
4374 ++*txtptr
; /* bump past "I" */
4375 ++*txtptr
; /* bump past "L" */
4378 ++*txtptr
; /* skip past open paren */
4379 while (**txtptr
== '(') {
4380 ++*txtptr
; /* skip past open paren */
4381 prev
= nam
; /* note previous if any */
4382 nam
= (NAMESPACE
*) memset (fs_get (sizeof (NAMESPACE
)),0,
4383 sizeof (NAMESPACE
));
4384 if (!ret
) ret
= nam
; /* if first time note first namespace */
4385 /* if previous link new block to it */
4386 if (prev
) prev
->next
= nam
;
4387 nam
->name
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,NIL
);
4388 /* ignore whitespace */
4389 while (**txtptr
== ' ') ++*txtptr
;
4390 switch (**txtptr
) { /* parse delimiter */
4393 *txtptr
+= 3; /* bump past "NIL" */
4396 if (*++*txtptr
== '\\') nam
->delimiter
= *++*txtptr
;
4397 else nam
->delimiter
= **txtptr
;
4398 *txtptr
+= 2; /* bump past character and closing quote */
4401 sprintf (LOCAL
->tmp
,"Missing delimiter in namespace: %.80s",
4403 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4404 stream
->unhealthy
= T
;
4405 *txtptr
= NIL
; /* stop parse */
4409 while (**txtptr
== ' '){/* append new parameter to tail */
4410 if (nam
->param
) par
= par
->next
= mail_newbody_parameter ();
4411 else nam
->param
= par
= mail_newbody_parameter ();
4412 if (!(par
->attribute
= imap_parse_string (stream
,txtptr
,reply
,NIL
,
4414 mm_notify (stream
,"Missing namespace extension attribute",WARN
);
4415 stream
->unhealthy
= T
;
4416 par
->attribute
= cpystr ("UNKNOWN");
4419 while (**txtptr
== ' ') ++*txtptr
;
4420 if (**txtptr
== '(') {/* have value list? */
4421 char *att
= par
->attribute
;
4422 ++*txtptr
; /* yes */
4423 do { /* parse each value */
4424 if (!(par
->value
= imap_parse_string (stream
,txtptr
,reply
,NIL
,
4426 sprintf (LOCAL
->tmp
,
4427 "Missing value for namespace attribute %.80s",att
);
4428 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4429 stream
->unhealthy
= T
;
4430 par
->value
= cpystr ("UNKNOWN");
4432 /* is there another value? */
4433 if (**txtptr
== ' ') par
= par
->next
= mail_newbody_parameter ();
4434 } while (!par
->value
);
4437 sprintf (LOCAL
->tmp
,"Missing values for namespace attribute %.80s",
4439 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4440 stream
->unhealthy
= T
;
4441 par
->value
= cpystr ("UNKNOWN");
4444 if (**txtptr
== ')') ++*txtptr
;
4445 else { /* missing trailing paren */
4446 sprintf (LOCAL
->tmp
,"Junk at end of namespace: %.80s",
4448 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4449 stream
->unhealthy
= T
;
4453 if (**txtptr
== ')') { /* expected trailing paren? */
4454 ++*txtptr
; /* got it! */
4458 sprintf (LOCAL
->tmp
,"Not a namespace: %.80s",(char *) *txtptr
);
4459 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4460 stream
->unhealthy
= T
;
4461 *txtptr
= NIL
; /* stop parse now */
4468 /* Parse a thread node list
4469 * Accepts: mail stream
4470 * current text pointer
4471 * Returns: thread node list, text pointer updated
4474 THREADNODE
*imap_parse_thread (MAILSTREAM
*stream
,unsigned char **txtptr
)
4477 THREADNODE
*ret
= NIL
; /* returned tree */
4478 THREADNODE
*last
= NIL
; /* last branch in this tree */
4479 THREADNODE
*parent
= NIL
; /* parent of current node */
4480 THREADNODE
*cur
; /* current node */
4481 while (**txtptr
== '(') { /* see a thread? */
4482 ++*txtptr
; /* skip past open paren */
4483 while (**txtptr
!= ')') { /* parse thread */
4484 if (**txtptr
== '(') { /* thread branch */
4485 cur
= imap_parse_thread (stream
,txtptr
);
4487 if (parent
) parent
= parent
->next
= cur
;
4488 else { /* no parent, create dummy */
4489 if (last
) last
= last
->branch
= mail_newthreadnode (NIL
);
4491 else ret
= last
= mail_newthreadnode (NIL
);
4492 /* add to dummy parent */
4493 last
->next
= parent
= cur
;
4496 /* threaded message number */
4497 else if (isdigit (*(s
= *txtptr
)) &&
4498 ((cur
= mail_newthreadnode (NIL
))->num
=
4499 strtoul (*txtptr
,(char **) txtptr
,10))) {
4500 if (LOCAL
->filter
&& !mail_elt (stream
,cur
->num
)->searched
)
4501 cur
->num
= NIL
; /* make dummy if filtering and not searched */
4503 if (parent
) parent
= parent
->next
= cur
;
4504 /* no parent, start new thread */
4505 else if (last
) last
= last
->branch
= parent
= cur
;
4506 /* create new tree */
4507 else ret
= last
= parent
= cur
;
4509 else { /* anything else is a bogon */
4510 char tmp
[MAILTMPLEN
];
4511 sprintf (tmp
,"Bogus thread member: %.80s",s
);
4512 mm_notify (stream
,tmp
,WARN
);
4513 stream
->unhealthy
= T
;
4516 /* skip past any space */
4517 if (**txtptr
== ' ') ++*txtptr
;
4519 ++*txtptr
; /* skip pase end of thread */
4520 parent
= NIL
; /* close this thread */
4522 return ret
; /* return parsed thread */
4525 /* Parse RFC822 message header
4526 * Accepts: MAIL stream
4527 * envelope to parse into
4528 * header as sized text
4529 * stringlist if partial header
4532 void imap_parse_header (MAILSTREAM
*stream
,ENVELOPE
**env
,SIZEDTEXT
*hdr
,
4536 /* parse what we can from this header */
4537 rfc822_parse_msg (&nenv
,NIL
,(char *) hdr
->data
,hdr
->size
,NIL
,
4538 net_host (LOCAL
->netstream
),stream
->dtb
->flags
);
4539 if (*env
) { /* need to merge this header into envelope? */
4540 if (!(*env
)->newsgroups
) { /* need Newsgroups? */
4541 (*env
)->newsgroups
= nenv
->newsgroups
;
4542 (*env
)->ngpathexists
= nenv
->ngpathexists
;
4543 nenv
->newsgroups
= NIL
;
4545 if (!(*env
)->followup_to
) { /* need Followup-To? */
4546 (*env
)->followup_to
= nenv
->followup_to
;
4547 nenv
->followup_to
= NIL
;
4549 if (!(*env
)->references
) { /* need References? */
4550 (*env
)->references
= nenv
->references
;
4551 nenv
->references
= NIL
;
4553 if (!(*env
)->sparep
) { /* need spare pointer? */
4554 (*env
)->sparep
= nenv
->sparep
;
4557 mail_free_envelope (&nenv
);
4558 (*env
)->imapenvonly
= NIL
; /* have complete envelope now */
4560 /* otherwise set it to this envelope */
4561 else (*env
= nenv
)->incomplete
= stl
? T
: NIL
;
4564 /* IMAP parse envelope
4565 * Accepts: MAIL stream
4566 * pointer to envelope pointer
4567 * current text pointer
4570 * Updates text pointer
4573 void imap_parse_envelope (MAILSTREAM
*stream
,ENVELOPE
**env
,
4574 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
4576 ENVELOPE
*oenv
= *env
;
4577 char c
= *((*txtptr
)++); /* grab first character */
4578 /* ignore leading spaces */
4579 while (c
== ' ') c
= *((*txtptr
)++);
4580 switch (c
) { /* dispatch on first character */
4581 case '(': /* if envelope S-expression */
4582 *env
= mail_newenvelope (); /* parse the new envelope */
4583 (*env
)->date
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4584 (*env
)->subject
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4585 (*env
)->from
= imap_parse_adrlist (stream
,txtptr
,reply
);
4586 (*env
)->sender
= imap_parse_adrlist (stream
,txtptr
,reply
);
4587 (*env
)->reply_to
= imap_parse_adrlist (stream
,txtptr
,reply
);
4588 (*env
)->to
= imap_parse_adrlist (stream
,txtptr
,reply
);
4589 (*env
)->cc
= imap_parse_adrlist (stream
,txtptr
,reply
);
4590 (*env
)->bcc
= imap_parse_adrlist (stream
,txtptr
,reply
);
4591 (*env
)->in_reply_to
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
4593 (*env
)->message_id
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4594 if (oenv
) { /* need to merge old envelope? */
4595 (*env
)->newsgroups
= oenv
->newsgroups
;
4596 oenv
->newsgroups
= NIL
;
4597 (*env
)->ngpathexists
= oenv
->ngpathexists
;
4598 (*env
)->followup_to
= oenv
->followup_to
;
4599 oenv
->followup_to
= NIL
;
4600 (*env
)->references
= oenv
->references
;
4601 oenv
->references
= NIL
;
4602 mail_free_envelope(&oenv
);/* free old envelope */
4604 /* have IMAP envelope components only */
4605 else (*env
)->imapenvonly
= T
;
4606 if (**txtptr
!= ')') {
4607 sprintf (LOCAL
->tmp
,"Junk at end of envelope: %.80s",(char *) *txtptr
);
4608 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4609 stream
->unhealthy
= T
;
4611 else ++*txtptr
; /* skip past delimiter */
4613 case 'N': /* if NIL */
4615 ++*txtptr
; /* bump past "I" */
4616 ++*txtptr
; /* bump past "L" */
4619 sprintf (LOCAL
->tmp
,"Not an envelope: %.80s",(char *) *txtptr
);
4620 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4621 stream
->unhealthy
= T
;
4626 /* IMAP parse address list
4627 * Accepts: MAIL stream
4628 * current text pointer
4630 * Returns: address list, NIL on failure
4632 * Updates text pointer
4635 ADDRESS
*imap_parse_adrlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
4636 IMAPPARSEDREPLY
*reply
)
4639 char c
= **txtptr
; /* sniff at first character */
4640 /* ignore leading spaces */
4641 while (c
== ' ') c
= *++*txtptr
;
4642 ++*txtptr
; /* skip past open paren */
4644 case '(': /* if envelope S-expression */
4645 adr
= imap_parse_address (stream
,txtptr
,reply
);
4646 if (**txtptr
!= ')') {
4647 sprintf (LOCAL
->tmp
,"Junk at end of address list: %.80s",
4649 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4650 stream
->unhealthy
= T
;
4652 else ++*txtptr
; /* skip past delimiter */
4654 case 'N': /* if NIL */
4656 ++*txtptr
; /* bump past "I" */
4657 ++*txtptr
; /* bump past "L" */
4660 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
4661 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4662 stream
->unhealthy
= T
;
4668 /* IMAP parse address
4669 * Accepts: MAIL stream
4670 * current text pointer
4672 * Returns: address, NIL on failure
4674 * Updates text pointer
4677 ADDRESS
*imap_parse_address (MAILSTREAM
*stream
,unsigned char **txtptr
,
4678 IMAPPARSEDREPLY
*reply
)
4683 ADDRESS
*prev
= NIL
;
4684 char c
= **txtptr
; /* sniff at first address character */
4686 case '(': /* if envelope S-expression */
4687 while (c
== '(') { /* recursion dies on small stack machines */
4688 ++*txtptr
; /* skip past open paren */
4689 if (adr
) prev
= adr
; /* note previous if any */
4690 adr
= mail_newaddr (); /* instantiate address and parse its fields */
4691 adr
->personal
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4692 adr
->adl
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4693 adr
->mailbox
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4694 adr
->host
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
4695 if (**txtptr
!= ')') { /* handle trailing paren */
4696 sprintf (LOCAL
->tmp
,"Junk at end of address: %.80s",(char *) *txtptr
);
4697 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4698 stream
->unhealthy
= T
;
4700 else ++*txtptr
; /* skip past close paren */
4701 c
= **txtptr
; /* set up for while test */
4702 /* ignore leading spaces in front of next */
4703 while (c
== ' ') c
= *++*txtptr
;
4705 if (!adr
->mailbox
) { /* end of group? */
4706 /* decrement group if all looks well */
4707 if (ingroup
&& !(adr
->personal
|| adr
->adl
|| adr
->host
)) --ingroup
;
4709 if (ingroup
) { /* in a group? */
4710 sprintf (LOCAL
->tmp
,/* yes, must be bad syntax */
4711 "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
4712 adr
->personal
? adr
->personal
: "",
4713 adr
->adl
? adr
->adl
: "",
4714 adr
->host
? adr
->host
: "");
4715 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4717 else mm_notify (stream
,"End of group encountered when not in group",
4719 stream
->unhealthy
= T
;
4720 mail_free_address (&adr
);
4725 else if (!adr
->host
) { /* start of group? */
4726 if (adr
->personal
|| adr
->adl
) {
4727 sprintf (LOCAL
->tmp
,"Junk in start of group: pn=%.80s al=%.80s",
4728 adr
->personal
? adr
->personal
: "",
4729 adr
->adl
? adr
->adl
: "");
4730 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4731 stream
->unhealthy
= T
;
4732 mail_free_address (&adr
);
4736 else ++ingroup
; /* in a group now */
4738 if (adr
) { /* good address */
4739 if (!ret
) ret
= adr
; /* if first time note first adr */
4740 /* if previous link new block to it */
4741 if (prev
) prev
->next
= adr
;
4742 /* flush bogus personal name */
4743 if (LOCAL
->loser
&& adr
->personal
&& strchr (adr
->personal
,'@'))
4744 fs_give ((void **) &adr
->personal
);
4748 case 'N': /* if NIL */
4750 *txtptr
+= 3; /* bump past NIL */
4753 sprintf (LOCAL
->tmp
,"Not an address: %.80s",(char *) *txtptr
);
4754 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4755 stream
->unhealthy
= T
;
4762 * Accepts: current message cache
4763 * current text pointer
4765 * Updates text pointer
4768 void imap_parse_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
,
4769 unsigned char **txtptr
)
4773 struct { /* old flags */
4774 unsigned int valid
: 1;
4775 unsigned int seen
: 1;
4776 unsigned int deleted
: 1;
4777 unsigned int flagged
: 1;
4778 unsigned int answered
: 1;
4779 unsigned int draft
: 1;
4780 unsigned long user_flags
;
4782 old
.valid
= elt
->valid
; old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
;
4783 old
.flagged
= elt
->flagged
; old
.answered
= elt
->answered
;
4784 old
.draft
= elt
->draft
; old
.user_flags
= elt
->user_flags
;
4785 elt
->valid
= T
; /* mark have valid flags now */
4786 elt
->user_flags
= NIL
; /* zap old flag values */
4787 elt
->seen
= elt
->deleted
= elt
->flagged
= elt
->answered
= elt
->draft
=
4789 while (c
!= ')') { /* parse list of flags */
4790 /* point at a flag */
4791 while (*(flag
= ++*txtptr
) == ' ');
4792 /* scan for end of flag */
4793 while (**txtptr
!= ' ' && **txtptr
!= ')') ++*txtptr
;
4794 c
= **txtptr
; /* save delimiter */
4795 **txtptr
= '\0'; /* tie off flag */
4796 if (!*flag
) break; /* null flag */
4797 /* if starts with \ must be sys flag */
4798 else if (*flag
== '\\') {
4799 if (!compare_cstring (flag
,"\\Seen")) elt
->seen
= T
;
4800 else if (!compare_cstring (flag
,"\\Deleted")) elt
->deleted
= T
;
4801 else if (!compare_cstring (flag
,"\\Flagged")) elt
->flagged
= T
;
4802 else if (!compare_cstring (flag
,"\\Answered")) elt
->answered
= T
;
4803 else if (!compare_cstring (flag
,"\\Recent")) elt
->recent
= T
;
4804 else if (!compare_cstring (flag
,"\\Draft")) elt
->draft
= T
;
4806 /* otherwise user flag */
4807 else elt
->user_flags
|= imap_parse_user_flag (stream
,flag
);
4809 ++*txtptr
; /* bump past delimiter */
4810 if (!old
.valid
|| (old
.seen
!= elt
->seen
) ||
4811 (old
.deleted
!= elt
->deleted
) || (old
.flagged
!= elt
->flagged
) ||
4812 (old
.answered
!= elt
->answered
) || (old
.draft
!= elt
->draft
) ||
4813 (old
.user_flags
!= elt
->user_flags
)) mm_flags (stream
,elt
->msgno
);
4817 /* IMAP parse user flag
4818 * Accepts: MAIL stream
4820 * Returns: flag bit position
4823 unsigned long imap_parse_user_flag (MAILSTREAM
*stream
,char *flag
)
4826 /* sniff through all user flags */
4827 for (i
= 0; i
< NUSERFLAGS
; ++i
) if (stream
->user_flags
[i
])
4828 if (!compare_cstring (flag
,stream
->user_flags
[i
])) return (1 << i
);
4829 return (unsigned long) 0; /* not found */
4832 /* IMAP parse atom-string
4833 * Accepts: MAIL stream
4834 * current text pointer
4836 * returned string length
4839 * Updates text pointer
4842 unsigned char *imap_parse_astring (MAILSTREAM
*stream
,unsigned char **txtptr
,
4843 IMAPPARSEDREPLY
*reply
,unsigned long *len
)
4846 unsigned char c
,*s
,*ret
;
4847 /* ignore leading spaces */
4848 for (c
= **txtptr
; c
== ' '; c
= *++*txtptr
);
4850 case '"': /* quoted string? */
4851 case '{': /* literal? */
4852 ret
= imap_parse_string (stream
,txtptr
,reply
,NIL
,len
,NIL
);
4854 default: /* must be atom */
4855 for (c
= *(s
= *txtptr
); /* find end of atom */
4856 c
&& (c
> ' ') && (c
!= '(') && (c
!= ')') && (c
!= '{') &&
4857 (c
!= '%') && (c
!= '*') && (c
!= '"') && (c
!= '\\') && (c
< 0x80);
4859 if (i
= *txtptr
- s
) { /* atom ends at atom_special */
4860 if (len
) *len
= i
; /* return length of atom */
4861 ret
= strncpy ((char *) fs_get (i
+ 1),s
,i
);
4862 ret
[i
] = '\0'; /* tie off string */
4864 else { /* no atom found */
4865 sprintf (LOCAL
->tmp
,"Not an atom: %.80s",(char *) *txtptr
);
4866 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4867 stream
->unhealthy
= T
;
4876 /* IMAP parse string
4877 * Accepts: MAIL stream
4878 * current text pointer
4881 * returned string length
4882 * filter newline flag
4885 * Updates text pointer
4888 unsigned char *imap_parse_string (MAILSTREAM
*stream
,unsigned char **txtptr
,
4889 IMAPPARSEDREPLY
*reply
,GETS_DATA
*md
,
4890 unsigned long *len
,long flags
)
4894 unsigned long i
,j
,k
;
4896 unsigned char c
= **txtptr
; /* sniff at first character */
4897 mailgets_t mg
= (mailgets_t
) mail_parameters (NIL
,GET_GETS
,NIL
);
4899 (readprogress_t
) mail_parameters (NIL
,GET_READPROGRESS
,NIL
);
4900 /* ignore leading spaces */
4901 while (c
== ' ') c
= *++*txtptr
;
4902 st
= ++*txtptr
; /* remember start of string */
4904 case '"': /* if quoted string */
4905 i
= 0; /* initial byte count */
4906 /* search for end of string */
4907 for (c
= **txtptr
; c
!= '"'; ++i
,c
= *++*txtptr
) {
4908 /* backslash quotes next character */
4909 if (c
== '\\') c
= *++*txtptr
;
4910 /* CHAR8 not permitted in quoted string */
4911 if (!bogon
&& (bogon
= (c
& 0x80))) {
4912 sprintf (LOCAL
->tmp
,"Invalid CHAR in quoted string: %x",
4914 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4915 stream
->unhealthy
= T
;
4917 else if (!c
) { /* NUL not permitted either */
4918 mm_notify (stream
,"Unterminated quoted string",WARN
);
4919 stream
->unhealthy
= T
;
4920 if (len
) *len
= 0; /* punt, since may be at end of string */
4924 ++*txtptr
; /* bump past delimiter */
4925 string
= (char *) fs_get ((size_t) i
+ 1);
4926 for (j
= 0; j
< i
; j
++) { /* copy the string */
4927 if (*st
== '\\') ++st
; /* quoted character */
4930 string
[j
] = '\0'; /* tie off string */
4931 if (len
) *len
= i
; /* set return value too */
4932 if (md
&& mg
) { /* have special routine to slurp string? */
4934 if (md
->first
) { /* partial fetch? */
4935 md
->first
--; /* restore origin octet */
4936 md
->last
= i
; /* number of octets that we got */
4938 INIT (&bs
,mail_string
,string
,i
);
4939 (*mg
) (mail_read
,&bs
,i
,md
);
4943 case 'N': /* if NIL */
4945 ++*txtptr
; /* bump past "I" */
4946 ++*txtptr
; /* bump past "L" */
4949 case '{': /* if literal string */
4950 /* get size of string */
4951 if ((i
= strtoul (*txtptr
,(char **) txtptr
,10)) > MAXSERVERLIT
) {
4952 sprintf (LOCAL
->tmp
,"Absurd server literal length %lu",i
);
4953 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4954 stream
->unhealthy
= T
; /* read and discard */
4955 do net_getbuffer (LOCAL
->netstream
,j
= min (i
,(long) IMAPTMPLEN
- 1),
4959 if (len
) *len
= i
; /* set return value */
4960 if (md
&& mg
) { /* have special routine to slurp string? */
4961 if (md
->first
) { /* partial fetch? */
4962 md
->first
--; /* restore origin octet */
4963 md
->last
= i
; /* number of octets that we got */
4965 else md
->flags
|= MG_COPY
;/* otherwise flag need to copy */
4966 string
= (*mg
) (net_getbuffer
,LOCAL
->netstream
,i
,md
);
4968 else { /* must slurp into free storage */
4969 string
= (char *) fs_get ((size_t) i
+ 1);
4970 *string
= '\0'; /* init in case getbuffer fails */
4971 /* get the literal */
4972 if (rp
) for (k
= 0; j
= min ((long) MAILTMPLEN
,(long) i
); i
-= j
) {
4973 net_getbuffer (LOCAL
->netstream
,j
,string
+ k
);
4976 else net_getbuffer (LOCAL
->netstream
,i
,string
);
4978 fs_give ((void **) &reply
->line
);
4979 if (flags
&& string
) /* need to filter newlines? */
4980 for (st
= string
; st
= strpbrk (st
,"\015\012\011"); *st
++ = ' ');
4981 /* get new reply text line */
4982 if (!(reply
->line
= net_getline (LOCAL
->netstream
)))
4983 reply
->line
= cpystr ("");
4984 if (stream
->debug
) mm_dlog (reply
->line
);
4985 *txtptr
= reply
->line
; /* set text pointer to point at it */
4988 sprintf (LOCAL
->tmp
,"Not a string: %c%.80s",c
,(char *) *txtptr
);
4989 mm_notify (stream
,LOCAL
->tmp
,WARN
);
4990 stream
->unhealthy
= T
;
4994 return (unsigned char *) string
;
4997 /* Register text in IMAP cache
4998 * Accepts: MAIL stream
5000 * IMAP segment specifier
5001 * header string list (if a HEADER section specifier)
5002 * sized text to register
5003 * Returns: non-zero if cache non-empty
5006 long imap_cache (MAILSTREAM
*stream
,unsigned long msgno
,char *seg
,
5007 STRINGLIST
*stl
,SIZEDTEXT
*text
)
5009 char *t
,tmp
[MAILTMPLEN
];
5014 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
5015 /* top-level header never does mailgets */
5016 if (!strcmp (seg
,"HEADER") || !strcmp (seg
,"0") ||
5017 !strcmp (seg
,"HEADER.FIELDS") || !strcmp (seg
,"HEADER.FIELDS.NOT")) {
5018 ret
= &elt
->private.msg
.header
.text
;
5019 if (text
) { /* don't do this if no text */
5020 if (ret
->data
) fs_give ((void **) &ret
->data
);
5021 mail_free_stringlist (&elt
->private.msg
.lines
);
5022 elt
->private.msg
.lines
= stl
;
5023 /* prevent cache reuse of .NOT */
5024 if ((seg
[0] == 'H') && (seg
[6] == '.') && (seg
[13] == '.'))
5025 for (stc
= stl
; stc
; stc
= stc
->next
) stc
->text
.size
= 0;
5026 if (stream
->scache
) { /* short caching puts it in the stream */
5027 if (stream
->msgno
!= msgno
) {
5028 /* flush old stuff */
5029 mail_free_envelope (&stream
->env
);
5030 mail_free_body (&stream
->body
);
5031 stream
->msgno
= msgno
;
5033 imap_parse_header (stream
,&stream
->env
,text
,stl
);
5035 /* regular caching */
5036 else imap_parse_header (stream
,&elt
->private.msg
.env
,text
,stl
);
5039 /* top level text */
5040 else if (!strcmp (seg
,"TEXT")) {
5041 ret
= &elt
->private.msg
.text
.text
;
5042 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5044 else if (!*seg
) { /* full message */
5045 ret
= &elt
->private.msg
.full
.text
;
5046 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5049 else { /* nested, find non-contents specifier */
5050 for (t
= seg
; *t
&& !((*t
== '.') && (isalpha(t
[1]) || !atol (t
+1))); t
++);
5051 if (*t
) *t
++ = '\0'; /* tie off section from data specifier */
5052 if (!(b
= mail_body (stream
,msgno
,seg
))) {
5053 sprintf (tmp
,"Unknown section number: %.80s",seg
);
5054 mm_notify (stream
,tmp
,WARN
);
5055 stream
->unhealthy
= T
;
5058 if (*t
) { /* if a non-numberic subpart */
5059 if ((i
= (b
->type
== TYPEMESSAGE
) && (!strcmp (b
->subtype
,"RFC822"))) &&
5060 (!strcmp (t
,"HEADER") || !strcmp (t
,"0") ||
5061 !strcmp (t
,"HEADER.FIELDS") || !strcmp (t
,"HEADER.FIELDS.NOT"))) {
5062 ret
= &b
->nested
.msg
->header
.text
;
5064 if (ret
->data
) fs_give ((void **) &ret
->data
);
5065 mail_free_stringlist (&b
->nested
.msg
->lines
);
5066 b
->nested
.msg
->lines
= stl
;
5067 /* prevent cache reuse of .NOT */
5068 if ((t
[0] == 'H') && (t
[6] == '.') && (t
[13] == '.'))
5069 for (stc
= stl
; stc
; stc
= stc
->next
) stc
->text
.size
= 0;
5070 imap_parse_header (stream
,&b
->nested
.msg
->env
,text
,stl
);
5073 else if (i
&& !strcmp (t
,"TEXT")) {
5074 ret
= &b
->nested
.msg
->text
.text
;
5075 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5077 /* otherwise it must be MIME */
5078 else if (!strcmp (t
,"MIME")) {
5079 ret
= &b
->mime
.text
;
5080 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5083 sprintf (tmp
,"Unknown section specifier: %.80s.%.80s",seg
,t
);
5084 mm_notify (stream
,tmp
,WARN
);
5085 stream
->unhealthy
= T
;
5089 else { /* ordinary contents */
5090 ret
= &b
->contents
.text
;
5091 if (text
&& ret
->data
) fs_give ((void **) &ret
->data
);
5094 if (text
) { /* update cache if requested */
5095 ret
->data
= text
->data
;
5096 ret
->size
= text
->size
;
5098 return ret
->data
? LONGT
: NIL
;
5101 /* IMAP parse body structure
5102 * Accepts: MAIL stream
5103 * body structure to write into
5104 * current text pointer
5107 * Updates text pointer
5110 void imap_parse_body_structure (MAILSTREAM
*stream
,BODY
*body
,
5111 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
5116 char c
= *((*txtptr
)++); /* grab first character */
5117 /* ignore leading spaces */
5118 while (c
== ' ') c
= *((*txtptr
)++);
5119 switch (c
) { /* dispatch on first character */
5120 case '(': /* body structure list */
5121 if (**txtptr
== '(') { /* multipart body? */
5122 body
->type
= TYPEMULTIPART
;/* yes, set its type */
5123 do { /* instantiate new body part */
5124 if (part
) part
= part
->next
= mail_newbody_part ();
5125 else body
->nested
.part
= part
= mail_newbody_part ();
5127 imap_parse_body_structure (stream
,&part
->body
,txtptr
,reply
);
5128 } while (**txtptr
== '(');/* for each body part */
5129 if (body
->subtype
= imap_parse_string(stream
,txtptr
,reply
,NIL
,NIL
,LONGT
))
5130 ucase (body
->subtype
);
5132 mm_notify (stream
,"Missing multipart subtype",WARN
);
5133 stream
->unhealthy
= T
;
5134 body
->subtype
= cpystr (rfc822_default_subtype (body
->type
));
5136 if (**txtptr
== ' ') /* multipart parameters */
5137 body
->parameter
= imap_parse_body_parameter (stream
,txtptr
,reply
);
5138 if (**txtptr
== ' ') { /* disposition */
5139 imap_parse_disposition (stream
,body
,txtptr
,reply
);
5140 if (LOCAL
->cap
.extlevel
< BODYEXTDSP
) LOCAL
->cap
.extlevel
= BODYEXTDSP
;
5142 if (**txtptr
== ' ') { /* language */
5143 body
->language
= imap_parse_language (stream
,txtptr
,reply
);
5144 if (LOCAL
->cap
.extlevel
< BODYEXTLANG
)
5145 LOCAL
->cap
.extlevel
= BODYEXTLANG
;
5147 if (**txtptr
== ' ') { /* location */
5148 body
->location
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5149 if (LOCAL
->cap
.extlevel
< BODYEXTLOC
) LOCAL
->cap
.extlevel
= BODYEXTLOC
;
5151 while (**txtptr
== ' ') imap_parse_extension (stream
,txtptr
,reply
);
5152 if (**txtptr
!= ')') { /* validate ending */
5153 sprintf (LOCAL
->tmp
,"Junk at end of multipart body: %.80s",
5155 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5156 stream
->unhealthy
= T
;
5158 else ++*txtptr
; /* skip past delimiter */
5161 else { /* not multipart, parse type name */
5162 if (**txtptr
== ')') { /* empty body? */
5163 ++*txtptr
; /* bump past it */
5164 break; /* and punt */
5166 body
->type
= TYPEOTHER
; /* assume unknown type */
5167 body
->encoding
= ENCOTHER
;/* and unknown encoding */
5169 if (s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) {
5170 ucase (s
); /* application always gets uppercase form */
5171 for (i
= 0; /* look in existing table */
5172 (i
<= TYPEMAX
) && body_types
[i
] && strcmp (s
,body_types
[i
]); i
++);
5173 if (i
<= TYPEMAX
) { /* only if found a slot */
5174 body
->type
= i
; /* set body type */
5175 if (body_types
[i
]) fs_give ((void **) &s
);
5176 else body_types
[i
]=s
; /* assign empty slot */
5179 if (body
->subtype
= imap_parse_string(stream
,txtptr
,reply
,NIL
,NIL
,LONGT
))
5180 ucase (body
->subtype
); /* parse subtype */
5182 mm_notify (stream
,"Missing body subtype",WARN
);
5183 stream
->unhealthy
= T
;
5184 body
->subtype
= cpystr (rfc822_default_subtype (body
->type
));
5186 body
->parameter
= imap_parse_body_parameter (stream
,txtptr
,reply
);
5187 body
->id
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5188 body
->description
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5190 if (s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
)) {
5191 ucase (s
); /* application always gets uppercase form */
5192 for (i
= 0; /* search for body encoding */
5193 (i
<= ENCMAX
) && body_encodings
[i
] && strcmp(s
,body_encodings
[i
]);
5195 if (i
> ENCMAX
) body
->encoding
= ENCOTHER
;
5196 else { /* only if found a slot */
5197 body
->encoding
= i
; /* set body encoding */
5198 if (body_encodings
[i
]) fs_give ((void **) &s
);
5199 /* assign empty slot */
5200 else body_encodings
[i
] = s
;
5203 \f /* parse size of contents in bytes */
5204 body
->size
.bytes
= strtoul (*txtptr
,(char **) txtptr
,10);
5205 switch (body
->type
) { /* possible extra stuff */
5206 case TYPEMESSAGE
: /* message envelope and body */
5207 /* non MESSAGE/RFC822 is basic type */
5208 if (strcmp (body
->subtype
,"RFC822")) break;
5209 { /* make certain server sends an envelope */
5210 ENVELOPE
*env
= NIL
;
5211 imap_parse_envelope (stream
,&env
,txtptr
,reply
);
5213 mm_notify (stream
,"Missing body message envelope",WARN
);
5214 stream
->unhealthy
= T
;
5215 body
->subtype
= cpystr ("RFC822_MISSING_ENVELOPE");
5218 (body
->nested
.msg
= mail_newmsg ())->env
= env
;
5220 body
->nested
.msg
->body
= mail_newbody ();
5221 imap_parse_body_structure (stream
,body
->nested
.msg
->body
,txtptr
,reply
);
5222 /* drop into text case */
5223 case TYPETEXT
: /* size in lines */
5224 body
->size
.lines
= strtoul (*txtptr
,(char **) txtptr
,10);
5226 default: /* otherwise nothing special */
5230 if (**txtptr
== ' ') { /* extension data - md5 */
5231 body
->md5
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5232 if (LOCAL
->cap
.extlevel
< BODYEXTMD5
) LOCAL
->cap
.extlevel
= BODYEXTMD5
;
5234 if (**txtptr
== ' ') { /* disposition */
5235 imap_parse_disposition (stream
,body
,txtptr
,reply
);
5236 if (LOCAL
->cap
.extlevel
< BODYEXTDSP
) LOCAL
->cap
.extlevel
= BODYEXTDSP
;
5238 if (**txtptr
== ' ') { /* language */
5239 body
->language
= imap_parse_language (stream
,txtptr
,reply
);
5240 if (LOCAL
->cap
.extlevel
< BODYEXTLANG
)
5241 LOCAL
->cap
.extlevel
= BODYEXTLANG
;
5243 if (**txtptr
== ' ') { /* location */
5244 body
->location
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
);
5245 if (LOCAL
->cap
.extlevel
< BODYEXTLOC
) LOCAL
->cap
.extlevel
= BODYEXTLOC
;
5247 while (**txtptr
== ' ') imap_parse_extension (stream
,txtptr
,reply
);
5248 if (**txtptr
!= ')') { /* validate ending */
5249 sprintf (LOCAL
->tmp
,"Junk at end of body part: %.80s",
5251 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5252 stream
->unhealthy
= T
;
5254 else ++*txtptr
; /* skip past delimiter */
5257 case 'N': /* if NIL */
5259 ++*txtptr
; /* bump past "I" */
5260 ++*txtptr
; /* bump past "L" */
5262 default: /* otherwise quite bogus */
5263 sprintf (LOCAL
->tmp
,"Bogus body structure: %.80s",(char *) *txtptr
);
5264 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5265 stream
->unhealthy
= T
;
5270 /* IMAP parse body parameter
5271 * Accepts: MAIL stream
5272 * current text pointer
5274 * Returns: body parameter
5275 * Updates text pointer
5278 PARAMETER
*imap_parse_body_parameter (MAILSTREAM
*stream
,
5279 unsigned char **txtptr
,
5280 IMAPPARSEDREPLY
*reply
)
5282 PARAMETER
*ret
= NIL
;
5283 PARAMETER
*par
= NIL
;
5285 /* ignore leading spaces */
5286 while ((c
= *(*txtptr
)++) == ' ');
5287 /* parse parameter list */
5288 if (c
== '(') while (c
!= ')') {
5289 /* append new parameter to tail */
5290 if (ret
) par
= par
->next
= mail_newbody_parameter ();
5291 else ret
= par
= mail_newbody_parameter ();
5292 if(!(par
->attribute
=imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5294 mm_notify (stream
,"Missing parameter attribute",WARN
);
5295 stream
->unhealthy
= T
;
5296 par
->attribute
= cpystr ("UNKNOWN");
5298 if (!(par
->value
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,LONGT
))){
5299 sprintf (LOCAL
->tmp
,"Missing value for parameter %.80s",par
->attribute
);
5300 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5301 stream
->unhealthy
= T
;
5302 par
->value
= cpystr ("UNKNOWN");
5304 switch (c
= **txtptr
) { /* see what comes after */
5305 case ' ': /* flush whitespace */
5306 while ((c
= *++*txtptr
) == ' ');
5308 case ')': /* end of attribute/value pairs */
5309 ++*txtptr
; /* skip past closing paren */
5312 sprintf (LOCAL
->tmp
,"Junk at end of parameter: %.80s",(char *) *txtptr
);
5313 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5314 stream
->unhealthy
= T
;
5318 /* empty parameter, must be NIL */
5319 else if (((c
== 'N') || (c
== 'n')) &&
5320 ((*(s
= *txtptr
) == 'I') || (*s
== 'i')) &&
5321 ((s
[1] == 'L') || (s
[1] == 'l'))) *txtptr
+= 2;
5323 sprintf (LOCAL
->tmp
,"Bogus body parameter: %c%.80s",c
,
5324 (char *) (*txtptr
) - 1);
5325 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5326 stream
->unhealthy
= T
;
5331 /* IMAP parse body disposition
5332 * Accepts: MAIL stream
5333 * body structure to write into
5334 * current text pointer
5338 void imap_parse_disposition (MAILSTREAM
*stream
,BODY
*body
,
5339 unsigned char **txtptr
,IMAPPARSEDREPLY
*reply
)
5341 switch (*++*txtptr
) {
5343 ++*txtptr
; /* skip open paren */
5344 body
->disposition
.type
= imap_parse_string (stream
,txtptr
,reply
,NIL
,NIL
,
5346 body
->disposition
.parameter
=
5347 imap_parse_body_parameter (stream
,txtptr
,reply
);
5348 if (**txtptr
!= ')') { /* validate ending */
5349 sprintf (LOCAL
->tmp
,"Junk at end of disposition: %.80s",
5351 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5352 stream
->unhealthy
= T
;
5354 else ++*txtptr
; /* skip past delimiter */
5356 case 'N': /* if NIL */
5358 ++*txtptr
; /* bump past "N" */
5359 ++*txtptr
; /* bump past "I" */
5360 ++*txtptr
; /* bump past "L" */
5363 sprintf (LOCAL
->tmp
,"Unknown body disposition: %.80s",(char *) *txtptr
);
5364 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5365 stream
->unhealthy
= T
;
5366 /* try to skip to next space */
5367 while ((*++*txtptr
!= ' ') && (**txtptr
!= ')') && **txtptr
);
5372 /* IMAP parse body language
5373 * Accepts: MAIL stream
5374 * current text pointer
5376 * Returns: string list or NIL if empty or error
5379 STRINGLIST
*imap_parse_language (MAILSTREAM
*stream
,unsigned char **txtptr
,
5380 IMAPPARSEDREPLY
*reply
)
5384 STRINGLIST
*ret
= NIL
;
5385 /* language is a list */
5386 if (*++*txtptr
== '(') ret
= imap_parse_stringlist (stream
,txtptr
,reply
);
5387 else if (s
= imap_parse_string (stream
,txtptr
,reply
,NIL
,&i
,LONGT
)) {
5388 (ret
= mail_newstringlist ())->text
.data
= (unsigned char *) s
;
5394 /* IMAP parse string list
5395 * Accepts: MAIL stream
5396 * current text pointer
5398 * Returns: string list or NIL if empty or error
5401 STRINGLIST
*imap_parse_stringlist (MAILSTREAM
*stream
,unsigned char **txtptr
,
5402 IMAPPARSEDREPLY
*reply
)
5404 STRINGLIST
*stl
= NIL
;
5405 STRINGLIST
*stc
= NIL
;
5406 unsigned char *t
= *txtptr
;
5407 /* parse the list */
5408 if (*t
++ == '(') while (*t
!= ')') {
5409 if (stl
) stc
= stc
->next
= mail_newstringlist ();
5410 else stc
= stl
= mail_newstringlist ();
5412 if (!(stc
->text
.data
=
5413 imap_parse_astring (stream
,&t
,reply
,&stc
->text
.size
))) {
5414 sprintf (LOCAL
->tmp
,"Bogus string list member: %.80s",(char *) t
);
5415 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5416 stream
->unhealthy
= T
;
5417 mail_free_stringlist (&stl
);
5420 else if (*t
== ' ') ++t
; /* another token follows */
5422 if (stl
) *txtptr
= ++t
; /* update return string */
5426 /* IMAP parse unknown body extension data
5427 * Accepts: MAIL stream
5428 * current text pointer
5431 * Updates text pointer
5434 void imap_parse_extension (MAILSTREAM
*stream
,unsigned char **txtptr
,
5435 IMAPPARSEDREPLY
*reply
)
5438 switch (*++*txtptr
) { /* action depends upon first character */
5440 while (**txtptr
!= ')') imap_parse_extension (stream
,txtptr
,reply
);
5441 ++*txtptr
; /* bump past closing parenthesis */
5443 case '"': /* if quoted string */
5444 while (*++*txtptr
!= '"') if (**txtptr
== '\\') ++*txtptr
;
5445 ++*txtptr
; /* bump past closing quote */
5447 case 'N': /* if NIL */
5449 ++*txtptr
; /* bump past "N" */
5450 ++*txtptr
; /* bump past "I" */
5451 ++*txtptr
; /* bump past "L" */
5453 case '{': /* get size of literal */
5454 ++*txtptr
; /* bump past open squiggle */
5455 if (i
= strtoul (*txtptr
,(char **) txtptr
,10)) do
5456 net_getbuffer (LOCAL
->netstream
,j
= min (i
,(long) IMAPTMPLEN
- 1),
5459 /* get new reply text line */
5460 if (!(reply
->line
= net_getline (LOCAL
->netstream
)))
5461 reply
->line
= cpystr ("");
5462 if (stream
->debug
) mm_dlog (reply
->line
);
5463 *txtptr
= reply
->line
; /* set text pointer to point at it */
5465 case '0': case '1': case '2': case '3': case '4':
5466 case '5': case '6': case '7': case '8': case '9':
5467 strtoul (*txtptr
,(char **) txtptr
,10);
5470 sprintf (LOCAL
->tmp
,"Unknown extension token: %.80s",(char *) *txtptr
);
5471 mm_notify (stream
,LOCAL
->tmp
,WARN
);
5472 stream
->unhealthy
= T
;
5473 /* try to skip to next space */
5474 while ((*++*txtptr
!= ' ') && (**txtptr
!= ')') && **txtptr
);
5479 /* IMAP parse capabilities
5480 * Accepts: MAIL stream
5484 void imap_parse_capabilities (MAILSTREAM
*stream
,char *t
)
5489 if (!LOCAL
->gotcapability
) { /* need to save previous capabilities? */
5490 /* no, flush threaders */
5491 if (thr
= LOCAL
->cap
.threader
) while (th
= thr
) {
5492 fs_give ((void **) &th
->name
);
5494 fs_give ((void **) &th
);
5496 /* zap capabilities */
5497 memset (&LOCAL
->cap
,0,sizeof (LOCAL
->cap
));
5498 LOCAL
->gotcapability
= T
; /* flag that capabilities arrived */
5500 for (t
= strtok_r (t
," ",&r
); t
; t
= strtok_r (NIL
," ",&r
)) {
5501 if (!compare_cstring (t
,"IMAP4"))
5502 LOCAL
->cap
.imap4
= LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5503 else if (!compare_cstring (t
,"IMAP4rev1"))
5504 LOCAL
->cap
.imap4rev1
= LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5505 else if (!compare_cstring (t
,"IMAP2")) LOCAL
->cap
.rfc1176
= T
;
5506 else if (!compare_cstring (t
,"IMAP2bis"))
5507 LOCAL
->cap
.imap2bis
= LOCAL
->cap
.rfc1176
= T
;
5508 else if (!compare_cstring (t
,"ACL")) LOCAL
->cap
.acl
= T
;
5509 else if (!compare_cstring (t
,"QUOTA")) LOCAL
->cap
.quota
= T
;
5510 else if (!compare_cstring (t
,"LITERAL+")) LOCAL
->cap
.litplus
= T
;
5511 else if (!compare_cstring (t
,"IDLE")) LOCAL
->cap
.idle
= T
;
5512 else if (!compare_cstring (t
,"MAILBOX-REFERRALS")) LOCAL
->cap
.mbx_ref
= T
;
5513 else if (!compare_cstring (t
,"LOGIN-REFERRALS")) LOCAL
->cap
.log_ref
= T
;
5514 else if (!compare_cstring (t
,"NAMESPACE")) LOCAL
->cap
.namespace = T
;
5515 else if (!compare_cstring (t
,"UIDPLUS")) LOCAL
->cap
.uidplus
= T
;
5516 else if (!compare_cstring (t
,"STARTTLS")) LOCAL
->cap
.starttls
= T
;
5517 else if (!compare_cstring (t
,"LOGINDISABLED"))LOCAL
->cap
.logindisabled
= T
;
5518 else if (!compare_cstring (t
,"ID")) LOCAL
->cap
.id
= T
;
5519 else if (!compare_cstring (t
,"CHILDREN")) LOCAL
->cap
.children
= T
;
5520 else if (!compare_cstring (t
,"MULTIAPPEND")) LOCAL
->cap
.multiappend
= T
;
5521 else if (!compare_cstring (t
,"BINARY")) LOCAL
->cap
.binary
= T
;
5522 else if (!compare_cstring (t
,"UNSELECT")) LOCAL
->cap
.unselect
= T
;
5523 else if (!compare_cstring (t
,"SASL-IR")) LOCAL
->cap
.sasl_ir
= T
;
5524 else if (!compare_cstring (t
,"SCAN")) LOCAL
->cap
.scan
= T
;
5525 else if (!compare_cstring (t
,"URLAUTH")) LOCAL
->cap
.urlauth
= T
;
5526 else if (!compare_cstring (t
,"CATENATE")) LOCAL
->cap
.catenate
= T
;
5527 else if (!compare_cstring (t
,"CONDSTORE")) LOCAL
->cap
.condstore
= T
;
5528 else if (!compare_cstring (t
,"ESEARCH")) LOCAL
->cap
.esearch
= T
;
5529 else if (((t
[0] == 'S') || (t
[0] == 's')) &&
5530 ((t
[1] == 'O') || (t
[1] == 'o')) &&
5531 ((t
[2] == 'R') || (t
[2] == 'r')) &&
5532 ((t
[3] == 'T') || (t
[3] == 't'))) LOCAL
->cap
.sort
= T
;
5533 /* capability with value? */
5534 else if (s
= strchr (t
,'=')) {
5535 *s
++ = '\0'; /* separate token from value */
5536 if (!compare_cstring (t
,"THREAD") && !LOCAL
->loser
) {
5537 THREADER
*thread
= (THREADER
*) fs_get (sizeof (THREADER
));
5538 thread
->name
= cpystr (s
);
5539 thread
->dispatch
= NIL
;
5540 thread
->next
= LOCAL
->cap
.threader
;
5541 LOCAL
->cap
.threader
= thread
;
5543 else if (!compare_cstring (t
,"AUTH")) {
5544 if ((i
= mail_lookup_auth_name (s
,LOCAL
->authflags
)) &&
5545 (--i
< MAXAUTHENTICATORS
)) LOCAL
->cap
.auth
|= (1 << i
);
5546 else if (!compare_cstring (s
,"ANONYMOUS")) LOCAL
->cap
.authanon
= T
;
5549 /* ignore other capabilities */
5551 /* disable LOGIN if PLAIN also advertised */
5552 if ((i
= mail_lookup_auth_name ("PLAIN",NIL
)) && (--i
< MAXAUTHENTICATORS
) &&
5553 (LOCAL
->cap
.auth
& (1 << i
)) &&
5554 (i
= mail_lookup_auth_name ("LOGIN",NIL
)) && (--i
< MAXAUTHENTICATORS
))
5555 LOCAL
->cap
.auth
&= ~(1 << i
);
5559 * Accepts: MAIL stream
5562 * Returns: parsed reply from fetch
5565 IMAPPARSEDREPLY
*imap_fetch (MAILSTREAM
*stream
,char *sequence
,long flags
)
5568 char *cmd
= (LEVELIMAP4 (stream
) && (flags
& FT_UID
)) ?
5569 "UID FETCH" : "FETCH";
5570 IMAPARG
*args
[9],aseq
,aarg
,aenv
,ahhr
,axtr
,ahtr
,abdy
,atrl
;
5571 if (LOCAL
->loser
) sequence
= imap_reform_sequence (stream
,sequence
,
5573 args
[0] = &aseq
; aseq
.type
= SEQUENCE
; aseq
.text
= (void *) sequence
;
5574 args
[1] = &aarg
; aarg
.type
= ATOM
;
5575 aenv
.type
= ATOM
; aenv
.text
= (void *) "ENVELOPE";
5576 ahhr
.type
= ATOM
; ahhr
.text
= (void *) hdrheader
[LOCAL
->cap
.extlevel
];
5577 axtr
.type
= ATOM
; axtr
.text
= (void *) imap_extrahdrs
;
5578 ahtr
.type
= ATOM
; ahtr
.text
= (void *) hdrtrailer
;
5579 abdy
.type
= ATOM
; abdy
.text
= (void *) "BODYSTRUCTURE";
5580 atrl
.type
= ATOM
; atrl
.text
= (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
5581 if (LEVELIMAP4 (stream
)) { /* include UID if IMAP4 or IMAP4rev1 */
5582 aarg
.text
= (void *) "(UID";
5583 if (flags
& FT_NEEDENV
) { /* if need envelopes */
5584 args
[i
++] = &aenv
; /* include envelope */
5585 /* extra header poop if IMAP4rev1 */
5586 if (!(flags
& FT_NOHDRS
) && LEVELIMAP4rev1 (stream
)) {
5587 args
[i
++] = &ahhr
; /* header header */
5588 if (axtr
.text
) args
[i
++] = &axtr
;
5589 args
[i
++] = &ahtr
; /* header trailer */
5591 /* fetch body if requested */
5592 if (flags
& FT_NEEDBODY
) args
[i
++] = &abdy
;
5594 args
[i
++] = &atrl
; /* fetch trailer */
5597 else aarg
.text
= (void *) (flags
& FT_NEEDENV
) ?
5598 ((flags
& FT_NEEDBODY
) ?
5599 "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
5600 "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
5601 args
[i
] = NIL
; /* tie off command */
5602 return imap_send (stream
,cmd
,args
);
5605 /* Reform sequence for losing server that doesn't handle ranges right
5606 * Accepts: MAIL stream
5612 char *imap_reform_sequence (MAILSTREAM
*stream
,char *sequence
,long flags
)
5614 unsigned long i
,j
,star
;
5616 /* can't win if empty */
5617 if (!stream
->nmsgs
) return sequence
;
5618 /* get highest possible range value */
5619 star
= flags
? mail_uid (stream
,stream
->nmsgs
) : stream
->nmsgs
;
5620 /* flush old reformed sequence */
5621 if (LOCAL
->reform
) fs_give ((void **) &LOCAL
->reform
);
5622 rs
= LOCAL
->reform
= (char *) fs_get (1+ strlen (sequence
));
5623 for (s
= sequence
; t
= strpbrk (s
,",:"); ) switch (*t
++) {
5624 case ',': /* single message */
5625 strncpy (rs
,s
,i
= t
- s
); /* copy string up to that point */
5626 rs
+= i
; /* advance destination pointer */
5627 s
+= i
; /* and source */
5629 case ':': /* message range */
5630 i
= (*s
== '*') ? star
: strtoul (s
,NIL
,10);
5631 if (*t
== '*') { /* range ends with star */
5635 else { /* numeric range end */
5636 j
= strtoul (t
,(char **) &tl
,10);
5637 if (!tl
) tl
= t
+ strlen (t
);
5639 if (i
<= j
) { /* if first less than second */
5640 if (*tl
) tl
++; /* skip past end of range if present */
5641 strncpy (rs
,s
,i
= tl
- s
);/* copy string up to that point */
5642 rs
+= i
; /* advance destination and source pointers */
5645 else { /* here's the workaround for losing servers */
5646 strncpy (rs
,t
,i
= tl
- t
);/* swap the order */
5647 rs
[i
] = ':'; /* delimit */
5648 strncpy (rs
+i
+1,s
,j
= (t
-1) - s
);
5649 rs
+= i
+ 1 + j
; /* advance destination pointer */
5650 if (*tl
) *rs
++ = *tl
++; /* write trailing delimiter if present */
5651 s
= tl
; /* advance source pointer */
5654 if (*s
) strcpy (rs
,s
); /* write remainder of sequence */
5655 else *rs
= '\0'; /* tie off string */
5656 return LOCAL
->reform
;
5659 /* IMAP return host name
5660 * Accepts: MAIL stream
5661 * Returns: host name
5664 char *imap_host (MAILSTREAM
*stream
)
5666 if (stream
->dtb
!= &imapdriver
)
5667 fatal ("imap_host called on non-IMAP stream!");
5668 /* return host name on stream if open */
5669 return (LOCAL
&& LOCAL
->netstream
) ? net_host (LOCAL
->netstream
) :
5670 ".NO-IMAP-CONNECTION.";
5674 /* IMAP return IMAP capability structure
5675 * Accepts: MAIL stream
5676 * Returns: IMAP capability structure
5679 IMAPCAP
*imap_cap (MAILSTREAM
*stream
)
5681 if (stream
->dtb
!= &imapdriver
)
5682 fatal ("imap_cap called on non-IMAP stream!");
5683 return &LOCAL
->cap
; /* return capability structure */