* Create /starttls as a synonym of /tls. Update the documentation to use
[alpine.git] / imap / src / c-client / mail.c
blob90020d5d141e15d02771280a26a8775f6bde6b18
1 /* ========================================================================
2 * Copyright 2008-2010 Mark Crispin
3 * ========================================================================
4 */
6 /*
7 * Program: Mailbox Access routines
9 * Author: Mark Crispin
11 * Date: 22 November 1989
12 * Last Edited: 15 November 2010
14 * Previous versions of this file were
16 * Copyright 1988-2008 University of Washington
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
22 * http://www.apache.org/licenses/LICENSE-2.0
27 #include <ctype.h>
28 #include <stdio.h>
29 #include <time.h>
30 #include "c-client.h"
32 char *Panda_copyright = "Copyright 2008-2010 Mark Crispin\n";
34 char *UW_copyright = "Copyright 1988-2008 University of Washington\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n";
36 /* c-client global data */
37 /* version of this library */
38 static char *mailcclientversion = CCLIENTVERSION;
39 /* Minimum in range of encryption supported */
40 static int encryption_range_min = 0;
41 /* Maximum in range of encryption supported */
42 static int encryption_range_max = 0;
43 /* app identity */
44 static IDLIST *idapp = NIL;
45 /* list of mail drivers */
46 static DRIVER *maildrivers = NIL;
47 /* list of authenticators */
48 static AUTHENTICATOR *mailauthenticators = NIL;
49 /* SSL driver pointer */
50 static NETDRIVER *mailssldriver = NIL;
51 /* pointer to alternate gets function */
52 static mailgets_t mailgets = NIL;
53 /* pointer to read progress function */
54 static readprogress_t mailreadprogress = NIL;
55 /* mail cache manipulation function */
56 static mailcache_t mailcache = mm_cache;
57 /* RFC-822 output generator */
58 static rfc822out_t mail822out = NIL;
59 /* RFC-822 output generator (new style) */
60 static rfc822outfull_t mail822outfull = NIL;
61 /* SMTP verbose callback */
62 static smtpverbose_t mailsmtpverbose = mm_dlog;
63 /* proxy copy routine */
64 static mailproxycopy_t mailproxycopy = NIL;
65 /* RFC-822 external line parse */
66 static parseline_t mailparseline = NIL;
67 /* RFC-822 external phrase parser */
68 static parsephrase_t mailparsephrase = NIL;
69 static kinit_t mailkinit = NIL; /* application kinit callback */
70 /* note network sent command */
71 static sendcommand_t mailsendcommand = NIL;
72 /* newsrc file name decision function */
73 static newsrcquery_t mailnewsrcquery = NIL;
74 /* ACL results callback */
75 static getacl_t mailaclresults = NIL;
76 /* list rights results callback */
77 static listrights_t maillistrightsresults = NIL;
78 /* my rights results callback */
79 static myrights_t mailmyrightsresults = NIL;
80 /* quota results callback */
81 static quota_t mailquotaresults = NIL;
82 /* quota root results callback */
83 static quotaroot_t mailquotarootresults = NIL;
84 /* sorted results callback */
85 static sortresults_t mailsortresults = NIL;
86 /* threaded results callback */
87 static threadresults_t mailthreadresults = NIL;
88 /* COPY UID results */
89 static copyuid_t mailcopyuid = NIL;
90 /* APPEND UID results */
91 static appenduid_t mailappenduid = NIL;
93 static oauth2getaccesscode_t oauth2getaccesscode = NIL;
95 static oauth2clientinfo_t oauth2clientinfo = NIL;
96 /* free elt extra stuff callback */
97 static freeeltsparep_t mailfreeeltsparep = NIL;
98 /* free envelope extra stuff callback */
99 static freeenvelopesparep_t mailfreeenvelopesparep = NIL;
100 /* free body extra stuff callback */
101 static freebodysparep_t mailfreebodysparep = NIL;
102 /* free stream extra stuff callback */
103 static freestreamsparep_t mailfreestreamsparep = NIL;
104 /* SSL start routine */
105 static sslstart_t mailsslstart = NIL;
106 /* SSL certificate query */
107 static sslcertificatequery_t mailsslcertificatequery = NIL;
108 /* SSL client certificate */
109 static sslclientcert_t mailsslclientcert = NIL;
110 /* SSL client private key */
111 static sslclientkey_t mailsslclientkey = NIL;
112 /* SSL failure notify */
113 static sslfailure_t mailsslfailure = NIL;
114 /* snarf interval */
115 static long mailsnarfinterval = 60;
116 /* snarf preservation */
117 static long mailsnarfpreserve = NIL;
118 /* newsrc name uses canonical host */
119 static long mailnewsrccanon = LONGT;
121 /* supported threaders */
122 static THREADER mailthreadordsub = {
123 "ORDEREDSUBJECT",mail_thread_orderedsubject,NIL
125 static THREADER mailthreadlist = {
126 "REFERENCES",mail_thread_references,&mailthreadordsub
129 /* server name */
130 static char *servicename = "unknown";
131 /* server externally-set authentication ID */
132 static char *externalauthid = NIL;
133 static int expungeatping = T; /* mail_ping() may call mm_expunged() */
134 static int trysslfirst = NIL; /* always try SSL first */
135 static int notimezones = NIL; /* write timezones in "From " header */
136 static int trustdns = T; /* do DNS canonicalization */
137 static int saslusesptrname = T; /* SASL uses name from DNS PTR lookup */
138 /* trustdns also must be set */
139 static int debugsensitive = NIL;/* debug telemetry includes sensitive data */
141 /* Default mail cache handler
142 * Accepts: pointer to cache handle
143 * message number
144 * caching function
145 * Returns: cache data
148 void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op)
150 size_t n;
151 void *ret = NIL;
152 unsigned long i;
153 switch ((int) op) { /* what function? */
154 case CH_INIT: /* initialize cache */
155 if (stream->cache) { /* flush old cache contents */
156 while (stream->cachesize) {
157 mm_cache (stream,stream->cachesize,CH_FREE);
158 mm_cache (stream,stream->cachesize--,CH_FREESORTCACHE);
160 fs_give ((void **) &stream->cache);
161 fs_give ((void **) &stream->sc);
162 stream->nmsgs = 0; /* can't have any messages now */
164 break;
165 case CH_SIZE: /* (re-)size the cache */
166 if (!stream->cache) { /* have a cache already? */
167 /* no, create new cache */
168 n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
169 stream->cache = (MESSAGECACHE **) memset (fs_get (n),0,n);
170 stream->sc = (SORTCACHE **) memset (fs_get (n),0,n);
172 /* is existing cache size large neough */
173 else if (msgno > stream->cachesize) {
174 i = stream->cachesize; /* remember old size */
175 n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
176 fs_resize ((void **) &stream->cache,n);
177 fs_resize ((void **) &stream->sc,n);
178 while (i < stream->cachesize) {
179 stream->cache[i] = NIL;
180 stream->sc[i++] = NIL;
183 break;
185 case CH_MAKEELT: /* return elt, make if necessary */
186 if (!stream->cache[msgno - 1])
187 stream->cache[msgno - 1] = mail_new_cache_elt (msgno);
188 /* falls through */
189 case CH_ELT: /* return elt */
190 ret = (void *) stream->cache[msgno - 1];
191 break;
192 case CH_SORTCACHE: /* return sortcache entry, make if needed */
193 if (!stream->sc[msgno - 1]) stream->sc[msgno - 1] =
194 (SORTCACHE *) memset (fs_get (sizeof (SORTCACHE)),0,sizeof (SORTCACHE));
195 ret = (void *) stream->sc[msgno - 1];
196 break;
197 case CH_FREE: /* free elt */
198 mail_free_elt (&stream->cache[msgno - 1]);
199 break;
200 case CH_FREESORTCACHE:
201 if (stream->sc[msgno - 1]) {
202 if (stream->sc[msgno - 1]->from)
203 fs_give ((void **) &stream->sc[msgno - 1]->from);
204 if (stream->sc[msgno - 1]->to)
205 fs_give ((void **) &stream->sc[msgno - 1]->to);
206 if (stream->sc[msgno - 1]->cc)
207 fs_give ((void **) &stream->sc[msgno - 1]->cc);
208 if (stream->sc[msgno - 1]->subject)
209 fs_give ((void **) &stream->sc[msgno - 1]->subject);
210 if (stream->sc[msgno - 1]->unique &&
211 (stream->sc[msgno - 1]->unique != stream->sc[msgno - 1]->message_id))
212 fs_give ((void **) &stream->sc[msgno - 1]->unique);
213 if (stream->sc[msgno - 1]->message_id)
214 fs_give ((void **) &stream->sc[msgno - 1]->message_id);
215 if (stream->sc[msgno - 1]->references)
216 mail_free_stringlist (&stream->sc[msgno - 1]->references);
217 fs_give ((void **) &stream->sc[msgno - 1]);
219 break;
220 case CH_EXPUNGE: /* expunge cache slot */
221 for (i = msgno - 1; msgno < stream->nmsgs; i++,msgno++) {
222 if ((stream->cache[i] = stream->cache[msgno]) != NULL)
223 stream->cache[i]->msgno = msgno;
224 stream->sc[i] = stream->sc[msgno];
226 stream->cache[i] = NIL; /* top of cache goes away */
227 stream->sc[i] = NIL;
228 break;
229 default:
230 fatal ("Bad mm_cache op");
231 break;
233 return ret;
236 /* Dummy string driver for complete in-memory strings */
238 static void mail_string_init (STRING *s,void *data,unsigned long size);
239 static char mail_string_next (STRING *s);
240 static void mail_string_setpos (STRING *s,unsigned long i);
242 STRINGDRIVER mail_string = {
243 mail_string_init, /* initialize string structure */
244 mail_string_next, /* get next byte in string structure */
245 mail_string_setpos /* set position in string structure */
249 /* Initialize mail string structure for in-memory string
250 * Accepts: string structure
251 * pointer to string
252 * size of string
255 static void mail_string_init (STRING *s,void *data,unsigned long size)
257 /* set initial string pointers */
258 s->chunk = s->curpos = (char *) (s->data = data);
259 /* and sizes */
260 s->size = s->chunksize = s->cursize = size;
261 s->data1 = s->offset = 0; /* never any offset */
265 /* Get next character from string
266 * Accepts: string structure
267 * Returns: character, string structure chunk refreshed
270 static char mail_string_next (STRING *s)
272 return *s->curpos++; /* return the last byte */
276 /* Set string pointer position
277 * Accepts: string structure
278 * new position
281 static void mail_string_setpos (STRING *s,unsigned long i)
283 s->curpos = s->chunk + i; /* set new position */
284 s->cursize = s->chunksize - i;/* and new size */
287 /* Mail routines
289 * mail_xxx routines are the interface between this module and the outside
290 * world. Only these routines should be referenced by external callers.
292 * Note that there is an important difference between a "sequence" and a
293 * "message #" (msgno). A sequence is a string representing a sequence in
294 * {"n", "n:m", or combination separated by commas} format, whereas a msgno
295 * is a single integer.
299 /* Mail version check
300 * Accepts: version
303 void mail_versioncheck (char *version)
305 /* attempt to protect again wrong .h */
306 if (strcmp (version,mailcclientversion)) {
307 char tmp[MAILTMPLEN];
308 sprintf (tmp,"c-client library version skew, app=%.100s library=%.100s",
309 version,mailcclientversion);
310 fatal (tmp);
315 /* Mail link driver
316 * Accepts: driver to add to list
319 void mail_link (DRIVER *driver)
321 DRIVER **d = &maildrivers;
322 while (*d) d = &(*d)->next; /* find end of list of drivers */
323 *d = driver; /* put driver at the end */
324 driver->next = NIL; /* this driver is the end of the list */
327 /* Mail manipulate driver parameters
328 * Accepts: mail stream
329 * function code
330 * function-dependent value
331 * Returns: function-dependent return value
334 void *mail_parameters (MAILSTREAM *stream,long function,void *value)
336 void *r,*ret = NIL;
337 DRIVER *d;
338 AUTHENTICATOR *a;
339 switch ((int) function) {
340 case SET_INBOXPATH:
341 fatal ("SET_INBOXPATH not permitted");
342 case GET_INBOXPATH:
343 if ((stream || (stream = mail_open (NIL,"INBOX",OP_PROTOTYPE))) &&
344 stream->dtb) ret = (*stream->dtb->parameters) (function,value);
345 break;
346 case SET_THREADERS:
347 fatal ("SET_THREADERS not permitted");
348 case GET_THREADERS: /* use stream dtb instead of global */
349 ret = (stream && stream->dtb) ?
350 /* KLUDGE ALERT: note stream passed as value */
351 (*stream->dtb->parameters) (function,stream) : (void *) &mailthreadlist;
352 break;
353 case SET_NAMESPACE:
354 fatal ("SET_NAMESPACE not permitted");
355 break;
356 case SET_NEWSRC: /* too late on open stream */
357 if (stream && stream->dtb && (stream != ((*stream->dtb->open) (NIL))))
358 fatal ("SET_NEWSRC not permitted");
359 else ret = env_parameters (function,value);
360 break;
361 case GET_NAMESPACE:
362 ret = (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL)) ?
363 /* KLUDGE ALERT: note stream passed as value */
364 (*stream->dtb->parameters) (function,stream) :
365 env_parameters (function,value);
366 break;
367 case GET_NEWSRC: /* use stream dtb instead of environment */
368 ret = (stream && stream->dtb) ?
369 /* KLUDGE ALERT: note stream passed as value */
370 (*stream->dtb->parameters) (function,stream) :
371 env_parameters (function,value);
372 break;
373 case ENABLE_DEBUG:
374 fatal ("ENABLE_DEBUG not permitted");
375 case DISABLE_DEBUG:
376 fatal ("DISABLE_DEBUG not permitted");
377 case SET_DIRFMTTEST:
378 fatal ("SET_DIRFMTTEST not permitted");
379 case GET_DIRFMTTEST:
380 if (!(stream && stream->dtb &&
381 (ret = (*stream->dtb->parameters) (function,NIL))))
382 fatal ("GET_DIRFMTTEST not permitted");
383 break;
385 case SET_DRIVERS:
386 fatal ("SET_DRIVERS not permitted");
387 case GET_DRIVERS: /* always return global */
388 ret = (void *) maildrivers;
389 break;
390 case SET_DRIVER:
391 fatal ("SET_DRIVER not permitted");
392 case GET_DRIVER:
393 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
394 d = d->next);
395 ret = (void *) d;
396 break;
397 case ENABLE_DRIVER:
398 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
399 d = d->next);
400 if ((ret = (void *) d) != NULL) d->flags &= ~DR_DISABLE;
401 break;
402 case DISABLE_DRIVER:
403 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
404 d = d->next);
405 if ((ret = (void *) d) != NULL) d->flags |= DR_DISABLE;
406 break;
407 case ENABLE_AUTHENTICATOR:
408 for (a = mailauthenticators;/* scan authenticators */
409 a && compare_cstring (a->name,(char *) value); a = a->next);
410 if ((ret = (void *) a) != NULL) a->flags &= ~AU_DISABLE;
411 break;
412 case DISABLE_AUTHENTICATOR:
413 for (a = mailauthenticators;/* scan authenticators */
414 a && compare_cstring (a->name,(char *) value); a = a->next);
415 if ((ret = (void *) a) != NULL) a->flags |= AU_DISABLE;
416 break;
417 case UNHIDE_AUTHENTICATOR:
418 for (a = mailauthenticators;/* scan authenticators */
419 a && compare_cstring (a->name,(char *) value); a = a->next);
420 if ((ret = (void *) a) != NULL) a->flags &= ~AU_HIDE;
421 break;
422 case HIDE_AUTHENTICATOR:
423 for (a = mailauthenticators;/* scan authenticators */
424 a && compare_cstring (a->name,(char *) value); a = a->next);
425 if ((ret = (void *) a) != NULL) a->flags |= AU_HIDE;
426 break;
427 case SET_EXTERNALAUTHID:
428 if (value) { /* setting external authentication ID */
429 externalauthid = cpystr ((char *) value);
430 mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"EXTERNAL");
432 else { /* clearing external authentication ID */
433 if (externalauthid) fs_give ((void **) &externalauthid);
434 mail_parameters (NIL,HIDE_AUTHENTICATOR,"EXTERNAL");
436 case GET_EXTERNALAUTHID:
437 ret = (void *) externalauthid;
438 break;
440 case SET_GETS:
441 mailgets = (mailgets_t) value;
442 case GET_GETS:
443 ret = (void *) mailgets;
444 break;
445 case SET_READPROGRESS:
446 mailreadprogress = (readprogress_t) value;
447 case GET_READPROGRESS:
448 ret = (void *) mailreadprogress;
449 break;
450 case SET_CACHE:
451 mailcache = (mailcache_t) value;
452 case GET_CACHE:
453 ret = (void *) mailcache;
454 break;
455 case SET_RFC822OUTPUT:
456 mail822out = (rfc822out_t) value;
457 case GET_RFC822OUTPUT:
458 ret = (void *) mail822out;
459 break;
460 case SET_RFC822OUTPUTFULL:
461 mail822outfull = (rfc822outfull_t) value;
462 case GET_RFC822OUTPUTFULL:
463 ret = (void *) mail822outfull;
464 break;
465 case SET_SMTPVERBOSE:
466 mailsmtpverbose = (smtpverbose_t) value;
467 case GET_SMTPVERBOSE:
468 ret = (void *) mailsmtpverbose;
469 break;
470 case SET_MAILPROXYCOPY:
471 mailproxycopy = (mailproxycopy_t) value;
472 case GET_MAILPROXYCOPY:
473 ret = (void *) mailproxycopy;
474 break;
475 case SET_PARSELINE:
476 mailparseline = (parseline_t) value;
477 case GET_PARSELINE:
478 ret = (void *) mailparseline;
479 break;
480 case SET_PARSEPHRASE:
481 mailparsephrase = (parsephrase_t) value;
482 case GET_PARSEPHRASE:
483 ret = (void *) mailparsephrase;
484 break;
485 case SET_NEWSRCQUERY:
486 mailnewsrcquery = (newsrcquery_t) value;
487 case GET_NEWSRCQUERY:
488 ret = (void *) mailnewsrcquery;
489 break;
490 case SET_NEWSRCCANONHOST:
491 mailnewsrccanon = (long) value;
492 case GET_NEWSRCCANONHOST:
493 ret = (void *) mailnewsrccanon;
494 break;
496 case SET_COPYUID:
497 mailcopyuid = (copyuid_t) value;
498 case GET_COPYUID:
499 ret = (void *) mailcopyuid;
500 break;
501 case SET_APPENDUID:
502 mailappenduid = (appenduid_t) value;
503 case GET_APPENDUID:
504 ret = (void *) mailappenduid;
505 break;
506 case SET_FREEENVELOPESPAREP:
507 mailfreeenvelopesparep = (freeenvelopesparep_t) value;
508 case GET_FREEENVELOPESPAREP:
509 ret = (void *) mailfreeenvelopesparep;
510 break;
511 case SET_FREEELTSPAREP:
512 mailfreeeltsparep = (freeeltsparep_t) value;
513 case GET_FREEELTSPAREP:
514 ret = (void *) mailfreeeltsparep;
515 break;
516 case SET_FREESTREAMSPAREP:
517 mailfreestreamsparep = (freestreamsparep_t) value;
518 case GET_FREESTREAMSPAREP:
519 ret = (void *) mailfreestreamsparep;
520 break;
521 case SET_FREEBODYSPAREP:
522 mailfreebodysparep = (freebodysparep_t) value;
523 case GET_FREEBODYSPAREP:
524 ret = (void *) mailfreebodysparep;
525 break;
527 case SET_SSLSTART:
528 mailsslstart = (sslstart_t) value;
529 case GET_SSLSTART:
530 ret = (void *) mailsslstart;
531 break;
532 case SET_SSLCERTIFICATEQUERY:
533 mailsslcertificatequery = (sslcertificatequery_t) value;
534 case GET_SSLCERTIFICATEQUERY:
535 ret = (void *) mailsslcertificatequery;
536 break;
537 case SET_SSLCLIENTCERT:
538 mailsslclientcert = (sslclientcert_t) value;
539 case GET_SSLCLIENTCERT:
540 ret = (void *) mailsslclientcert;
541 break;
542 case SET_SSLCLIENTKEY:
543 mailsslclientkey = (sslclientkey_t) value;
544 case GET_SSLCLIENTKEY:
545 ret = (void *) mailsslclientkey;
546 break;
547 case SET_SSLFAILURE:
548 mailsslfailure = (sslfailure_t) value;
549 case GET_SSLFAILURE:
550 ret = (void *) mailsslfailure;
551 break;
552 case SET_ENCRYPTION_RANGE_MIN:
553 encryption_range_min = *(int *) value;
554 case GET_ENCRYPTION_RANGE_MIN:
555 ret = (void *) &encryption_range_min;
556 break;
557 case SET_ENCRYPTION_RANGE_MAX:
558 encryption_range_max = *(int *) value;
559 case GET_ENCRYPTION_RANGE_MAX:
560 ret = (void *) &encryption_range_max;
561 break;
562 case SET_KINIT:
563 mailkinit = (kinit_t) value;
564 case GET_KINIT:
565 ret = (void *) mailkinit;
566 break;
567 case SET_SENDCOMMAND:
568 mailsendcommand = (sendcommand_t) value;
569 case GET_SENDCOMMAND:
570 ret = (void *) mailsendcommand;
571 break;
573 case SET_SERVICENAME:
574 servicename = (char *) value;
575 case GET_SERVICENAME:
576 ret = (void *) servicename;
577 break;
578 case SET_EXPUNGEATPING:
579 expungeatping = (value ? T : NIL);
580 case GET_EXPUNGEATPING:
581 ret = (void *) (expungeatping ? VOIDT : NIL);
582 break;
583 case SET_SORTRESULTS:
584 mailsortresults = (sortresults_t) value;
585 case GET_SORTRESULTS:
586 ret = (void *) mailsortresults;
587 break;
588 case SET_THREADRESULTS:
589 mailthreadresults = (threadresults_t) value;
590 case GET_THREADRESULTS:
591 ret = (void *) mailthreadresults;
592 break;
593 case SET_SSLDRIVER:
594 mailssldriver = (NETDRIVER *) value;
595 case GET_SSLDRIVER:
596 ret = (void *) mailssldriver;
597 break;
598 case SET_TRYSSLFIRST:
599 trysslfirst = (value ? T : NIL);
600 case GET_TRYSSLFIRST:
601 ret = (void *) (trysslfirst ? VOIDT : NIL);
602 break;
603 case SET_NOTIMEZONES:
604 notimezones = (value ? T : NIL);
605 case GET_NOTIMEZONES:
606 ret = (void *) (notimezones ? VOIDT : NIL);
607 break;
608 case SET_TRUSTDNS:
609 trustdns = (value ? T : NIL);
610 case GET_TRUSTDNS:
611 ret = (void *) (trustdns ? VOIDT : NIL);
612 break;
613 case SET_SASLUSESPTRNAME:
614 saslusesptrname = (value ? T : NIL);
615 case GET_SASLUSESPTRNAME:
616 ret = (void *) (saslusesptrname ? VOIDT : NIL);
617 break;
618 case SET_DEBUGSENSITIVE:
619 debugsensitive = (value ? T : NIL);
620 case GET_DEBUGSENSITIVE:
621 ret = (void *) (debugsensitive ? VOIDT : NIL);
622 break;
624 case SET_ACL:
625 mailaclresults = (getacl_t) value;
626 case GET_ACL:
627 ret = (void *) mailaclresults;
628 break;
629 case SET_LISTRIGHTS:
630 maillistrightsresults = (listrights_t) value;
631 case GET_LISTRIGHTS:
632 ret = (void *) maillistrightsresults;
633 break;
634 case SET_MYRIGHTS:
635 mailmyrightsresults = (myrights_t) value;
636 case GET_MYRIGHTS:
637 ret = (void *) mailmyrightsresults;
638 break;
639 case SET_QUOTA:
640 mailquotaresults = (quota_t) value;
641 case GET_QUOTA:
642 ret = (void *) mailquotaresults;
643 break;
644 case SET_QUOTAROOT:
645 mailquotarootresults = (quotaroot_t) value;
646 case GET_QUOTAROOT:
647 ret = (void *) mailquotarootresults;
648 break;
649 case SET_SNARFINTERVAL:
650 mailsnarfinterval = (long) value;
651 case GET_SNARFINTERVAL:
652 ret = (void *) mailsnarfinterval;
653 break;
654 case SET_SNARFPRESERVE:
655 mailsnarfpreserve = (long) value;
656 case GET_SNARFPRESERVE:
657 ret = (void *) mailsnarfpreserve;
658 break;
659 case SET_SNARFMAILBOXNAME:
660 if (stream) { /* have a stream? */
661 if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
662 stream->snarf.name = cpystr ((char *) value);
664 else fatal ("SET_SNARFMAILBOXNAME with no stream");
665 case GET_SNARFMAILBOXNAME:
666 if (stream) ret = (void *) stream->snarf.name;
667 break;
668 case SET_IDPARAMS: /* program id */
669 idapp = (IDLIST *) value;
670 case GET_IDPARAMS:
671 ret = (void *) idapp;
672 break;
673 case SET_OA2CLIENTGETACCESSCODE:
674 oauth2getaccesscode = (oauth2getaccesscode_t) value;
675 case GET_OA2CLIENTGETACCESSCODE:
676 ret = (void *) oauth2getaccesscode;
677 break;
678 case SET_OA2CLIENTINFO:
679 oauth2clientinfo = (oauth2clientinfo_t) value;
680 case GET_OA2CLIENTINFO:
681 ret = (void *) oauth2clientinfo;
682 break;
683 default:
684 if ((r = smtp_parameters (function,value)) != NULL) ret = r;
685 if ((r = env_parameters (function,value)) != NULL) ret = r;
686 if ((r = tcp_parameters (function,value)) != NULL) ret = r;
687 if ((r = utf8_parameters (function,value)) != NULL) ret = r;
688 if (stream && stream->dtb) {/* if have stream, do for its driver only */
689 if ((r = (*stream->dtb->parameters) (function,value)) != NULL) ret = r;
691 /* else do all drivers */
692 else for (d = maildrivers; d; d = d->next)
693 if ((r = (d->parameters) (function,value)) != NULL) ret = r;
694 break;
696 return ret;
699 /* Mail validate mailbox name
700 * Accepts: MAIL stream
701 * mailbox name
702 * purpose string for error message
703 * Return: driver factory on success, NIL on failure
706 DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose)
708 char tmp[MAILTMPLEN];
709 DRIVER *factory = NIL;
710 /* never allow names with newlines */
711 if (strpbrk (mailbox,"\015\012")) {
712 if (purpose) { /* if want an error message */
713 sprintf (tmp,"Can't %s with such a name",purpose);
714 MM_LOG (tmp,ERROR);
716 return NIL;
718 /* validate name, find driver factory */
719 if (strlen (mailbox) < (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50))
720 for (factory = maildrivers; factory &&
721 ((factory->flags & DR_DISABLE) ||
722 ((factory->flags & DR_LOCAL) && (*mailbox == '{')) ||
723 !(*factory->valid) (mailbox));
724 factory = factory->next);
725 /* validate factory against non-dummy stream */
726 if (factory && stream && stream->dtb && (stream->dtb != factory) &&
727 strcmp (stream->dtb->name,"dummy"))
728 /* factory invalid; if dummy, use stream */
729 factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb;
730 if (!factory && purpose) { /* if want an error message */
731 sprintf (tmp,"Can't %s %.80s: %s",purpose,mailbox,(*mailbox == '{') ?
732 "invalid remote specification" : "no such mailbox");
733 MM_LOG (tmp,ERROR);
735 return factory; /* return driver factory */
738 /* Mail validate network mailbox name
739 * Accepts: mailbox name
740 * mailbox driver to validate against
741 * pointer to where to return host name if non-NIL
742 * pointer to where to return mailbox name if non-NIL
743 * Returns: driver on success, NIL on failure
746 DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox)
748 NETMBX mb;
749 if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,drv->name))
750 return NIL;
751 if (host) strcpy (host,mb.host);
752 if (mailbox) strcpy (mailbox,mb.mailbox);
753 return drv;
757 /* Mail validate network mailbox name
758 * Accepts: mailbox name
759 * NETMBX structure to return values
760 * Returns: T on success, NIL on failure
763 long mail_valid_net_parse (char *name,NETMBX *mb)
765 return mail_valid_net_parse_work (name,mb,"imap");
768 /* Mail validate network mailbox name worker routine
769 * Accepts: mailbox name
770 * NETMBX structure to return values
771 * default service
772 * Returns: T on success, NIL on failure
775 long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service)
777 int i,j;
778 char c,*s,*t,*v,tmp[MAILTMPLEN],arg[MAILTMPLEN];
779 /* initialize structure */
780 memset (mb,'\0',sizeof (NETMBX));
781 /* must have host specification */
782 if (*name++ != '{') return NIL;
783 if (*name == '[') { /* if domain literal, find its ending */
784 if (!((v = strpbrk (name,"]}")) && (*v++ == ']'))) return NIL;
786 /* find end of host name */
787 else if (!(v = strpbrk (name,"/:}"))) return NIL;
788 /* validate length, find mailbox part */
789 if (!((i = v - name) && (i < NETMAXHOST) && (t = strchr (v,'}')) &&
790 ((j = t - v) < MAILTMPLEN) && (strlen (t+1) < (size_t) NETMAXMBX)))
791 return NIL; /* invalid mailbox */
792 strncpy (mb->host,name,i); /* set host name */
793 strncpy (mb->orighost,name,i);
794 mb->host[i] = mb->orighost[i] = '\0';
795 strcpy (mb->mailbox,t+1); /* set mailbox name */
796 if (t - v) { /* any switches or port specification? */
797 strncpy (t = tmp,v,j); /* copy it */
798 tmp[j] = '\0'; /* tie it off */
799 c = *t++; /* get first delimiter */
800 do switch (c) { /* act based upon the character */
801 case ':': /* port specification */
802 if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL;
803 c = t ? *t++ : '\0'; /* get delimiter, advance pointer */
804 break;
805 case '/': /* switch */
806 /* find delimiter */
807 if ((t = strpbrk (s = t,"/:=")) != NULL) {
808 c = *t; /* remember delimiter for later */
809 *t++ = '\0'; /* tie off switch name */
811 else c = '\0'; /* no delimiter */
812 if (c == '=') { /* parse switches which take arguments */
813 if (*t == '"') { /* quoted string? */
814 for (v = arg,i = 0,++t; (c = *t++) != '"';) {
815 if (!c) return NIL; /* unterminated string */
816 /* quote next character */
817 if (c == '\\') c = *t++;
818 if (!c) return NIL; /* can't quote NUL either */
819 arg[i++] = c;
821 c = *t++; /* remember delimiter for later */
822 arg[i] = '\0'; /* tie off argument */
824 else { /* non-quoted argument */
825 if ((t = strpbrk (v = t,"/:")) != NULL) {
826 c = *t; /* remember delimiter for later */
827 *t++ = '\0'; /* tie off switch name */
829 else c = '\0'; /* no delimiter */
830 i = strlen (v); /* length of argument */
832 if (!compare_cstring (s,"service") && (i < NETMAXSRV) && !*mb->service)
833 lcase (strcpy (mb->service,v));
834 else if (!compare_cstring (s,"user") && (i < NETMAXUSER) && !*mb->user)
835 strcpy (mb->user,v);
836 else if (!compare_cstring (s,"authuser") && (i < NETMAXUSER) &&
837 !*mb->authuser) strcpy (mb->authuser,v);
838 else if (!compare_cstring (s,"auth") && (i < NETMAXAUTH) &&
839 !*mb->auth) strcpy (mb->auth,v);
840 else return NIL;
843 else { /* non-argument switch */
844 if (!compare_cstring (s,"anonymous")) mb->anoflag = T;
845 else if (!compare_cstring (s,"debug")) mb->dbgflag = T;
846 else if (!compare_cstring (s,"readonly")) mb->readonlyflag = T;
847 else if (!compare_cstring (s,"secure")) mb->secflag = T;
848 else if (!compare_cstring (s,"norsh")) mb->norsh = T;
849 else if (!compare_cstring (s,"loser")) mb->loser = T;
850 else if ((!compare_cstring (s,"starttls") || !compare_cstring (s,"tls")) && !mb->notlsflag)
851 mb->tlsflag = T;
852 else if (!compare_cstring (s,"tls-sslv23") && !mb->notlsflag)
853 mb->tlssslv23 = mb->tlsflag = T;
854 else if ((!compare_cstring (s,"notls") || !compare_cstring(s,"nostarttls")) && !mb->tlsflag)
855 mb->notlsflag = T;
856 else if (!compare_cstring (s,"tryssl"))
857 mb->trysslflag = mailssldriver? T : NIL;
858 else if (mailssldriver && !compare_cstring (s,"ssl") && !mb->tlsflag)
859 mb->sslflag = mb->notlsflag = T;
860 else if (!compare_cstring(s, "tls1")
861 && !mb->tls1_1 && !mb->tls1_2 && !mb->tls1_3)
862 mb->sslflag = mb->notlsflag = mb->tls1 = T;
863 else if (!compare_cstring(s, "tls1_1")
864 && !mb->tls1 && !mb->tls1_2 && !mb->tls1_3)
865 mb->sslflag = mb->notlsflag = mb->tls1_1 = T;
866 else if (!compare_cstring(s, "tls1_2")
867 && !mb->tls1 && !mb->tls1_1 && !mb->tls1_3)
868 mb->sslflag = mb->notlsflag = mb->tls1_2 = T;
869 else if (!compare_cstring(s, "tls1_3")
870 && !mb->tls1 && !mb->tls1_1 && !mb->tls1_2)
871 mb->sslflag = mb->notlsflag = mb->tls1_3 = T;
872 else if (mailssldriver && !compare_cstring (s,"novalidate-cert"))
873 mb->novalidate = T;
874 /* hack for compatibility with the past */
875 else if (mailssldriver && !compare_cstring (s,"validate-cert"));
876 /* service switches below here */
877 else if (*mb->service) return NIL;
878 else if (!compare_cstring (s,"imap") ||
879 !compare_cstring (s,"nntp") ||
880 !compare_cstring (s,"pop3") ||
881 !compare_cstring (s,"smtp") ||
882 !compare_cstring (s,"submit"))
883 lcase (strcpy (mb->service,s));
884 else if (!compare_cstring (s,"imap2") ||
885 !compare_cstring (s,"imap2bis") ||
886 !compare_cstring (s,"imap4") ||
887 !compare_cstring (s,"imap4rev1"))
888 strcpy (mb->service,"imap");
889 else if (!compare_cstring (s,"pop"))
890 strcpy (mb->service,"pop3");
891 else return NIL; /* invalid non-argument switch */
893 break;
894 default: /* anything else is bogus */
895 return NIL;
896 } while (c); /* see if anything more to parse */
898 /* default mailbox name */
899 if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX");
900 /* default service name */
901 if (!*mb->service) strcpy (mb->service,service);
902 /* /norsh only valid if imap */
903 if (mb->norsh && strcmp (mb->service,"imap")) return NIL;
904 return T;
907 /* Mail scan mailboxes for string
908 * Accepts: mail stream
909 * reference
910 * pattern to search
911 * contents to search
914 void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
916 int remote = ((*pat == '{') || (ref && *ref == '{'));
917 DRIVER *d;
918 if (ref && (strlen (ref) > NETMAXMBX)) {
919 char tmp[MAILTMPLEN];
920 sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
921 MM_LOG (tmp,ERROR);
922 return;
924 if (strlen (pat) > NETMAXMBX) {
925 char tmp[MAILTMPLEN];
926 sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
927 MM_LOG (tmp,ERROR);
928 return;
930 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
931 if (stream) { /* if have a stream, do it for that stream */
932 if ((d = stream->dtb) && d->scan &&
933 !(((d->flags & DR_LOCAL) && remote)))
934 (*d->scan) (stream,ref,pat,contents);
936 /* otherwise do for all DTB's */
937 else for (d = maildrivers; d; d = d->next)
938 if (d->scan && !((d->flags & DR_DISABLE) ||
939 ((d->flags & DR_LOCAL) && remote)))
940 (d->scan) (NIL,ref,pat,contents);
943 /* Mail list mailboxes
944 * Accepts: mail stream
945 * reference
946 * pattern to search
949 void mail_list (MAILSTREAM *stream,char *ref,char *pat)
951 int remote = ((*pat == '{') || (ref && *ref == '{'));
952 DRIVER *d = maildrivers;
953 if (ref && (strlen (ref) > NETMAXMBX)) {
954 char tmp[MAILTMPLEN];
955 sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
956 MM_LOG (tmp,ERROR);
957 return;
959 if (strlen (pat) > NETMAXMBX) {
960 char tmp[MAILTMPLEN];
961 sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
962 MM_LOG (tmp,ERROR);
963 return;
965 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
966 if (stream && stream->dtb) { /* if have a stream, do it for that stream */
967 if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
968 (*d->list) (stream,ref,pat);
970 /* otherwise do for all DTB's */
971 else do if (!((d->flags & DR_DISABLE) ||
972 ((d->flags & DR_LOCAL) && remote)))
973 (d->list) (NIL,ref,pat);
974 while ((d = d->next) != NULL); /* until at the end */
977 /* Mail list subscribed mailboxes
978 * Accepts: mail stream
979 * pattern to search
982 void mail_lsub (MAILSTREAM *stream,char *ref,char *pat)
984 int remote = ((*pat == '{') || (ref && *ref == '{'));
985 DRIVER *d = maildrivers;
986 if (ref && (strlen (ref) > NETMAXMBX)) {
987 char tmp[MAILTMPLEN];
988 sprintf (tmp,"Invalid LSUB reference specification: %.80s",ref);
989 MM_LOG (tmp,ERROR);
990 return;
992 if (strlen (pat) > NETMAXMBX) {
993 char tmp[MAILTMPLEN];
994 sprintf (tmp,"Invalid LSUB pattern specification: %.80s",pat);
995 MM_LOG (tmp,ERROR);
996 return;
998 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
999 if (stream && stream->dtb) { /* if have a stream, do it for that stream */
1000 if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
1001 (*d->lsub) (stream,ref,pat);
1003 /* otherwise do for all DTB's */
1004 else do if (!((d->flags & DR_DISABLE) ||
1005 ((d->flags & DR_LOCAL) && remote)))
1006 (d->lsub) (NIL,ref,pat);
1007 while ((d = d->next) != NULL); /* until at the end */
1010 /* Mail subscribe to mailbox
1011 * Accepts: mail stream
1012 * mailbox to add to subscription list
1013 * Returns: T on success, NIL on failure
1016 long mail_subscribe (MAILSTREAM *stream,char *mailbox)
1018 DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox");
1019 return factory ?
1020 (factory->subscribe ?
1021 (*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL;
1025 /* Mail unsubscribe to mailbox
1026 * Accepts: mail stream
1027 * mailbox to delete from subscription list
1028 * Returns: T on success, NIL on failure
1031 long mail_unsubscribe (MAILSTREAM *stream,char *mailbox)
1033 DRIVER *factory = mail_valid (stream,mailbox,NIL);
1034 return (factory && factory->unsubscribe) ?
1035 (*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox);
1038 /* Mail create mailbox
1039 * Accepts: mail stream
1040 * mailbox name to create
1041 * Returns: T on success, NIL on failure
1044 long mail_create (MAILSTREAM *stream,char *mailbox)
1046 MAILSTREAM *ts;
1047 char *s,*t,tmp[MAILTMPLEN];
1048 size_t i;
1049 DRIVER *d;
1050 /* never allow names with newlines */
1051 if ((s = strpbrk (mailbox,"\015\012")) != NULL) {
1052 MM_LOG ("Can't create mailbox with such a name",ERROR);
1053 return NIL;
1055 if (strlen (mailbox) >= (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
1056 sprintf (tmp,"Can't create %.80s: %s",mailbox,(*mailbox == '{') ?
1057 "invalid remote specification" : "no such mailbox");
1058 MM_LOG (tmp,ERROR);
1059 return NIL;
1061 /* create of INBOX invalid */
1062 if (!compare_cstring (mailbox,"INBOX")) {
1063 MM_LOG ("Can't create INBOX",ERROR);
1064 return NIL;
1066 /* validate name */
1067 if ((s = mail_utf7_valid (mailbox)) != NULL) {
1068 sprintf (tmp,"Can't create %s: %.80s",s,mailbox);
1069 MM_LOG (tmp,ERROR);
1070 return NIL;
1073 /* see if special driver hack */
1074 if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) &&
1075 ((mailbox[2] == 'r') || (mailbox[2] == 'R')) &&
1076 ((mailbox[3] == 'i') || (mailbox[3] == 'I')) &&
1077 ((mailbox[4] == 'v') || (mailbox[4] == 'V')) &&
1078 ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
1079 ((mailbox[6] == 'r') || (mailbox[6] == 'R')) && (mailbox[7] == '.')) {
1080 /* copy driver until likely delimiter */
1081 if ((s = strpbrk (t = mailbox+8,"/\\:")) && (i = s - t)) {
1082 strncpy (tmp,t,i);
1083 tmp[i] = '\0';
1085 else {
1086 sprintf (tmp,"Can't create mailbox %.80s: bad driver syntax",mailbox);
1087 MM_LOG (tmp,ERROR);
1088 return NIL;
1090 for (d = maildrivers; d && strcmp (d->name,tmp); d = d->next);
1091 if (d) mailbox = ++s; /* skip past driver specification */
1092 else {
1093 sprintf (tmp,"Can't create mailbox %.80s: unknown driver",mailbox);
1094 MM_LOG (tmp,ERROR);
1095 return NIL;
1098 /* use stream if one given or deterministic */
1099 else if ((stream && stream->dtb) ||
1100 (((*mailbox == '{') || (*mailbox == '#')) &&
1101 (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT))))
1102 d = stream->dtb;
1103 else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb;
1104 else { /* failed utterly */
1105 sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox);
1106 MM_LOG (tmp,ERROR);
1107 return NIL;
1109 return (*d->create) (stream,mailbox);
1112 /* Mail delete mailbox
1113 * Accepts: mail stream
1114 * mailbox name to delete
1115 * Returns: T on success, NIL on failure
1118 long mail_delete (MAILSTREAM *stream,char *mailbox)
1120 DRIVER *dtb = mail_valid (stream,mailbox,"delete mailbox");
1121 if (!dtb) return NIL;
1122 if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
1123 ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
1124 ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
1125 ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
1126 ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5]) {
1127 MM_LOG ("Can't delete INBOX",ERROR);
1128 return NIL;
1130 return SAFE_DELETE (dtb,stream,mailbox);
1134 /* Mail rename mailbox
1135 * Accepts: mail stream
1136 * old mailbox name
1137 * new mailbox name
1138 * Returns: T on success, NIL on failure
1141 long mail_rename (MAILSTREAM *stream,char *old,char *newname)
1143 char *s,tmp[MAILTMPLEN];
1144 DRIVER *dtb = mail_valid (stream,old,"rename mailbox");
1145 if (!dtb) return NIL;
1146 /* validate name */
1147 if ((s = mail_utf7_valid (newname)) != NULL) {
1148 sprintf (tmp,"Can't rename to %s: %.80s",s,newname);
1149 MM_LOG (tmp,ERROR);
1150 return NIL;
1152 if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) {
1153 sprintf (tmp,"Can't rename %.80s: mailbox %.80s already exists",
1154 old,newname);
1155 MM_LOG (tmp,ERROR);
1156 return NIL;
1158 return SAFE_RENAME (dtb,stream,old,newname);
1161 /* Validate mailbox as Modified UTF-7
1162 * Accepts: candidate mailbox name
1163 * Returns: error string if error, NIL if valid
1166 char *mail_utf7_valid (char *mailbox)
1168 char *s;
1169 for (s = mailbox; *s; s++) { /* make sure valid name */
1170 /* reserved for future use with UTF-8 */
1171 if (*s & 0x80) return "mailbox name with 8-bit octet";
1172 /* validate modified UTF-7 */
1173 else if (*s == '&') while (*++s != '-') switch (*s) {
1174 case '\0':
1175 return "unterminated modified UTF-7 name";
1176 case '+': /* valid modified BASE64 */
1177 case ',':
1178 break; /* all OK so far */
1179 default: /* must be alphanumeric */
1180 if (!isalnum (*s)) return "invalid modified UTF-7 name";
1181 break;
1184 return NIL; /* all OK */
1187 /* Mail status of mailbox
1188 * Accepts: mail stream if open on this mailbox
1189 * mailbox name
1190 * status flags
1191 * Returns: T on success, NIL on failure
1194 long mail_status (MAILSTREAM *stream,char *mbx,long flags)
1196 DRIVER *dtb = mail_valid (stream,mbx,"get status of mailbox");
1197 if (!dtb) return NIL; /* only if valid */
1198 if (stream && ((dtb != stream->dtb) ||
1199 ((dtb->flags & DR_LOCAL) && strcmp (mbx,stream->mailbox) &&
1200 strcmp (mbx,stream->original_mailbox))))
1201 stream = NIL; /* stream not suitable */
1202 return SAFE_STATUS (dtb,stream,mbx,flags);
1206 /* Mail status of mailbox default handler
1207 * Accepts: mail stream
1208 * mailbox name
1209 * status flags
1210 * Returns: T on success, NIL on failure
1213 long mail_status_default (MAILSTREAM *stream,char *mbx,long flags)
1215 MAILSTATUS status;
1216 unsigned long i;
1217 MAILSTREAM *tstream = NIL;
1218 /* make temporary stream (unless this mbx) */
1219 if (!stream && !(stream = tstream =
1220 mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
1221 status.flags = flags; /* return status values */
1222 status.messages = stream->nmsgs;
1223 status.recent = stream->recent;
1224 if (flags & SA_UNSEEN) /* must search to get unseen messages */
1225 for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
1226 if (!mail_elt (stream,i)->seen) status.unseen++;
1227 status.uidnext = stream->uid_last + 1;
1228 status.uidvalidity = stream->uid_validity;
1229 MM_STATUS(stream,mbx,&status);/* pass status to main program */
1230 if (tstream) mail_close (tstream);
1231 return T; /* success */
1234 /* Mail open
1235 * Accepts: candidate stream for recycling
1236 * mailbox name
1237 * open options
1238 * Returns: stream to use on success, NIL on failure
1241 MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options)
1243 int i;
1244 char c,*s,tmp[MAILTMPLEN];
1245 NETMBX mb;
1246 DRIVER *d;
1247 switch (name[0]) { /* see if special handling */
1248 case '#': /* possible special hacks */
1249 if (((name[1] == 'M') || (name[1] == 'm')) &&
1250 ((name[2] == 'O') || (name[2] == 'o')) &&
1251 ((name[3] == 'V') || (name[3] == 'v')) &&
1252 ((name[4] == 'E') || (name[4] == 'e')) && (c = name[5]) &&
1253 (s = strchr (name+6,c)) && (i = s - (name + 6)) && (i < MAILTMPLEN)) {
1254 if ((stream = mail_open (stream,s+1,options)) != NULL) {
1255 strncpy (tmp,name+6,i); /* copy snarf mailbox name */
1256 tmp[i] = '\0'; /* tie off name */
1257 mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
1258 stream->snarf.options = options;
1259 mail_ping (stream); /* do initial snarf */
1260 /* punt if can't do initial snarf */
1261 if (!stream->snarf.time) stream = mail_close (stream);
1263 return stream;
1265 /* special POP hack */
1266 else if (((name[1] == 'P') || (name[1] == 'p')) &&
1267 ((name[2] == 'O') || (name[2] == 'o')) &&
1268 ((name[3] == 'P') || (name[3] == 'p')) &&
1269 mail_valid_net_parse_work (name+4,&mb,"pop3") &&
1270 !strcmp (mb.service,"pop3") && !mb.anoflag && !mb.readonlyflag) {
1271 if ((stream = mail_open (stream,mb.mailbox,options)) != NULL) {
1272 sprintf (tmp,"{%.255s",mb.host);
1273 if (mb.port) sprintf (tmp + strlen (tmp),":%lu",mb.port);
1274 if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=%.64s",mb.user);
1275 if (mb.dbgflag) strcat (tmp,"/debug");
1276 if (mb.secflag) strcat (tmp,"/secure");
1277 if (mb.tlsflag) strcat (tmp,"/starttls");
1278 if (mb.notlsflag) strcat (tmp,"/notls");
1279 if (mb.sslflag) strcat (tmp,"/ssl");
1280 if (mb.tls1) strcat (tmp,"/tls1");
1281 if (mb.tls1_1) strcat (tmp,"/tls1_1");
1282 if (mb.tls1_2) strcat (tmp,"/tls1_2");
1283 if (mb.tls1_3) strcat (tmp,"/tls1_3");
1284 if (mb.trysslflag) strcat (tmp,"/tryssl");
1285 if (mb.novalidate) strcat (tmp,"/novalidate-cert");
1286 strcat (tmp,"/pop3/loser}");
1287 mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
1288 mail_ping (stream); /* do initial snarf */
1290 return stream; /* return local mailbox stream */
1293 else if ((options & OP_PROTOTYPE) &&
1294 ((name[1] == 'D') || (name[1] == 'd')) &&
1295 ((name[2] == 'R') || (name[2] == 'r')) &&
1296 ((name[3] == 'I') || (name[3] == 'i')) &&
1297 ((name[4] == 'V') || (name[4] == 'v')) &&
1298 ((name[5] == 'E') || (name[5] == 'e')) &&
1299 ((name[6] == 'R') || (name[6] == 'r')) && (name[7] == '.')) {
1300 sprintf (tmp,"%.80s",name+8);
1301 /* tie off name at likely delimiter */
1302 if ((s = strpbrk (tmp,"/\\:")) != NULL) *s++ = '\0';
1303 else {
1304 sprintf (tmp,"Can't resolve mailbox %.80s: bad driver syntax",name);
1305 MM_LOG (tmp,ERROR);
1306 return mail_close (stream);
1308 for (d = maildrivers; d && compare_cstring (d->name,tmp); d = d->next);
1309 if (d) return (*d->open) (NIL);
1310 sprintf (tmp,"Can't resolve mailbox %.80s: unknown driver",name);
1311 MM_LOG (tmp,ERROR);
1312 return mail_close (stream);
1314 /* fall through to default case */
1315 default: /* not special hack (but could be # name */
1316 d = mail_valid (NIL,name,(options & OP_SILENT) ?
1317 (char *) NIL : "open mailbox");
1319 return d ? mail_open_work (d,stream,name,options) : stream;
1322 /* Mail open worker routine
1323 * Accepts: factory
1324 * candidate stream for recycling
1325 * mailbox name
1326 * open options
1327 * Returns: stream to use on success, NIL on failure
1330 MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name,
1331 long options)
1333 int i;
1334 char tmp[MAILTMPLEN];
1335 NETMBX mb;
1336 if (options & OP_PROTOTYPE) return (*d->open) (NIL);
1337 /* name is copied here in case the caller does a re-open using
1338 * stream->mailbox or stream->original_mailbox as the argument.
1340 name = cpystr (name); /* make copy of name */
1341 if (stream) { /* recycling requested? */
1342 if ((stream->dtb == d) && (d->flags & DR_RECYCLE) &&
1343 ((d->flags & DR_HALFOPEN) || !(options & OP_HALFOPEN)) &&
1344 mail_usable_network_stream (stream,name)) {
1345 /* yes, checkpoint if needed */
1346 if (d->flags & DR_XPOINT) mail_check (stream);
1347 mail_free_cache (stream); /* clean up stream */
1348 if (stream->mailbox) fs_give ((void **) &stream->mailbox);
1349 if (stream->original_mailbox)
1350 fs_give ((void **) &stream->original_mailbox);
1351 /* flush user flags */
1352 for (i = 0; i < NUSERFLAGS; i++)
1353 if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
1355 else { /* stream not recycleable, babble if net */
1356 if (!stream->silent && stream->dtb && !(stream->dtb->flags&DR_LOCAL) &&
1357 mail_valid_net_parse (stream->mailbox,&mb)) {
1358 sprintf (tmp,"Closing connection to %.80s",mb.host);
1359 MM_LOG (tmp,(long) NIL);
1361 /* flush the old stream */
1362 stream = mail_close (stream);
1365 /* check if driver does not support halfopen */
1366 else if ((options & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)) {
1367 fs_give ((void **) &name);
1368 return NIL;
1371 /* instantiate new stream if not recycling */
1372 if (!stream) (*mailcache) (stream = (MAILSTREAM *)
1373 memset (fs_get (sizeof (MAILSTREAM)),0,
1374 sizeof (MAILSTREAM)),(long) 0,CH_INIT);
1375 stream->dtb = d; /* set dispatch */
1376 /* set mailbox name */
1377 stream->mailbox = cpystr (stream->original_mailbox = name);
1378 /* initialize stream flags */
1379 stream->inbox = stream->lock = NIL;
1380 stream->debug = (options & OP_DEBUG) ? T : NIL;
1381 stream->rdonly = (options & OP_READONLY) ? T : NIL;
1382 stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL;
1383 stream->scache = (options & OP_SHORTCACHE) ? T : NIL;
1384 stream->silent = (options & OP_SILENT) ? T : NIL;
1385 stream->halfopen = (options & OP_HALFOPEN) ? T : NIL;
1386 stream->secure = (options & OP_SECURE) ? T : NIL;
1387 stream->tryssl = (options & OP_TRYSSL) ? T : NIL;
1388 stream->mulnewsrc = (options & OP_MULNEWSRC) ? T : NIL;
1389 stream->nokod = (options & OP_NOKOD) ? T : NIL;
1390 stream->sniff = (options & OP_SNIFF) ? T : NIL;
1391 stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
1392 stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
1393 stream->uid_nosticky = (d->flags & DR_NOSTICKY) ? T : NIL;
1394 stream->uid_last = 0; /* default UID validity */
1395 stream->uid_validity = (unsigned long) time (0);
1396 /* have driver open, flush if failed */
1397 return ((*d->open) (stream)) ? stream : mail_close (stream);
1400 /* Mail close
1401 * Accepts: mail stream
1402 * close options
1403 * Returns: NIL, always
1406 MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options)
1408 int i;
1409 if (stream) { /* make sure argument given */
1410 /* do the driver's close action */
1411 if (stream->dtb) (*stream->dtb->close) (stream,options);
1412 stream->dtb = NIL; /* resign driver */
1413 if (stream->mailbox) fs_give ((void **) &stream->mailbox);
1414 if (stream->original_mailbox)
1415 fs_give ((void **) &stream->original_mailbox);
1416 if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
1417 stream->sequence++; /* invalidate sequence */
1418 /* flush user flags */
1419 for (i = 0; i < NUSERFLAGS; i++)
1420 if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
1421 mail_free_cache (stream); /* finally free the stream's storage */
1422 if (mailfreestreamsparep && stream->sparep)
1423 (*mailfreestreamsparep) (&stream->sparep);
1424 if (!stream->use) fs_give ((void **) &stream);
1426 return NIL;
1429 /* Mail make handle
1430 * Accepts: mail stream
1431 * Returns: handle
1433 * Handles provide a way to have multiple pointers to a stream yet allow the
1434 * stream's owner to nuke it or recycle it.
1437 MAILHANDLE *mail_makehandle (MAILSTREAM *stream)
1439 MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
1440 handle->stream = stream; /* copy stream */
1441 /* and its sequence */
1442 handle->sequence = stream->sequence;
1443 stream->use++; /* let stream know another handle exists */
1444 return handle;
1447 void mail_free_idlist (IDLIST **idlist)
1449 if (idlist && *idlist){
1450 if((*idlist)->name) fs_give((void **)&(*idlist)->name);
1451 if((*idlist)->value) fs_give((void **)&(*idlist)->value);
1452 if((*idlist)->next) mail_free_idlist(&(*idlist)->next);
1453 fs_give((void **) idlist);
1458 /* Mail release handle
1459 * Accepts: Mail handle
1462 void mail_free_handle (MAILHANDLE **handle)
1464 MAILSTREAM *s;
1465 if (*handle) { /* only free if exists */
1466 /* resign stream, flush unreferenced zombies */
1467 if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
1468 fs_give ((void **) handle); /* now flush the handle */
1473 /* Mail get stream handle
1474 * Accepts: Mail handle
1475 * Returns: mail stream or NIL if stream gone
1478 MAILSTREAM *mail_stream (MAILHANDLE *handle)
1480 MAILSTREAM *s = handle->stream;
1481 return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
1484 /* Mail fetch cache element
1485 * Accepts: mail stream
1486 * message # to fetch
1487 * Returns: cache element of this message
1488 * Can also be used to create cache elements for new messages.
1491 MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno)
1493 if (msgno < 1 || msgno > stream->nmsgs) {
1494 char tmp[MAILTMPLEN];
1495 sprintf (tmp,"Bad msgno %lu in mail_elt, nmsgs = %lu, mbx=%.80s",
1496 msgno,stream->nmsgs,stream->mailbox ? stream->mailbox : "???");
1497 fatal (tmp);
1499 return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT);
1503 /* Mail fetch fast information
1504 * Accepts: mail stream
1505 * sequence
1506 * option flags
1508 * Generally, mail_fetch_structure is preferred
1511 void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags)
1513 /* do the driver's action */
1514 if (stream->dtb && stream->dtb->fast)
1515 (*stream->dtb->fast) (stream,sequence,flags);
1519 /* Mail fetch flags
1520 * Accepts: mail stream
1521 * sequence
1522 * option flags
1525 void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags)
1527 /* do the driver's action */
1528 if (stream->dtb && stream->dtb->msgflags)
1529 (*stream->dtb->msgflags) (stream,sequence,flags);
1532 /* Mail fetch message overview
1533 * Accepts: mail stream
1534 * UID sequence to fetch
1535 * pointer to overview return function
1538 void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
1540 if (stream->dtb && mail_uid_sequence (stream,sequence) &&
1541 !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
1542 mail_ping (stream))
1543 mail_fetch_overview_default (stream,ofn);
1547 /* Mail fetch message overview using sequence numbers instead of UIDs
1548 * Accepts: mail stream
1549 * sequence to fetch
1550 * pointer to overview return function
1553 void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence,
1554 overview_t ofn)
1556 if (stream->dtb && mail_sequence (stream,sequence) &&
1557 !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
1558 mail_ping (stream))
1559 mail_fetch_overview_default (stream,ofn);
1563 /* Mail fetch message overview default handler
1564 * Accepts: mail stream with sequence bits lit
1565 * pointer to overview return function
1568 void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn)
1570 MESSAGECACHE *elt;
1571 ENVELOPE *env;
1572 OVERVIEW ov;
1573 unsigned long i;
1574 ov.optional.lines = 0;
1575 ov.optional.xref = NIL;
1576 for (i = 1; i <= stream->nmsgs; i++)
1577 if (((elt = mail_elt (stream,i))->sequence) &&
1578 (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
1579 ov.subject = env->subject;
1580 ov.from = env->from;
1581 ov.date = env->date;
1582 ov.message_id = env->message_id;
1583 ov.references = env->references;
1584 ov.optional.octets = elt->rfc822_size;
1585 (*ofn) (stream,mail_uid (stream,i),&ov,i);
1589 /* Mail fetch message structure
1590 * Accepts: mail stream
1591 * message # to fetch
1592 * pointer to return body
1593 * option flags
1594 * Returns: envelope of this message, body returned in body value
1596 * Fetches the "fast" information as well
1599 ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno,
1600 BODY **body,long flags)
1602 ENVELOPE **env;
1603 BODY **b;
1604 MESSAGECACHE *elt;
1605 char c,*s,*hdr;
1606 unsigned long hdrsize;
1607 STRING bs;
1608 /* do the driver's action if specified */
1609 if (stream->dtb && stream->dtb->structure)
1610 return (*stream->dtb->structure) (stream,msgno,body,flags);
1611 if (flags & FT_UID) { /* UID form of call */
1612 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
1613 else return NIL; /* must get UID/msgno map first */
1615 elt = mail_elt (stream,msgno);/* get elt for real message number */
1616 if (stream->scache) { /* short caching */
1617 if (msgno != stream->msgno){/* garbage collect if not same message */
1618 mail_gc (stream,GC_ENV | GC_TEXTS);
1619 stream->msgno = msgno; /* this is the current message now */
1621 env = &stream->env; /* get pointers to envelope and body */
1622 b = &stream->body;
1624 else { /* get pointers to elt envelope and body */
1625 env = &elt->private.msg.env;
1626 b = &elt->private.msg.body;
1629 if (stream->dtb && ((body && !*b) || !*env || (*env)->incomplete)) {
1630 mail_free_envelope (env); /* flush old envelope and body */
1631 mail_free_body (b);
1632 /* see if need to fetch the whole thing */
1633 if (body || !elt->rfc822_size) {
1634 s = (*stream->dtb->header) (stream,msgno,&hdrsize,flags & ~FT_INTERNAL);
1635 /* make copy in case body fetch smashes it */
1636 hdr = (char *) memcpy (fs_get ((size_t) hdrsize+1),s,(size_t) hdrsize);
1637 hdr[hdrsize] = '\0'; /* tie off header */
1638 (*stream->dtb->text) (stream,msgno,&bs,(flags & ~FT_INTERNAL) | FT_PEEK);
1639 if (!elt->rfc822_size) elt->rfc822_size = hdrsize + SIZE (&bs);
1640 if (body) /* only parse body if requested */
1641 rfc822_parse_msg (env,b,hdr,hdrsize,&bs,BADHOST,stream->dtb->flags);
1642 else
1643 rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
1644 fs_give ((void **) &hdr); /* flush header */
1646 else { /* can save memory doing it this way */
1647 hdr = (*stream->dtb->header) (stream,msgno,&hdrsize,flags | FT_INTERNAL);
1648 if (hdrsize) { /* in case null header */
1649 c = hdr[hdrsize]; /* preserve what's there */
1650 hdr[hdrsize] = '\0'; /* tie off header */
1651 rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
1652 hdr[hdrsize] = c; /* restore in case cached data */
1654 else *env = mail_newenvelope ();
1657 /* if need date, have date in envelope? */
1658 if (!elt->day && *env && (*env)->date) mail_parse_date (elt,(*env)->date);
1659 /* sigh, fill in bogus default */
1660 if (!elt->day) elt->day = elt->month = 1;
1661 if (body) *body = *b; /* return the body */
1662 return *env; /* return the envelope */
1665 /* Mail mark single message (internal use only)
1666 * Accepts: mail stream
1667 * elt to mark
1668 * fetch flags
1671 static void markseen (MAILSTREAM *stream,MESSAGECACHE *elt,long flags)
1673 unsigned long i;
1674 char sequence[20];
1675 MESSAGECACHE *e;
1676 /* non-peeking and needs to set \Seen? */
1677 if (!(flags & FT_PEEK) && !elt->seen) {
1678 if (stream->dtb->flagmsg){ /* driver wants per-message call? */
1679 elt->valid = NIL; /* do pre-alteration driver call */
1680 (*stream->dtb->flagmsg) (stream,elt);
1681 /* set seen, do post-alteration driver call */
1682 elt->seen = elt->valid = T;
1683 (*stream->dtb->flagmsg) (stream,elt);
1685 if (stream->dtb->flag) { /* driver wants one-time call? */
1686 /* better safe than sorry, save seq bits */
1687 for (i = 1; i <= stream->nmsgs; i++) {
1688 e = mail_elt (stream,i);
1689 e->private.sequence = e->sequence;
1691 /* call driver to set the message */
1692 sprintf (sequence,"%lu",elt->msgno);
1693 (*stream->dtb->flag) (stream,sequence,"\\Seen",ST_SET);
1694 /* restore sequence bits */
1695 for (i = 1; i <= stream->nmsgs; i++) {
1696 e = mail_elt (stream,i);
1697 e->sequence = e->private.sequence;
1700 /* notify mail program of flag change */
1701 MM_FLAGS (stream,elt->msgno);
1705 /* Mail fetch message
1706 * Accepts: mail stream
1707 * message # to fetch
1708 * pointer to returned length
1709 * flags
1710 * Returns: message text
1713 char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno,
1714 unsigned long *len,long flags)
1716 GETS_DATA md;
1717 SIZEDTEXT *t;
1718 STRING bs;
1719 MESSAGECACHE *elt;
1720 char *s,*u;
1721 unsigned long i,j;
1722 if (len) *len = 0; /* default return size */
1723 if (flags & FT_UID) { /* UID form of call */
1724 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
1725 else return ""; /* must get UID/msgno map first */
1727 /* initialize message data identifier */
1728 INIT_GETS (md,stream,msgno,"",0,0);
1729 /* is data already cached? */
1730 if ((t = &(elt = mail_elt (stream,msgno))->private.msg.full.text)->data) {
1731 markseen (stream,elt,flags);/* mark message seen */
1732 return mail_fetch_text_return (&md,t,len);
1734 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1735 if (stream->dtb->msgdata) return
1736 ((*stream->dtb->msgdata) (stream,msgno,"",0,0,NIL,flags) && t->data) ?
1737 mail_fetch_text_return (&md,t,len) : "";
1738 /* ugh, have to do this the crufty way */
1739 u = mail_fetch_header (stream,msgno,NIL,NIL,&i,flags);
1740 /* copy in case text method stomps on it */
1741 s = (char *) memcpy (fs_get ((size_t) i),u,(size_t) i);
1742 if ((*stream->dtb->text) (stream,msgno,&bs,flags)) {
1743 t = &stream->text; /* build combined copy */
1744 if (t->data) fs_give ((void **) &t->data);
1745 t->data = (unsigned char *) fs_get ((t->size = i + SIZE (&bs)) + 1);
1746 if (!elt->rfc822_size) elt->rfc822_size = t->size;
1747 else if (elt->rfc822_size != t->size) {
1748 char tmp[MAILTMPLEN];
1749 sprintf (tmp,"Calculated RFC822.SIZE (%lu) != reported size (%lu)",
1750 t->size,elt->rfc822_size);
1751 mm_log (tmp,WARN); /* bug trap */
1753 memcpy (t->data,s,(size_t) i);
1754 for (u = (char *) t->data + i, j = SIZE (&bs); j;) {
1755 memcpy (u,bs.curpos,bs.cursize);
1756 u += bs.cursize; /* update text */
1757 j -= bs.cursize;
1758 bs.curpos += (bs.cursize -1);
1759 bs.cursize = 0;
1760 (*bs.dtb->next) (&bs); /* advance to next buffer's worth */
1762 *u = '\0'; /* tie off data */
1763 u = mail_fetch_text_return (&md,t,len);
1765 else u = "";
1766 fs_give ((void **) &s); /* finished with copy of header */
1767 return u;
1770 /* Mail fetch message header
1771 * Accepts: mail stream
1772 * message # to fetch
1773 * MIME section specifier (#.#.#...#)
1774 * list of lines to fetch
1775 * pointer to returned length
1776 * flags
1777 * Returns: message header in RFC822 format
1779 * Note: never calls a mailgets routine
1782 char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section,
1783 STRINGLIST *lines,unsigned long *len,long flags)
1785 STRING bs;
1786 BODY *b = NIL;
1787 SIZEDTEXT *t = NIL,rt;
1788 MESSAGE *m = NIL;
1789 MESSAGECACHE *elt;
1790 char tmp[MAILTMPLEN];
1791 if (len) *len = 0; /* default return size */
1792 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1793 if (flags & FT_UID) { /* UID form of call */
1794 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
1795 else return ""; /* must get UID/msgno map first */
1797 elt = mail_elt (stream,msgno);/* get cache data */
1798 if (section && *section) { /* nested body header wanted? */
1799 if (!((b = mail_body (stream,msgno,section)) &&
1800 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
1801 return ""; /* lose if no body or not MESSAGE/RFC822 */
1802 m = b->nested.msg; /* point to nested message */
1804 /* else top-level message header wanted */
1805 else m = &elt->private.msg;
1806 if (m->header.text.data && mail_match_lines (lines,m->lines,flags)) {
1807 if (lines) textcpy (t = &stream->text,&m->header.text);
1808 else t = &m->header.text; /* in cache, and cache is valid */
1809 markseen (stream,elt,flags);/* mark message seen */
1812 else if (stream->dtb) { /* not in cache, has live driver? */
1813 if (stream->dtb->msgdata) { /* has driver section fetch? */
1814 /* build driver section specifier */
1815 if (section && *section) sprintf (tmp,"%s.HEADER",section);
1816 else strcpy (tmp,"HEADER");
1817 if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,lines,flags)) {
1818 t = &m->header.text; /* fetch data */
1819 /* don't need to postprocess lines */
1820 if (m->lines) lines = NIL;
1821 else if (lines) textcpy (t = &stream->text,&m->header.text);
1824 else if (b) { /* nested body wanted? */
1825 if (stream->private.search.text) {
1826 rt.data = (unsigned char *) stream->private.search.text +
1827 b->nested.msg->header.offset;
1828 rt.size = b->nested.msg->header.text.size;
1829 t = &rt;
1831 else if ((*stream->dtb->text) (stream,msgno,&bs,flags & ~FT_INTERNAL)) {
1832 if ((bs.dtb->next == mail_string_next) && !lines) {
1833 rt.data = (unsigned char *) bs.curpos + b->nested.msg->header.offset;
1834 rt.size = b->nested.msg->header.text.size;
1835 if (stream->private.search.string)
1836 stream->private.search.text = bs.curpos;
1837 t = &rt; /* special hack to avoid extra copy */
1839 else textcpyoffstring (t = &stream->text,&bs,
1840 b->nested.msg->header.offset,
1841 b->nested.msg->header.text.size);
1844 else { /* top-level header fetch */
1845 /* mark message seen */
1846 markseen (stream,elt,flags);
1847 if ((rt.data = (unsigned char *)
1848 (*stream->dtb->header) (stream,msgno,&rt.size,flags)) != NULL) {
1849 /* make a safe copy if need to filter */
1850 if (lines) textcpy (t = &stream->text,&rt);
1851 else t = &rt; /* top level header */
1855 if (!t || !t->data) return "";/* error if no string */
1856 /* filter headers if requested */
1857 if (lines) t->size = mail_filter ((char *) t->data,t->size,lines,flags);
1858 if (len) *len = t->size; /* return size if requested */
1859 return (char *) t->data; /* and text */
1862 /* Mail fetch message text
1863 * Accepts: mail stream
1864 * message # to fetch
1865 * MIME section specifier (#.#.#...#)
1866 * pointer to returned length
1867 * flags
1868 * Returns: message text
1871 char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section,
1872 unsigned long *len,long flags)
1874 GETS_DATA md;
1875 PARTTEXT *p;
1876 STRING bs;
1877 MESSAGECACHE *elt;
1878 BODY *b = NIL;
1879 char tmp[MAILTMPLEN];
1880 unsigned long i;
1881 if (len) *len = 0; /* default return size */
1882 memset (&stream->private.string,NIL,sizeof (STRING));
1883 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1884 if (flags & FT_UID) { /* UID form of call */
1885 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
1886 else return ""; /* must get UID/msgno map first */
1888 elt = mail_elt (stream,msgno);/* get cache data */
1889 if (section && *section) { /* nested body text wanted? */
1890 if (!((b = mail_body (stream,msgno,section)) &&
1891 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
1892 return ""; /* lose if no body or not MESSAGE/RFC822 */
1893 p = &b->nested.msg->text; /* point at nested message */
1894 /* build IMAP-format section specifier */
1895 sprintf (tmp,"%s.TEXT",section);
1896 flags &= ~FT_INTERNAL; /* can't win with this set */
1898 else { /* top-level message text wanted */
1899 p = &elt->private.msg.text;
1900 strcpy (tmp,"TEXT");
1902 /* initialize message data identifier */
1903 INIT_GETS (md,stream,msgno,section,0,0);
1904 if (p->text.data) { /* is data already cached? */
1905 markseen (stream,elt,flags);/* mark message seen */
1906 return mail_fetch_text_return (&md,&p->text,len);
1908 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1909 if (stream->dtb->msgdata) return
1910 ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && p->text.data)?
1911 mail_fetch_text_return (&md,&p->text,len) : "";
1912 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return "";
1913 if (section && *section) { /* nested is more complex */
1914 SETPOS (&bs,p->offset);
1915 i = p->text.size; /* just want this much */
1917 else i = SIZE (&bs); /* want entire text */
1918 return mail_fetch_string_return (&md,&bs,i,len,flags);
1921 /* Mail fetch message body part MIME headers
1922 * Accepts: mail stream
1923 * message # to fetch
1924 * MIME section specifier (#.#.#...#)
1925 * pointer to returned length
1926 * flags
1927 * Returns: message text
1930 char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section,
1931 unsigned long *len,long flags)
1933 PARTTEXT *p;
1934 STRING bs;
1935 BODY *b;
1936 char tmp[MAILTMPLEN];
1937 if (len) *len = 0; /* default return size */
1938 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1939 if (flags & FT_UID) { /* UID form of call */
1940 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
1941 else return ""; /* must get UID/msgno map first */
1943 flags &= ~FT_INTERNAL; /* can't win with this set */
1944 if (!(section && *section && (b = mail_body (stream,msgno,section))))
1945 return ""; /* not valid section */
1946 /* in cache? */
1947 if ((p = &b->mime)->text.data) {
1948 /* mark message seen */
1949 markseen (stream,mail_elt (stream,msgno),flags);
1950 if (len) *len = p->text.size;
1951 return (char *) p->text.data;
1953 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1954 if (stream->dtb->msgdata) { /* has driver fetch? */
1955 /* build driver section specifier */
1956 sprintf (tmp,"%s.MIME",section);
1957 if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) &&
1958 p->text.data) {
1959 if (len) *len = p->text.size;
1960 return (char *) p->text.data;
1962 else return "";
1964 if (len) *len = b->mime.text.size;
1965 if (!b->mime.text.size) { /* empty MIME header -- mark seen anyway */
1966 markseen (stream,mail_elt (stream,msgno),flags);
1967 return "";
1969 /* have to get it from offset */
1970 if (stream->private.search.text)
1971 return stream->private.search.text + b->mime.offset;
1972 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
1973 if (len) *len = 0;
1974 return "";
1976 if (bs.dtb->next == mail_string_next) {
1977 if (stream->private.search.string) stream->private.search.text = bs.curpos;
1978 return bs.curpos + b->mime.offset;
1980 return textcpyoffstring (&stream->text,&bs,b->mime.offset,b->mime.text.size);
1983 /* Mail fetch message body part
1984 * Accepts: mail stream
1985 * message # to fetch
1986 * MIME section specifier (#.#.#...#)
1987 * pointer to returned length
1988 * flags
1989 * Returns: message body
1992 char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section,
1993 unsigned long *len,long flags)
1995 GETS_DATA md;
1996 PARTTEXT *p;
1997 STRING bs;
1998 BODY *b;
1999 SIZEDTEXT *t;
2000 char *s,tmp[MAILTMPLEN];
2001 memset (&stream->private.string,NIL,sizeof (STRING));
2002 if (!(section && *section)) /* top-level text wanted? */
2003 return mail_fetch_message (stream,msgno,len,flags);
2004 else if (strlen (section) > (MAILTMPLEN - 20)) return "";
2005 flags &= ~FT_INTERNAL; /* can't win with this set */
2006 /* initialize message data identifier */
2007 INIT_GETS (md,stream,msgno,section,0,0);
2008 /* kludge for old section 0 header */
2009 if (!strcmp (s = strcpy (tmp,section),"0") ||
2010 ((s = strstr (tmp,".0")) && !s[2])) {
2011 SIZEDTEXT ht;
2012 *s = '\0'; /* tie off section */
2013 /* this silly way so it does mailgets */
2014 ht.data = (unsigned char *) mail_fetch_header (stream,msgno,
2015 tmp[0] ? tmp : NIL,NIL,
2016 &ht.size,flags);
2017 /* may have UIDs here */
2018 md.flags = (flags & FT_UID) ? MG_UID : NIL;
2019 return mail_fetch_text_return (&md,&ht,len);
2021 if (len) *len = 0; /* default return size */
2022 if (flags & FT_UID) { /* UID form of call */
2023 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
2024 else return ""; /* must get UID/msgno map first */
2026 /* must have body */
2027 if (!(b = mail_body (stream,msgno,section))) return "";
2028 /* have cached text? */
2029 if ((t = &(p = &b->contents)->text)->data) {
2030 /* mark message seen */
2031 markseen (stream,mail_elt (stream,msgno),flags);
2032 return mail_fetch_text_return (&md,t,len);
2034 if (!stream->dtb) return ""; /* not in cache, must have live driver */
2035 if (stream->dtb->msgdata) return
2036 ((*stream->dtb->msgdata)(stream,msgno,section,0,0,NIL,flags) && t->data) ?
2037 mail_fetch_text_return (&md,t,len) : "";
2038 if (len) *len = t->size;
2039 if (!t->size) { /* empty body part -- mark seen anyway */
2040 markseen (stream,mail_elt (stream,msgno),flags);
2041 return "";
2043 /* copy body from stringstruct offset */
2044 if (stream->private.search.text)
2045 return stream->private.search.text + p->offset;
2046 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
2047 if (len) *len = 0;
2048 return "";
2050 if (bs.dtb->next == mail_string_next) {
2051 if (stream->private.search.string) stream->private.search.text = bs.curpos;
2052 return bs.curpos + p->offset;
2054 SETPOS (&bs,p->offset);
2055 return mail_fetch_string_return (&md,&bs,t->size,len,flags);
2058 /* Mail fetch partial message text
2059 * Accepts: mail stream
2060 * message # to fetch
2061 * MIME section specifier (#.#.#...#)
2062 * offset of first designed byte or 0 to start at beginning
2063 * maximum number of bytes or 0 for all bytes
2064 * flags
2065 * Returns: T if successful, else NIL
2068 long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section,
2069 unsigned long first,unsigned long last,long flags)
2071 GETS_DATA md;
2072 PARTTEXT *p = NIL;
2073 MESSAGECACHE *elt;
2074 STRING bs;
2075 BODY *b;
2076 char tmp[MAILTMPLEN];
2077 unsigned long i;
2078 if (!mailgets) fatal ("mail_partial_text() called without a mailgets!");
2079 if (section && (strlen (section) > (MAILTMPLEN - 20))) return NIL;
2080 if (flags & FT_UID) { /* UID form of call */
2081 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
2082 else return NIL; /* must get UID/msgno map first */
2084 elt = mail_elt (stream,msgno);/* get cache data */
2085 flags &= ~FT_INTERNAL; /* bogus if this is set */
2086 if (section && *section) { /* nested body text wanted? */
2087 if (!((b = mail_body (stream,msgno,section)) &&
2088 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
2089 return NIL; /* lose if no body or not MESSAGE/RFC822 */
2090 p = &b->nested.msg->text; /* point at nested message */
2091 /* build IMAP-format section specifier */
2092 sprintf (tmp,"%s.TEXT",section);
2094 else { /* else top-level message text wanted */
2095 p = &elt->private.msg.text;
2096 strcpy (tmp,"TEXT");
2099 /* initialize message data identifier */
2100 INIT_GETS (md,stream,msgno,tmp,first,last);
2101 if (p->text.data) { /* is data already cached? */
2102 INIT (&bs,mail_string,p->text.data,i = p->text.size);
2103 markseen (stream,elt,flags);/* mark message seen */
2105 else { /* else get data from driver */
2106 if (!stream->dtb) return NIL;
2107 if (stream->dtb->msgdata) /* driver will handle this */
2108 return (*stream->dtb->msgdata) (stream,msgno,tmp,first,last,NIL,flags);
2109 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
2110 if (section && *section) { /* nexted if more complex */
2111 SETPOS (&bs,p->offset); /* offset stringstruct to data */
2112 i = p->text.size; /* maximum size of data */
2114 else i = SIZE (&bs); /* just want this much */
2116 if (i <= first) i = first = 0;/* first byte is beyond end of text */
2117 /* truncate as needed */
2118 else { /* offset and truncate */
2119 SETPOS (&bs,first + GETPOS (&bs));
2120 i -= first; /* reduced size */
2121 if (last && (i > last)) i = last;
2123 /* do the mailgets thing */
2124 (*mailgets) (mail_read,&bs,i,&md);
2125 return T; /* success */
2128 /* Mail fetch partial message body part
2129 * Accepts: mail stream
2130 * message # to fetch
2131 * MIME section specifier (#.#.#...#)
2132 * offset of first designed byte or 0 to start at beginning
2133 * maximum number of bytes or 0 for all bytes
2134 * flags
2135 * Returns: T if successful, else NIL
2138 long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section,
2139 unsigned long first,unsigned long last,long flags)
2141 GETS_DATA md;
2142 PARTTEXT *p;
2143 STRING bs;
2144 BODY *b;
2145 SIZEDTEXT *t;
2146 unsigned long i;
2147 if (!(section && *section)) /* top-level text wanted? */
2148 return mail_partial_text (stream,msgno,NIL,first,last,flags);
2149 if (!mailgets) fatal ("mail_partial_body() called without a mailgets!");
2150 if (flags & FT_UID) { /* UID form of call */
2151 if ((msgno = mail_msgno (stream,msgno)) != 0L) flags &= ~FT_UID;
2152 else return NIL; /* must get UID/msgno map first */
2154 /* must have body */
2155 if (!(b = mail_body (stream,msgno,section))) return NIL;
2156 flags &= ~FT_INTERNAL; /* bogus if this is set */
2158 /* initialize message data identifier */
2159 INIT_GETS (md,stream,msgno,section,first,last);
2160 /* have cached text? */
2161 if ((t = &(p = &b->contents)->text)->data) {
2162 /* mark message seen */
2163 markseen (stream,mail_elt (stream,msgno),flags);
2164 INIT (&bs,mail_string,t->data,i = t->size);
2166 else { /* else get data from driver */
2167 if (!stream->dtb) return NIL;
2168 if (stream->dtb->msgdata) /* driver will handle this */
2169 return (*stream->dtb->msgdata) (stream,msgno,section,first,last,NIL,
2170 flags);
2171 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
2172 if (section && *section) { /* nexted if more complex */
2173 SETPOS (&bs,p->offset); /* offset stringstruct to data */
2174 i = t->size; /* maximum size of data */
2176 else i = SIZE (&bs); /* just want this much */
2178 if (i <= first) i = first = 0;/* first byte is beyond end of text */
2179 else { /* offset and truncate */
2180 SETPOS (&bs,first + GETPOS (&bs));
2181 i -= first; /* reduced size */
2182 if (last && (i > last)) i = last;
2184 /* do the mailgets thing */
2185 (*mailgets) (mail_read,&bs,i,&md);
2186 return T; /* success */
2189 /* Mail return message text
2190 * Accepts: identifier data
2191 * sized text
2192 * pointer to returned length
2193 * Returns: text
2196 char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len)
2198 STRING bs;
2199 if (len) *len = t->size; /* return size */
2200 if (t->size && mailgets) { /* have to do the mailgets thing? */
2201 /* silly but do it anyway for consistency */
2202 INIT (&bs,mail_string,t->data,t->size);
2203 return (*mailgets) (mail_read,&bs,t->size,md);
2205 return t->size ? (char *) t->data : "";
2209 /* Mail return message string
2210 * Accepts: identifier data
2211 * stringstruct
2212 * text length
2213 * pointer to returned length
2214 * flags
2215 * Returns: text, or NIL if stringstruct returned
2218 char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i,
2219 unsigned long *len,long flags)
2221 char *ret = NIL;
2222 if (len) *len = i; /* return size */
2223 /* return stringstruct hack */
2224 if (flags & FT_RETURNSTRINGSTRUCT) {
2225 memcpy (&md->stream->private.string,bs,sizeof (STRING));
2226 SETPOS (&md->stream->private.string,GETPOS (&md->stream->private.string));
2228 /* have to do the mailgets thing? */
2229 else if (mailgets) ret = (*mailgets) (mail_read,bs,i,md);
2230 /* special hack to avoid extra copy */
2231 else if (bs->dtb->next == mail_string_next) ret = bs->curpos;
2232 /* make string copy in memory */
2233 else ret = textcpyoffstring (&md->stream->text,bs,GETPOS (bs),i);
2234 return ret;
2237 /* Read data from stringstruct
2238 * Accepts: stringstruct
2239 * size of data to read
2240 * buffer to read into
2241 * Returns: T, always, stringstruct updated
2244 long mail_read (void *stream,unsigned long size,char *buffer)
2246 unsigned long i;
2247 STRING *s = (STRING *) stream;
2248 while (size) { /* until satisfied */
2249 memcpy (buffer,s->curpos,i = min (s->cursize,size));
2250 buffer += i; /* update buffer */
2251 size -= i; /* note that we read this much */
2252 s->curpos += --i; /* advance that many spaces minus 1 */
2253 s->cursize -= i;
2254 SNX (s); /* now use SNX to advance the last byte */
2256 return T;
2259 /* Mail fetch UID
2260 * Accepts: mail stream
2261 * message number
2262 * Returns: UID or zero if dead stream
2265 unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno)
2267 unsigned long uid = mail_elt (stream,msgno)->private.uid;
2268 return uid ? uid :
2269 (stream->dtb && stream->dtb->uid) ? (*stream->dtb->uid) (stream,msgno) : 0;
2273 /* Mail fetch msgno from UID
2274 * Accepts: mail stream
2275 * UID
2276 * Returns: msgno or zero if failed
2279 unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid)
2281 unsigned long msgno,delta,first,firstuid,last,lastuid,middle,miduid;
2282 if (stream->dtb) { /* active stream? */
2283 if (stream->dtb->msgno) /* direct way */
2284 return (*stream->dtb->msgno) (stream,uid);
2285 else if (stream->dtb->uid) {/* indirect way */
2286 /* Placeholder for now, since currently there are no drivers which
2287 * have a uid method but not a msgno method
2289 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2290 if ((*stream->dtb->uid) (stream,msgno) == uid) return msgno;
2292 /* binary search since have full map */
2293 else for (first = 1,last = stream->nmsgs, delta = (first <= last) ? 1 : 0;
2294 delta &&
2295 (uid >= (firstuid = mail_elt (stream,first)->private.uid)) &&
2296 (uid <= (lastuid = mail_elt (stream,last)->private.uid));) {
2297 /* done if match at an endpoint */
2298 if (uid == firstuid) return first;
2299 if (uid == lastuid) return last;
2300 /* have anything between endpoints? */
2301 if ((delta = ((last - first) / 2)) != 0L){
2302 if ((miduid = mail_elt (stream,middle = first + delta)->private.uid)
2303 == uid)
2304 return middle; /* found match in middle */
2305 else if (uid < miduid) last = middle - 1;
2306 else first = middle + 1;
2310 else { /* dead stream, do linear search for UID */
2311 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2312 if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
2314 return 0; /* didn't find the UID anywhere */
2317 /* Mail fetch From string for menu
2318 * Accepts: destination string
2319 * mail stream
2320 * message # to fetch
2321 * desired string length
2322 * Returns: string of requested length
2325 void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno,
2326 long length)
2328 char *t;
2329 char tmp[MAILTMPLEN];
2330 ENVELOPE *env = mail_fetchenvelope (stream,msgno);
2331 ADDRESS *adr = env ? env->from : NIL;
2332 memset (s,' ',(size_t)length);/* fill it with spaces */
2333 s[length] = '\0'; /* tie off with null */
2334 /* get first from address from envelope */
2335 while (adr && !adr->host) adr = adr->next;
2336 if (adr) { /* if a personal name exists use it */
2337 if (!(t = adr->personal))
2338 sprintf (t = tmp,"%.256s@%.256s",adr->mailbox,adr->host);
2339 memcpy (s,t,(size_t) min (length,(long) strlen (t)));
2344 /* Mail fetch Subject string for menu
2345 * Accepts: destination string
2346 * mail stream
2347 * message # to fetch
2348 * desired string length
2349 * Returns: string of no more than requested length
2352 void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno,
2353 long length)
2355 ENVELOPE *env = mail_fetchenvelope (stream,msgno);
2356 memset (s,'\0',(size_t) length+1);
2357 /* copy subject from envelope */
2358 if (env && env->subject) strncpy (s,env->subject,(size_t) length);
2359 else *s = ' '; /* if no subject then just a space */
2362 /* Mail modify flags
2363 * Accepts: mail stream
2364 * sequence
2365 * flag(s)
2366 * option flags
2369 void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
2371 MESSAGECACHE *elt;
2372 unsigned long i,uf;
2373 long f;
2374 short nf;
2375 if (!stream->dtb) return; /* no-op if no stream */
2376 if ((stream->dtb->flagmsg || !stream->dtb->flag) &&
2377 ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
2378 mail_sequence (stream,sequence)) &&
2379 ((f = mail_parse_flags (stream,flag,&uf)) || uf))
2380 for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
2381 if ((elt = mail_elt (stream,i))->sequence) {
2382 struct { /* old flags */
2383 unsigned int valid : 1;
2384 unsigned int seen : 1;
2385 unsigned int deleted : 1;
2386 unsigned int flagged : 1;
2387 unsigned int answered : 1;
2388 unsigned int draft : 1;
2389 unsigned long user_flags;
2390 } old;
2391 old.valid = elt->valid; old.seen = elt->seen;
2392 old.deleted = elt->deleted; old.flagged = elt->flagged;
2393 old.answered = elt->answered; old.draft = elt->draft;
2394 old.user_flags = elt->user_flags;
2395 elt->valid = NIL; /* prepare for flag alteration */
2396 if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
2397 if (f&fSEEN) elt->seen = nf;
2398 if (f&fDELETED) elt->deleted = nf;
2399 if (f&fFLAGGED) elt->flagged = nf;
2400 if (f&fANSWERED) elt->answered = nf;
2401 if (f&fDRAFT) elt->draft = nf;
2402 /* user flags */
2403 if (flags & ST_SET) elt->user_flags |= uf;
2404 else elt->user_flags &= ~uf;
2405 elt->valid = T; /* flags now altered */
2406 if ((old.valid != elt->valid) || (old.seen != elt->seen) ||
2407 (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
2408 (old.answered != elt->answered) || (old.draft != elt->draft) ||
2409 (old.user_flags != elt->user_flags))
2410 MM_FLAGS (stream,elt->msgno);
2411 if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
2413 /* call driver once */
2414 if (stream->dtb->flag) (*stream->dtb->flag) (stream,sequence,flag,flags);
2417 /* Mail search for messages
2418 * Accepts: mail stream
2419 * character set
2420 * search program
2421 * option flags
2422 * Returns: T if successful, NIL if dead stream, NIL searchpgm or bad charset
2425 long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
2426 long flags)
2428 unsigned long i;
2429 long ret = NIL;
2430 if (!(flags & SE_RETAIN)) /* clear search vector unless retaining */
2431 for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
2432 if (pgm && stream->dtb) /* must have a search program and driver */
2433 ret = (*(stream->dtb->search ? stream->dtb->search : mail_search_default))
2434 (stream,charset,pgm,flags);
2435 /* flush search program if requested */
2436 if (flags & SE_FREE) mail_free_searchpgm (&pgm);
2437 return ret;
2441 /* Mail search for messages default handler
2442 * Accepts: mail stream
2443 * character set
2444 * search program
2445 * option flags
2446 * Returns: T if successful, NIL if bad charset
2449 long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
2450 long flags)
2452 unsigned long i;
2453 char *msg;
2454 /* make sure that charset is good */
2455 if ((msg = utf8_badcharset (charset)) != NULL) {
2456 MM_LOG (msg,ERROR); /* output error */
2457 fs_give ((void **) &msg);
2458 return NIL;
2460 utf8_searchpgm (pgm,charset);
2461 for (i = 1; i <= stream->nmsgs; ++i)
2462 if (mail_search_msg (stream,i,NIL,pgm)) {
2463 if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
2464 else { /* mark as searched, notify mail program */
2465 mail_elt (stream,i)->searched = T;
2466 if (!stream->silent) mm_searched (stream,i);
2469 return LONGT; /* search completed */
2472 /* Mail ping mailbox
2473 * Accepts: mail stream
2474 * Returns: stream if still open else NIL
2477 long mail_ping (MAILSTREAM *stream)
2479 unsigned long i,n,uf,len;
2480 char *s,*f,tmp[MAILTMPLEN],flags[MAILTMPLEN];
2481 MAILSTREAM *snarf;
2482 MESSAGECACHE *elt;
2483 STRING bs;
2484 long ret;
2485 /* do driver action */
2486 if ((ret = ((stream && stream->dtb) ? (stream->dtb->ping) (stream) : NIL)) &&
2487 stream->snarf.name && /* time to snarf? */
2488 /* prohibit faster than once/min */
2489 (time (0) > (time_t) (stream->snarf.time + min(60,mailsnarfinterval))) &&
2490 (snarf = mail_open (NIL,stream->snarf.name,
2491 stream->snarf.options | OP_SILENT))) {
2492 if ((n = snarf->nmsgs) && /* yes, have messages to snarf? */
2493 mail_search_full (snarf,NIL,mail_criteria ("UNDELETED"),SE_FREE)) {
2494 for (i = 1; ret && (i <= n); i++) /* for each message */
2495 if ((elt = mail_elt (snarf,i))->searched &&
2496 (s = mail_fetch_message (snarf,i,&len,FT_PEEK)) && len) {
2497 INIT (&bs,mail_string,s,len);
2498 if (mailsnarfpreserve) {
2499 /* yes, make sure have fast data */
2500 if (!elt->valid || !elt->day) {
2501 sprintf (tmp,"%lu",n);
2502 mail_fetch_fast (snarf,tmp,NIL);
2504 /* initialize flag string */
2505 memset (flags,0,MAILTMPLEN);
2506 /* output system flags except \Deleted */
2507 if (elt->seen) strcat (flags," \\Seen");
2508 if (elt->flagged) strcat (flags," \\Flagged");
2509 if (elt->answered) strcat (flags," \\Answered");
2510 if (elt->draft) strcat (flags," \\Draft");
2511 /* any user flags? */
2512 for (uf = elt->user_flags,s = flags + strlen (flags);
2513 uf && (f = stream->user_flags[find_rightmost_bit (&uf)]) &&
2514 ((MAILTMPLEN - (s - tmp)) > (long) (2 + strlen (f)));
2515 s += strlen (s)) sprintf (s," %s",f);
2516 ret = mail_append_full (stream,stream->mailbox,flags + 1,
2517 mail_date (tmp,elt),&bs);
2519 else ret = mail_append (stream,stream->mailbox,&bs);
2521 if (ret) { /* did snarf succeed? */
2522 /* driver has per-message (or no) flag call */
2523 if (snarf->dtb->flagmsg || !snarf->dtb->flag) {
2524 elt->valid = NIL; /* prepare for flag alteration */
2525 if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
2526 /* flags now altered */
2527 elt->deleted = elt->seen = elt->valid = T;
2528 if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
2530 /* driver has one-time flag call */
2531 if (snarf->dtb->flag) {
2532 sprintf (tmp,"%lu",i);
2533 (*snarf->dtb->flag) (snarf,tmp,"\\Deleted \\Seen",ST_SET);
2536 else { /* copy failed */
2537 sprintf (tmp,"Unable to move message %lu from %s mailbox",
2538 i,snarf->dtb->name);
2539 mm_log (tmp,WARN);
2543 /* expunge the messages */
2544 mail_close_full (snarf,n ? CL_EXPUNGE : NIL);
2545 stream->snarf.time = (unsigned long) time (0);
2546 /* Even if the snarf failed, we don't want to return NIL if the stream
2547 * is still alive. Or at least that's what we currently think.
2549 /* redo the driver's action */
2550 ret = stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
2552 return ret;
2555 /* Mail check mailbox
2556 * Accepts: mail stream
2559 void mail_check (MAILSTREAM *stream)
2561 /* do the driver's action */
2562 if (stream->dtb) (*stream->dtb->check) (stream);
2566 /* Mail expunge mailbox
2567 * Accepts: mail stream
2568 * sequence to expunge if non-NIL
2569 * expunge options
2570 * Returns: T on success, NIL on failure
2573 long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options)
2575 /* do the driver's action */
2576 return stream->dtb ? (*stream->dtb->expunge) (stream,sequence,options) : NIL;
2580 /* Mail copy message(s)
2581 * Accepts: mail stream
2582 * sequence
2583 * destination mailbox
2584 * flags
2587 long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox,
2588 long options)
2590 return stream->dtb ?
2591 SAFE_COPY (stream->dtb,stream,sequence,mailbox,options) : NIL;
2594 /* Append data package to use for old single-message mail_append() interface */
2596 typedef struct mail_append_package {
2597 char *flags; /* initial flags */
2598 char *date; /* message internal date */
2599 STRING *message; /* stringstruct of message */
2600 } APPENDPACKAGE;
2603 /* Single append message string
2604 * Accepts: mail stream
2605 * package pointer (cast as a void *)
2606 * pointer to return initial flags
2607 * pointer to return message internal date
2608 * pointer to return stringstruct of message to append
2609 * Returns: T, always
2612 static long mail_append_single (MAILSTREAM *stream,void *data,char **flags,
2613 char **date,STRING **message)
2615 APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
2616 *flags = ap->flags; /* get desired data from the package */
2617 *date = ap->date;
2618 *message = ap->message;
2619 ap->message = NIL; /* so next callback puts a stop to it */
2620 return LONGT; /* always return success */
2624 /* Mail append message string
2625 * Accepts: mail stream
2626 * destination mailbox
2627 * initial flags
2628 * message internal date
2629 * stringstruct of message to append
2630 * Returns: T on success, NIL on failure
2633 long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
2634 STRING *message)
2636 APPENDPACKAGE ap;
2637 ap.flags = flags; /* load append package */
2638 ap.date = date;
2639 ap.message = message;
2640 return mail_append_multiple (stream,mailbox,mail_append_single,(void *) &ap);
2643 /* Mail append message(s)
2644 * Accepts: mail stream
2645 * destination mailbox
2646 * append data callback
2647 * arbitrary data for callback use
2648 * Returns: T on success, NIL on failure
2651 long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af,
2652 void *data)
2654 char *s,tmp[MAILTMPLEN];
2655 DRIVER *d = NIL;
2656 long ret = NIL;
2657 /* never allow names with newlines */
2658 if (strpbrk (mailbox,"\015\012"))
2659 MM_LOG ("Can't append to mailbox with such a name",ERROR);
2660 else if (strlen (mailbox) >=
2661 (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
2662 sprintf (tmp,"Can't append %.80s: %s",mailbox,(*mailbox == '{') ?
2663 "invalid remote specification" : "no such mailbox");
2664 MM_LOG (tmp,ERROR);
2666 /* special driver hack? */
2667 else if (!strncmp (lcase (strcpy (tmp,mailbox)),"#driver.",8)) {
2668 /* yes, tie off name at likely delimiter */
2669 if (!(s = strpbrk (tmp+8,"/\\:"))) {
2670 sprintf (tmp,"Can't append to mailbox %.80s: bad driver syntax",mailbox);
2671 MM_LOG (tmp,ERROR);
2672 return NIL;
2674 *s++ = '\0'; /* tie off at delimiter */
2675 if (!(d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,tmp+8))) {
2676 sprintf (tmp,"Can't append to mailbox %.80s: unknown driver",mailbox);
2677 MM_LOG (tmp,ERROR);
2679 else ret = SAFE_APPEND (d,stream,mailbox + (s - tmp),af,data);
2681 else if ((d = mail_valid (stream,mailbox,NIL)) != NULL)
2682 ret = SAFE_APPEND (d,stream,mailbox,af,data);
2683 /* No driver, try for TRYCREATE if no stream. Note that we use the
2684 * createProto here, not the appendProto, since the dummy driver already
2685 * took care of the appendProto case. Otherwise, if appendProto is set to
2686 * NIL, we won't get a TRYCREATE.
2688 else if (!stream && (stream = default_proto (NIL)) && stream->dtb &&
2689 SAFE_APPEND (stream->dtb,stream,mailbox,af,data))
2690 /* timing race? */
2691 MM_NOTIFY (stream,"Append validity confusion",WARN);
2692 /* generate error message */
2693 else mail_valid (stream,mailbox,"append to mailbox");
2694 return ret;
2697 /* Mail garbage collect stream
2698 * Accepts: mail stream
2699 * garbage collection flags
2702 void mail_gc (MAILSTREAM *stream,long gcflags)
2704 MESSAGECACHE *elt;
2705 unsigned long i;
2706 /* do the driver's action first */
2707 if (stream->dtb && stream->dtb->gc) (*stream->dtb->gc) (stream,gcflags);
2708 stream->msgno = 0; /* nothing cached now */
2709 if (gcflags & GC_ENV) { /* garbage collect envelopes? */
2710 if (stream->env) mail_free_envelope (&stream->env);
2711 if (stream->body) mail_free_body (&stream->body);
2713 if (gcflags & GC_TEXTS) { /* free texts */
2714 if (stream->text.data) fs_give ((void **) &stream->text.data);
2715 stream->text.size = 0;
2717 /* garbage collect per-message stuff */
2718 for (i = 1; i <= stream->nmsgs; i++)
2719 if ((elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) != NULL)
2720 mail_gc_msg (&elt->private.msg,gcflags);
2724 /* Mail garbage collect message
2725 * Accepts: message structure
2726 * garbage collection flags
2729 void mail_gc_msg (MESSAGE *msg,long gcflags)
2731 if (gcflags & GC_ENV) { /* garbage collect envelopes? */
2732 mail_free_envelope (&msg->env);
2733 mail_free_body (&msg->body);
2735 if (gcflags & GC_TEXTS) { /* garbage collect texts */
2736 if (msg->full.text.data) fs_give ((void **) &msg->full.text.data);
2737 if (msg->header.text.data) {
2738 mail_free_stringlist (&msg->lines);
2739 fs_give ((void **) &msg->header.text.data);
2741 if (msg->text.text.data) fs_give ((void **) &msg->text.text.data);
2742 /* now GC all body components */
2743 if (msg->body) mail_gc_body (msg->body);
2747 /* Mail garbage collect texts in BODY structure
2748 * Accepts: BODY structure
2751 void mail_gc_body (BODY *body)
2753 PART *part;
2754 switch (body->type) { /* free contents */
2755 case TYPEMULTIPART: /* multiple part */
2756 for (part = body->nested.part; part; part = part->next)
2757 mail_gc_body (&part->body);
2758 break;
2759 case TYPEMESSAGE: /* encapsulated message */
2760 if (body->subtype && !strcmp (body->subtype,"RFC822")) {
2761 mail_free_stringlist (&body->nested.msg->lines);
2762 mail_gc_msg (body->nested.msg,GC_TEXTS);
2764 break;
2765 default:
2766 break;
2768 if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
2769 if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
2771 /* Mail get body section
2772 * Accepts: body of message
2773 * section specifier
2774 * Returns: pointer to body at given section
2777 BODY *mail_body_section (BODY *b, unsigned char *section)
2779 PART *pt;
2780 unsigned long i;
2781 /* make sure have a body */
2782 if (section && *section && b)
2783 while (*section) { /* find desired section */
2784 if (isdigit (*section)) { /* get section specifier */
2785 /* make sure what follows is valid */
2786 if (!(i = strtoul (section,(char **) &section,10)) ||
2787 (*section && ((*section++ != '.') || !*section))) return NIL;
2788 /* multipart content? */
2789 if (b->type == TYPEMULTIPART) {
2790 /* yes, find desired part */
2791 if ((pt = b->nested.part) != NULL) while (--i && (pt = pt->next));
2792 if (!pt) return NIL; /* bad specifier */
2793 b = &pt->body; /* note new body */
2795 /* otherwise must be section 1 */
2796 else if (i != 1) return NIL;
2797 /* need to go down further? */
2798 if (*section) switch (b->type) {
2799 case TYPEMULTIPART: /* multipart */
2800 break;
2801 case TYPEMESSAGE: /* embedded message */
2802 if (!strcmp (b->subtype,"RFC822")) {
2803 b = b->nested.msg->body;
2804 break;
2806 default: /* bogus subpart specification */
2807 return NIL;
2810 else return NIL; /* unknown section specifier */
2812 return b;
2815 /* Mail get body part
2816 * Accepts: mail stream
2817 * message number
2818 * section specifier
2819 * Returns: pointer to body
2822 BODY *mail_body (MAILSTREAM *stream,unsigned long msgno,unsigned char *section)
2824 BODY *b = NIL;
2825 /* make sure have a body */
2826 if (section && *section && mail_fetchstructure (stream,msgno,&b) && b)
2827 return mail_body_section(b, section);
2828 return b;
2831 /* Mail output date from elt fields
2832 * Accepts: character string to write into
2833 * elt to get data data from
2834 * Returns: the character string
2837 const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
2839 const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2840 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2842 char *mail_date (char *string,MESSAGECACHE *elt)
2844 sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d",
2845 elt->day ? elt->day : 1,
2846 months[elt->month ? (elt->month - 1) : 0],
2847 elt->year + BASEYEAR,elt->hours,elt->minutes,elt->seconds,
2848 elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes);
2849 return string;
2853 /* Mail output extended-ctime format date from elt fields
2854 * Accepts: character string to write into
2855 * elt to get data data from
2856 * Returns: the character string
2859 char *mail_cdate (char *string,MESSAGECACHE *elt)
2861 char *fmt = "%s %s %2d %02d:%02d:%02d %4d %s%02d%02d\n";
2862 int d = elt->day ? elt->day : 1;
2863 int m = elt->month ? (elt->month - 1) : 0;
2864 int y = elt->year + BASEYEAR;
2865 const char *s = months[m];
2866 if (m < 2) { /* if before March, */
2867 m += 10; /* January = month 10 of previous year */
2868 y--;
2870 else m -= 2; /* March is month 0 */
2871 sprintf (string,fmt,days[(int) (d + 2 + ((7 + 31 * m) / 12)
2872 #ifndef USEJULIANCALENDAR
2873 #ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
2874 + (y / 400)
2875 #ifdef Y4KBUGFIX
2876 - (y / 4000)
2877 #endif
2878 #else /* Orthodox calendar */
2879 + (2 * (y / 900)) + ((y % 900) >= 200)
2880 + ((y % 900) >= 600)
2881 #endif
2882 - (y / 100)
2883 #endif
2884 + y + (y / 4)) % 7],
2885 s,d,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR,
2886 elt->zoccident ? "-" : "+",elt->zhours,elt->zminutes);
2887 return string;
2890 /* Mail parse date into elt fields
2891 * Accepts: elt to write into
2892 * date string to parse
2893 * Returns: T if parse successful, else NIL
2894 * This routine parses dates as follows:
2895 * . leading three alphas followed by comma and space are ignored
2896 * . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy,
2897 * dd mmm yy, dd mmm yyyy, yyyy-mm-dd, yyyymmdd
2898 * . two and three digit years interpreted according to RFC 2822 rules
2899 * . mandatory end of string if yyyy-mm-dd or yyyymmdd; otherwise optional
2900 * space followed by time:
2901 * . time accepted in format hh:mm:ss or hh:mm
2902 * . end of string accepted
2903 * . timezone accepted: hyphen followed by symbolic timezone, or space
2904 * followed by signed numeric timezone or symbolic timezone
2905 * Examples of normal input:
2906 * . IMAP date-only (SEARCH):
2907 * dd-mmm-yyyy
2908 * . IMAP date-time (INTERNALDATE):
2909 * dd-mmm-yyyy hh:mm:ss +zzzz
2910 * . RFC-822:
2911 * www, dd mmm yy hh:mm:ss zzz
2912 * . RFC-2822:
2913 * www, dd mmm yyyy hh:mm:ss +zzzz
2916 long mail_parse_date (MESSAGECACHE *elt,unsigned char *s)
2918 unsigned long d,m,y;
2919 int mi,ms;
2920 struct tm *t;
2921 time_t tn;
2922 char tmp[MAILTMPLEN];
2923 static unsigned long maxyear = 0;
2924 if (!maxyear) { /* know the end of time yet? */
2925 MESSAGECACHE tmpelt;
2926 memset (&tmpelt,0xff,sizeof (MESSAGECACHE));
2927 maxyear = BASEYEAR + tmpelt.year;
2929 /* clear elt */
2930 elt->zoccident = elt->zhours = elt->zminutes =
2931 elt->hours = elt->minutes = elt->seconds =
2932 elt->day = elt->month = elt->year = 0;
2933 /* make a writeable uppercase copy */
2934 if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s));
2935 else return NIL;
2936 /* skip over possible day of week */
2937 if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') &&
2938 (s[4] == ' ')) s += 5;
2939 while (*s == ' ') s++; /* parse first number (probable month) */
2940 if (!(m = strtoul (s,(char **) &s,10))) return NIL;
2942 switch (*s) { /* different parse based on delimiter */
2943 case '/': /* mm/dd/yy format */
2944 if (isdigit (*++s) && (d = strtoul (s,(char **) &s,10)) &&
2945 (*s == '/') && isdigit (*++s)) {
2946 y = strtoul (s,(char **) &s,10);
2947 if (*s == '\0') break; /* must end here */
2949 return NIL; /* bogon */
2950 case ' ': /* dd mmm yy format */
2951 while (s[1] == ' ') s++; /* slurp extra whitespace */
2952 case '-':
2953 if (isdigit (s[1])) { /* possible ISO 8601 date format? */
2954 y = m; /* yes, first number is year */
2955 /* get month and day */
2956 if ((m = strtoul (s+1,(char **) &s,10)) && (*s++ == '-') &&
2957 (d = strtoul (s,(char **) &s,10)) && !*s) break;
2958 return NIL; /* syntax error or time present */
2960 d = m; /* dd-mmm-yy[yy], so first number is a day */
2961 /* make sure string long enough! */
2962 if (strlen (s) < (size_t) 5) return NIL;
2963 /* Some compilers don't allow `<<' and/or longs in case statements. */
2964 /* slurp up the month string */
2965 ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A');
2966 switch (ms) { /* determine the month */
2967 case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
2968 case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
2969 case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
2970 case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
2971 case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
2972 case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
2973 case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
2974 case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
2975 case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
2976 case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break;
2977 case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break;
2978 case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break;
2979 default: return NIL; /* unknown month */
2981 if (s[4] == *s) s += 5; /* advance to year */
2982 else { /* first three were OK, possibly full name */
2983 mi = *s; /* note delimiter, skip alphas */
2984 for (s += 4; isalpha (*s); s++);
2985 /* error if delimiter not here */
2986 if (mi != *s++) return NIL;
2988 while (*s == ' ') s++; /* parse year */
2989 if (isdigit (*s)) { /* must be a digit here */
2990 y = strtoul (s,(char **) &s,10);
2991 if (*s == '\0' || *s == ' ') break;
2993 case '\0': /* ISO 8601 compact date */
2994 if (m < (BASEYEAR * 10000)) return NIL;
2995 y = m / 10000; /* get year */
2996 d = (m %= 10000) % 100; /* get day */
2997 m /= 100; /* and month */
2998 break;
2999 default:
3000 return NIL; /* unknown date format */
3003 /* minimal validity check of date */
3004 if ((d > 31) || (m > 12)) return NIL;
3005 if (y < 49) y += 2000; /* RFC 2282 rules for two digit years 00-49 */
3006 else if (y < 999) y += 1900; /* 2-digit years 50-99 and 3-digit years */
3007 /* reject prehistoric and far future years */
3008 if ((y < BASEYEAR) || (y > maxyear)) return NIL;
3009 /* set values in elt */
3010 elt->day = d; elt->month = m; elt->year = y - BASEYEAR;
3011 ms = '\0'; /* initially no time zone string */
3012 if (*s) { /* time specification present? */
3013 /* parse time */
3014 d = strtoul (s+1,(char **) &s,10);
3015 if (*s != ':') return NIL;
3016 m = strtoul (++s,(char **) &s,10);
3017 y = (*s == ':') ? strtoul (++s,(char **) &s,10) : 0;
3018 /* validity check time */
3019 if ((d > 23) || (m > 59) || (y > 60)) return NIL;
3020 /* set values in elt */
3021 elt->hours = d; elt->minutes = m; elt->seconds = y;
3022 switch (*s) { /* time zone specifier? */
3023 case ' ': /* numeric time zone */
3024 while (s[1] == ' ') s++; /* slurp extra whitespace */
3025 if (!isalpha (s[1])) { /* treat as '-' case if alphabetic */
3026 /* test for sign character */
3027 if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++;
3028 /* validate proper timezone */
3029 if (isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && (s[2] < '6') &&
3030 isdigit(s[3])) {
3031 elt->zhours = (*s - '0') * 10 + (s[1] - '0');
3032 elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0');
3034 return T; /* all done! */
3036 /* falls through */
3037 case '-': /* symbolic time zone */
3038 if (!(ms = *++s)) ms = 'Z';
3039 else if (*++s) { /* multi-character? */
3040 ms -= 'A'; ms *= 1024; /* yes, make compressed three-byte form */
3041 ms += ((*s++ - 'A') * 32);
3042 if (*s) ms += *s++ - 'A';
3043 if (*s) ms = '\0'; /* more than three characters */
3045 default: /* ignore anything else */
3046 break;
3050 /* This is not intended to be a comprehensive list of all possible
3051 * timezone strings. Such a list would be impractical. Rather, this
3052 * listing is intended to incorporate all military, North American, and
3053 * a few special cases such as Japan and the major European zone names,
3054 * such as what might be expected to be found in a Tenex format mailbox
3055 * and spewed from an IMAP server. The trend is to migrate to numeric
3056 * timezones which lack the flavor but also the ambiguity of the names.
3058 * RFC-822 only recognizes UT, GMT, 1-letter military timezones, and the
3059 * 4 CONUS timezones and their summer time variants. [Sorry, Canadian
3060 * Atlantic Provinces, Alaska, and Hawaii.]
3062 switch (ms) { /* determine the timezone */
3063 /* Universal */
3064 case (('U'-'A')*1024)+(('T'-'A')*32):
3065 #ifndef STRICT_RFC822_TIMEZONES
3066 case (('U'-'A')*1024)+(('T'-'A')*32)+'C'-'A':
3067 #endif
3068 /* Greenwich */
3069 case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A':
3070 case 'Z': elt->zhours = 0; break;
3072 /* oriental (from Greenwich) timezones */
3073 #ifndef STRICT_RFC822_TIMEZONES
3074 /* Middle Europe */
3075 case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
3076 #endif
3077 #ifdef BRITISH_SUMMER_TIME
3078 /* British Summer */
3079 case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3080 #endif
3081 case 'A': elt->zhours = 1; break;
3082 #ifndef STRICT_RFC822_TIMEZONES
3083 /* Eastern Europe */
3084 case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
3085 #endif
3086 case 'B': elt->zhours = 2; break;
3087 case 'C': elt->zhours = 3; break;
3088 case 'D': elt->zhours = 4; break;
3089 case 'E': elt->zhours = 5; break;
3090 case 'F': elt->zhours = 6; break;
3091 case 'G': elt->zhours = 7; break;
3092 case 'H': elt->zhours = 8; break;
3093 #ifndef STRICT_RFC822_TIMEZONES
3094 /* Japan */
3095 case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3096 #endif
3097 case 'I': elt->zhours = 9; break;
3098 case 'K': elt->zhours = 10; break;
3099 case 'L': elt->zhours = 11; break;
3100 case 'M': elt->zhours = 12; break;
3102 /* occidental (from Greenwich) timezones */
3103 case 'N': elt->zoccident = 1; elt->zhours = 1; break;
3104 case 'O': elt->zoccident = 1; elt->zhours = 2; break;
3105 #ifndef STRICT_RFC822_TIMEZONES
3106 case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3107 #endif
3108 case 'P': elt->zoccident = 1; elt->zhours = 3; break;
3109 #ifdef NEWFOUNDLAND_STANDARD_TIME
3110 /* Newfoundland */
3111 case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3112 elt->zoccident = 1; elt->zhours = 3; elt->zminutes = 30; break;
3113 #endif
3114 #ifndef STRICT_RFC822_TIMEZONES
3115 /* Atlantic */
3116 case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3117 #endif
3118 /* CONUS */
3119 case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3120 case 'Q': elt->zoccident = 1; elt->zhours = 4; break;
3121 /* Eastern */
3122 case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3123 case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3124 case 'R': elt->zoccident = 1; elt->zhours = 5; break;
3125 /* Central */
3126 case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3127 case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3128 case 'S': elt->zoccident = 1; elt->zhours = 6; break;
3129 /* Mountain */
3130 case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3131 case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3132 case 'T': elt->zoccident = 1; elt->zhours = 7; break;
3133 /* Pacific */
3134 case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3135 #ifndef STRICT_RFC822_TIMEZONES
3136 case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3137 #endif
3138 case 'U': elt->zoccident = 1; elt->zhours = 8; break;
3139 #ifndef STRICT_RFC822_TIMEZONES
3140 /* Yukon */
3141 case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3142 #endif
3143 case 'V': elt->zoccident = 1; elt->zhours = 9; break;
3144 #ifndef STRICT_RFC822_TIMEZONES
3145 /* Hawaii */
3146 case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3147 #endif
3148 case 'W': elt->zoccident = 1; elt->zhours = 10; break;
3149 /* Nome/Bering/Samoa */
3150 #ifdef NOME_STANDARD_TIME
3151 case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3152 #endif
3153 #ifdef BERING_STANDARD_TIME
3154 case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3155 #endif
3156 #ifdef SAMOA_STANDARD_TIME
3157 case (('S'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3158 #endif
3159 case 'X': elt->zoccident = 1; elt->zhours = 11; break;
3160 case 'Y': elt->zoccident = 1; elt->zhours = 12; break;
3162 default: /* unknown time zones treated as local */
3163 tn = time (0); /* time now... */
3164 t = localtime (&tn); /* get local minutes since midnight */
3165 mi = t->tm_hour * 60 + t->tm_min;
3166 ms = t->tm_yday; /* note Julian day */
3167 if ((t = gmtime (&tn)) != NULL) { /* minus UTC minutes since midnight */
3168 mi -= t->tm_hour * 60 + t->tm_min;
3169 /* ms can be one of:
3170 * 36x local time is December 31, UTC is January 1, offset -24 hours
3171 * 1 local time is 1 day ahead of UTC, offset +24 hours
3172 * 0 local time is same day as UTC, no offset
3173 * -1 local time is 1 day behind UTC, offset -24 hours
3174 * -36x local time is January 1, UTC is December 31, offset +24 hours
3176 if (ms -= t->tm_yday) /* correct offset if different Julian day */
3177 mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60;
3178 if (mi < 0) { /* occidental? */
3179 mi = abs (mi); /* yup, make positive number */
3180 elt->zoccident = 1; /* and note west of UTC */
3182 elt->zhours = mi / 60; /* now break into hours and minutes */
3183 elt->zminutes = mi % 60;
3185 break;
3187 return T;
3190 /* Mail n messages exist
3191 * Accepts: mail stream
3192 * number of messages
3195 void mail_exists (MAILSTREAM *stream,unsigned long nmsgs)
3197 char tmp[MAILTMPLEN];
3198 if (nmsgs > MAXMESSAGES) {
3199 sprintf (tmp,"Mailbox has more messages (%lu) exist than maximum (%lu)",
3200 nmsgs,MAXMESSAGES);
3201 mm_log (tmp,ERROR);
3202 nmsgs = MAXMESSAGES; /* cap to maximum */
3203 /* probably will crash in mail_elt() soon enough... */
3205 /* make sure cache is large enough */
3206 (*mailcache) (stream,nmsgs,CH_SIZE);
3207 stream->nmsgs = nmsgs; /* update stream status */
3208 /* notify main program of change */
3209 if (!stream->silent) MM_EXISTS (stream,nmsgs);
3213 /* Mail n messages are recent
3214 * Accepts: mail stream
3215 * number of recent messages
3218 void mail_recent (MAILSTREAM *stream,unsigned long recent)
3220 char tmp[MAILTMPLEN];
3221 if (recent <= stream->nmsgs) stream->recent = recent;
3222 else {
3223 sprintf (tmp,"Non-existent recent message(s) %lu, nmsgs=%lu",
3224 recent,stream->nmsgs);
3225 mm_log (tmp,ERROR);
3230 /* Mail message n is expunged
3231 * Accepts: mail stream
3232 * message #
3235 void mail_expunged (MAILSTREAM *stream,unsigned long msgno)
3237 char tmp[MAILTMPLEN];
3238 MESSAGECACHE *elt;
3239 if (msgno > stream->nmsgs) {
3240 sprintf (tmp,"Expunge of non-existent message %lu, nmsgs=%lu",
3241 msgno,stream->nmsgs);
3242 mm_log (tmp,ERROR);
3244 else {
3245 elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
3246 /* notify main program of change */
3247 if (!stream->silent) MM_EXPUNGED (stream,msgno);
3248 if (elt) { /* if an element is there */
3249 elt->msgno = 0; /* invalidate its message number and free */
3250 (*mailcache) (stream,msgno,CH_FREE);
3251 (*mailcache) (stream,msgno,CH_FREESORTCACHE);
3253 /* expunge the slot */
3254 (*mailcache) (stream,msgno,CH_EXPUNGE);
3255 --stream->nmsgs; /* update stream status */
3256 if (stream->msgno) { /* have stream pointers? */
3257 /* make sure the short cache is nuked */
3258 if (stream->scache) mail_gc (stream,GC_ENV | GC_TEXTS);
3259 else stream->msgno = 0; /* make sure invalidated in any case */
3264 /* Mail stream status routines */
3267 /* Mail lock stream
3268 * Accepts: mail stream
3271 void mail_lock (MAILSTREAM *stream)
3273 if (stream->lock) {
3274 char tmp[MAILTMPLEN];
3275 sprintf (tmp,"Lock when already locked, mbx=%.80s",
3276 stream->mailbox ? stream->mailbox : "???");
3277 fatal (tmp);
3279 else stream->lock = T; /* lock stream */
3283 /* Mail unlock stream
3284 * Accepts: mail stream
3287 void mail_unlock (MAILSTREAM *stream)
3289 if (!stream->lock) fatal ("Unlock when not locked");
3290 else stream->lock = NIL; /* unlock stream */
3294 /* Mail turn on debugging telemetry
3295 * Accepts: mail stream
3298 void mail_debug (MAILSTREAM *stream)
3300 stream->debug = T; /* turn on debugging telemetry */
3301 if (stream->dtb) (*stream->dtb->parameters) (ENABLE_DEBUG,stream);
3305 /* Mail turn off debugging telemetry
3306 * Accepts: mail stream
3309 void mail_nodebug (MAILSTREAM *stream)
3311 stream->debug = NIL; /* turn off debugging telemetry */
3312 if (stream->dtb) (*stream->dtb->parameters) (DISABLE_DEBUG,stream);
3316 /* Mail log to debugging telemetry
3317 * Accepts: message
3318 * flag that data is "sensitive"
3321 void mail_dlog (char *string,long flag)
3323 mm_dlog ((debugsensitive || !flag) ? string : "<suppressed>");
3326 /* Mail parse UID sequence
3327 * Accepts: mail stream
3328 * sequence to parse
3329 * Returns: T if parse successful, else NIL
3332 long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence)
3334 unsigned long i,j,k,x,y;
3335 for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
3336 while (sequence && *sequence){/* while there is something to parse */
3337 if (*sequence == '*') { /* maximum message */
3338 i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
3339 sequence++; /* skip past * */
3341 /* parse and validate message number */
3342 /* parse and validate message number */
3343 else if (!isdigit (*sequence)) {
3344 MM_LOG ("Syntax error in sequence",ERROR);
3345 return NIL;
3347 else if (!(i = strtoul (sequence,(char **) &sequence,10))) {
3348 MM_LOG ("UID may not be zero",ERROR);
3349 return NIL;
3351 switch (*sequence) { /* see what the delimiter is */
3352 case ':': /* sequence range */
3353 if (*++sequence == '*') { /* maximum message */
3354 j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
3355 sequence++; /* skip past * */
3357 /* parse end of range */
3358 else if (!(j = strtoul (sequence,(char **) &sequence,10))) {
3359 MM_LOG ("UID sequence range invalid",ERROR);
3360 return NIL;
3362 if (*sequence && *sequence++ != ',') {
3363 MM_LOG ("UID sequence range syntax error",ERROR);
3364 return NIL;
3366 if (i > j) { /* swap the range if backwards */
3367 x = i; i = j; j = x;
3369 x = mail_msgno (stream,i);/* get msgnos */
3370 y = mail_msgno (stream,j);/* for both UIDS (don't && it) */
3371 /* easy if both UIDs valid */
3372 if (x && y) while (x <= y) mail_elt (stream,x++)->sequence = T;
3373 /* start UID valid, end is not */
3374 else if (x) while ((x <= stream->nmsgs) && (mail_uid (stream,x) <= j))
3375 mail_elt (stream,x++)->sequence = T;
3376 /* end UID valid, start is not */
3377 else if (y) for (x = 1; x <= y; x++) {
3378 if (mail_uid (stream,x) >= i) mail_elt (stream,x)->sequence = T;
3380 /* neither is valid, ugh */
3381 else for (x = 1; x <= stream->nmsgs; x++)
3382 if (((k = mail_uid (stream,x)) >= i) && (k <= j))
3383 mail_elt (stream,x)->sequence = T;
3384 break;
3385 case ',': /* single message */
3386 ++sequence; /* skip the delimiter, fall into end case */
3387 case '\0': /* end of sequence, mark this message */
3388 if ((x = mail_msgno (stream,i)) != 0L) mail_elt (stream,x)->sequence = T;
3389 break;
3390 default: /* anything else is a syntax error! */
3391 MM_LOG ("UID sequence syntax error",ERROR);
3392 return NIL;
3395 return T; /* successfully parsed sequence */
3398 /* Mail see if line list matches that in cache
3399 * Accepts: candidate line list
3400 * cached line list
3401 * matching flags
3402 * Returns: T if match, NIL if no match
3405 long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags)
3407 unsigned long i;
3408 unsigned char *s,*t;
3409 STRINGLIST *m;
3410 if (!msglines) return T; /* full header is in cache */
3411 /* need full header but filtered in cache */
3412 if ((flags & FT_NOT) || !lines) return NIL;
3413 do { /* make sure all present & accounted for */
3414 for (m = msglines; m; m = m->next) if (lines->text.size == m->text.size) {
3415 for (s = lines->text.data,t = m->text.data,i = lines->text.size;
3416 i && !compare_uchar (*s,*t); s++,t++,i--);
3417 if (!i) break; /* this line matches */
3419 if (!m) return NIL; /* didn't find in the list */
3421 while ((lines = lines->next) != NULL);
3422 return T; /* all lines found */
3425 /* Mail filter text by header lines
3426 * Accepts: text to filter, with trailing null
3427 * length of text
3428 * list of lines
3429 * fetch flags
3430 * Returns: new text size, text overwritten
3433 unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines,
3434 long flags)
3436 STRINGLIST *hdrs;
3437 int notfound;
3438 unsigned long i;
3439 char c,*s,*e,*t,tmp[MAILTMPLEN];
3440 char *src = text;
3441 char *dst = src;
3442 char *end = text + len;
3443 text[len] = '\012'; /* guard against running off buffer */
3444 while (src < end) { /* process header */
3445 /* slurp header line name */
3446 for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp;
3447 (s < e) && ((c = (*s ? *s : (*s = ' '))) != ':') &&
3448 ((c > ' ') ||
3449 ((c != ' ') && (c != '\t') && (c != '\015') && (c != '\012')));
3450 *t++ = *s++);
3451 *t = '\0'; /* tie off */
3452 notfound = T; /* not found yet */
3453 if ((i = t - tmp) != 0L) /* see if found in header */
3454 for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next)
3455 if ((hdrs->text.size == i) && !compare_csizedtext (tmp,&hdrs->text))
3456 notfound = NIL;
3457 /* skip header line if not wanted */
3458 if (i && ((flags & FT_NOT) ? !notfound : notfound))
3459 while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3460 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3461 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3462 (*src++ != '\012')) ||
3463 ((src < end) && ((*src == ' ') || (*src == '\t'))));
3464 else if (src == dst) { /* copy to self */
3465 while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3466 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3467 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3468 (*src++ != '\012')) ||
3469 ((src < end) && ((*src == ' ') || (*src == '\t'))));
3470 dst = src; /* update destination */
3472 else { /* copy line and any continuation line */
3473 while ((((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3474 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3475 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3476 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3477 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012'))||
3478 ((src < end) && ((*src == ' ') || (*src == '\t'))));
3479 /* in case hit the guard LF */
3480 if (src > end) dst -= (src - end);
3483 *dst = '\0'; /* tie off destination */
3484 return dst - text;
3487 /* Local mail search message
3488 * Accepts: MAIL stream
3489 * message number
3490 * optional section specification
3491 * search program
3492 * Returns: T if found, NIL otherwise
3495 long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section,
3496 SEARCHPGM *pgm)
3498 unsigned short d;
3499 char tmp[MAILTMPLEN];
3500 MESSAGECACHE *elt = mail_elt (stream,msgno);
3501 SEARCHHEADER *hdr;
3502 SEARCHOR *or;
3503 SEARCHPGMLIST *not;
3504 unsigned long now = (unsigned long) time (0);
3505 if (pgm->msgno || pgm->uid) { /* message set searches */
3506 SEARCHSET *set;
3507 /* message sequences */
3508 if (pgm->msgno) { /* inside this message sequence set */
3509 for (set = pgm->msgno; set; set = set->next)
3510 if (set->last ? ((set->first <= set->last) ?
3511 ((msgno >= set->first) && (msgno <= set->last)) :
3512 ((msgno >= set->last) && (msgno <= set->first))) :
3513 msgno == set->first) break;
3514 if (!set) return NIL; /* not found within sequence */
3516 if (pgm->uid) { /* inside this unique identifier set */
3517 unsigned long uid = mail_uid (stream,msgno);
3518 for (set = pgm->uid; set; set = set->next)
3519 if (set->last ? ((set->first <= set->last) ?
3520 ((uid >= set->first) && (uid <= set->last)) :
3521 ((uid >= set->last) && (uid <= set->first))) :
3522 uid == set->first) break;
3523 if (!set) return NIL; /* not found within sequence */
3527 /* Fast data searches */
3528 /* need to fetch fast data? */
3529 if ((!elt->rfc822_size && (pgm->larger || pgm->smaller)) ||
3530 (!elt->year && (pgm->before || pgm->on || pgm->since ||
3531 pgm->older || pgm->younger)) ||
3532 (!elt->valid && (pgm->answered || pgm->unanswered ||
3533 pgm->deleted || pgm->undeleted ||
3534 pgm->draft || pgm->undraft ||
3535 pgm->flagged || pgm->unflagged ||
3536 pgm->recent || pgm->old ||
3537 pgm->seen || pgm->unseen ||
3538 pgm->keyword || pgm->unkeyword))) {
3539 unsigned long i;
3540 MESSAGECACHE *ielt;
3541 for (i = elt->msgno; /* find last unloaded message in range */
3542 (i < stream->nmsgs) && (ielt = mail_elt (stream,i+1)) &&
3543 ((!ielt->rfc822_size && (pgm->larger || pgm->smaller)) ||
3544 (!ielt->year && (pgm->before || pgm->on || pgm->since ||
3545 pgm->older || pgm->younger)) ||
3546 (!ielt->valid && (pgm->answered || pgm->unanswered ||
3547 pgm->deleted || pgm->undeleted ||
3548 pgm->draft || pgm->undraft ||
3549 pgm->flagged || pgm->unflagged ||
3550 pgm->recent || pgm->old ||
3551 pgm->seen || pgm->unseen ||
3552 pgm->keyword || pgm->unkeyword))); ++i);
3553 if (i == elt->msgno) sprintf (tmp,"%lu",elt->msgno);
3554 else sprintf (tmp,"%lu:%lu",elt->msgno,i);
3555 mail_fetch_fast (stream,tmp,NIL);
3557 /* size ranges */
3558 if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
3559 (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;
3560 /* message flags */
3561 if ((pgm->answered && !elt->answered) ||
3562 (pgm->unanswered && elt->answered) ||
3563 (pgm->deleted && !elt->deleted) ||
3564 (pgm->undeleted && elt->deleted) ||
3565 (pgm->draft && !elt->draft) ||
3566 (pgm->undraft && elt->draft) ||
3567 (pgm->flagged && !elt->flagged) ||
3568 (pgm->unflagged && elt->flagged) ||
3569 (pgm->recent && !elt->recent) ||
3570 (pgm->old && elt->recent) ||
3571 (pgm->seen && !elt->seen) ||
3572 (pgm->unseen && elt->seen)) return NIL;
3573 /* keywords */
3574 if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
3575 (pgm->unkeyword && !mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
3576 return NIL;
3577 /* internal date ranges */
3578 if (pgm->before || pgm->on || pgm->since) {
3579 d = mail_shortdate (elt->year,elt->month,elt->day);
3580 if (pgm->before && (d >= pgm->before)) return NIL;
3581 if (pgm->on && (d != pgm->on)) return NIL;
3582 if (pgm->since && (d < pgm->since)) return NIL;
3584 if (pgm->older || pgm->younger) {
3585 unsigned long msgd = mail_longdate (elt);
3586 if (pgm->older && msgd > (now - pgm->older)) return NIL;
3587 if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
3590 /* envelope searches */
3591 if (pgm->sentbefore || pgm->senton || pgm->sentsince ||
3592 pgm->bcc || pgm->cc || pgm->from || pgm->to || pgm->subject ||
3593 pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to ||
3594 pgm->message_id || pgm->newsgroups || pgm->followup_to ||
3595 pgm->references) {
3596 ENVELOPE *env;
3597 MESSAGECACHE delt;
3598 if (section) { /* use body part envelope */
3599 BODY *body = mail_body (stream,msgno,section);
3600 env = (body && (body->type == TYPEMESSAGE) && body->subtype &&
3601 !strcmp (body->subtype,"RFC822")) ? body->nested.msg->env : NIL;
3603 else { /* use top level envelope if no section */
3604 if (pgm->header && !stream->scache && !(stream->dtb->flags & DR_LOCAL))
3605 mail_fetch_header(stream,msgno,NIL,NIL,NIL,FT_PEEK|FT_SEARCHLOOKAHEAD);
3606 env = mail_fetchenvelope (stream,msgno);
3608 if (!env) return NIL; /* no envelope obtained */
3609 /* sent date ranges */
3610 if ((pgm->sentbefore || pgm->senton || pgm->sentsince) &&
3611 (!mail_parse_date (&delt,env->date) ||
3612 !(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
3613 (pgm->sentbefore && (d >= pgm->sentbefore)) ||
3614 (pgm->senton && (d != pgm->senton)) ||
3615 (pgm->sentsince && (d < pgm->sentsince)))) return NIL;
3616 /* search headers */
3617 if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
3618 (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
3619 (pgm->from && !mail_search_addr (env->from,pgm->from)) ||
3620 (pgm->to && !mail_search_addr (env->to,pgm->to)) ||
3621 (pgm->subject && !mail_search_header_text (env->subject,pgm->subject)))
3622 return NIL;
3623 /* These criteria are not supported by IMAP and have to be emulated */
3624 if ((pgm->return_path &&
3625 !mail_search_addr (env->return_path,pgm->return_path)) ||
3626 (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
3627 (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
3628 (pgm->in_reply_to &&
3629 !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
3630 (pgm->message_id &&
3631 !mail_search_header_text (env->message_id,pgm->message_id)) ||
3632 (pgm->newsgroups &&
3633 !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
3634 (pgm->followup_to &&
3635 !mail_search_header_text (env->followup_to,pgm->followup_to)) ||
3636 (pgm->references &&
3637 !mail_search_header_text (env->references,pgm->references)))
3638 return NIL;
3641 /* search header lines */
3642 for (hdr = pgm->header; hdr; hdr = hdr->next) {
3643 char *t,*e,*v;
3644 SIZEDTEXT s;
3645 STRINGLIST sth,stc;
3646 sth.next = stc.next = NIL; /* only one at a time */
3647 sth.text.data = hdr->line.data;
3648 sth.text.size = hdr->line.size;
3649 /* get the header text */
3650 if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
3651 FT_INTERNAL | FT_PEEK |
3652 (section ? NIL : FT_SEARCHLOOKAHEAD))) &&
3653 strchr (t,':')) {
3654 if (hdr->text.size) { /* anything matches empty search string */
3655 /* non-empty, copy field data */
3656 s.data = (unsigned char *) fs_get (s.size + 1);
3657 /* for each line */
3658 for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
3659 default: /* non-continuation, skip leading field name */
3660 while ((t < e) && (*t++ != ':'));
3661 if ((t < e) && (*t == ':')) t++;
3662 case '\t': case ' ': /* copy field data */
3663 while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
3664 *v++ = '\n'; /* tie off line */
3665 while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
3667 /* calculate true size */
3668 s.size = v - (char *) s.data;
3669 *v = '\0'; /* tie off results */
3670 stc.text.data = hdr->text.data;
3671 stc.text.size = hdr->text.size;
3672 /* search header */
3673 if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
3674 else { /* search failed */
3675 fs_give ((void **) &s.data);
3676 return NIL;
3680 else return NIL; /* no matching header text */
3682 /* search strings */
3683 if ((pgm->text && !mail_search_text (stream,msgno,section,pgm->text,LONGT))||
3684 (pgm->body && !mail_search_text (stream,msgno,section,pgm->body,NIL)))
3685 return NIL;
3686 /* logical conditions */
3687 for (or = pgm->or; or; or = or->next)
3688 if (!(mail_search_msg (stream,msgno,section,or->first) ||
3689 mail_search_msg (stream,msgno,section,or->second))) return NIL;
3690 for (not = pgm->not; not; not = not->next)
3691 if (mail_search_msg (stream,msgno,section,not->pgm)) return NIL;
3692 return T;
3695 /* Mail search message header null-terminated text
3696 * Accepts: header text
3697 * strings to search
3698 * Returns: T if search found a match
3701 long mail_search_header_text (char *s,STRINGLIST *st)
3703 SIZEDTEXT h;
3704 /* have any text? */
3705 if ((h.data = (unsigned char *) s) != NULL) {
3706 h.size = strlen (s); /* yes, get its size */
3707 return mail_search_header (&h,st);
3709 return NIL;
3713 /* Mail search message header
3714 * Accepts: header as sized text
3715 * strings to search
3716 * Returns: T if search found a match
3719 long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st)
3721 SIZEDTEXT h;
3722 long ret = LONGT;
3723 /* make UTF-8 version of header */
3724 utf8_mime2text (hdr,&h,U8T_CANONICAL);
3725 while (h.size && ((h.data[h.size-1]=='\015') || (h.data[h.size-1]=='\012')))
3726 --h.size; /* slice off trailing newlines */
3727 do if (h.size ? /* search non-empty string */
3728 !ssearch (h.data,h.size,st->text.data,st->text.size) : st->text.size)
3729 ret = NIL;
3730 while (ret && (st = st->next));
3731 if (h.data != hdr->data) fs_give ((void **) &h.data);
3732 return ret;
3735 /* Mail search message body
3736 * Accepts: MAIL stream
3737 * message number
3738 * optional section specification
3739 * string list
3740 * flags
3741 * Returns: T if search found a match
3744 long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section,
3745 STRINGLIST *st,long flags)
3747 BODY *body;
3748 long ret = NIL;
3749 STRINGLIST *s = mail_newstringlist ();
3750 mailgets_t omg = mailgets;
3751 if (stream->dtb->flags & DR_LOWMEM) mailgets = mail_search_gets;
3752 /* strings to search */
3753 for (stream->private.search.string = s; st;) {
3754 s->text.data = st->text.data;
3755 s->text.size = st->text.size;
3756 if ((st = st->next) != NULL) s = s->next = mail_newstringlist ();
3758 stream->private.search.text = NIL;
3759 if (flags) { /* want header? */
3760 SIZEDTEXT s,t;
3761 s.data = (unsigned char *)
3762 mail_fetch_header (stream,msgno,section,NIL,&s.size,FT_INTERNAL|FT_PEEK);
3763 utf8_mime2text (&s,&t,U8T_CANONICAL);
3764 ret = mail_search_string_work (&t,&stream->private.search.string);
3765 if (t.data != s.data) fs_give ((void **) &t.data);
3767 if (!ret) { /* still looking for match? */
3768 /* no section, get top-level body */
3769 if (!section) mail_fetchstructure (stream,msgno,&body);
3770 /* get body of nested message */
3771 else if ((body = mail_body (stream,msgno,section)) &&
3772 (body->type == TYPEMULTIPART) && body->subtype &&
3773 !strcmp (body->subtype,"RFC822")) body = body->nested.msg->body;
3774 if (body) ret = mail_search_body (stream,msgno,body,NIL,1,flags);
3776 mailgets = omg; /* restore former gets routine */
3777 /* clear searching */
3778 for (s = stream->private.search.string; s; s = s->next) s->text.data = NIL;
3779 mail_free_stringlist (&stream->private.search.string);
3780 stream->private.search.text = NIL;
3781 return ret;
3784 /* Mail search message body text parts
3785 * Accepts: MAIL stream
3786 * message number
3787 * current body pointer
3788 * hierarchical level prefix
3789 * position at current hierarchical level
3790 * string list
3791 * flags
3792 * Returns: T if search found a match
3795 long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body,
3796 char *prefix,unsigned long section,long flags)
3798 long ret = NIL;
3799 unsigned long i;
3800 char *s,*t,sect[MAILTMPLEN];
3801 SIZEDTEXT st,h;
3802 PART *part;
3803 PARAMETER *param;
3804 if (prefix && (strlen (prefix) > (MAILTMPLEN - 20))) return NIL;
3805 sprintf (sect,"%s%lu",prefix ? prefix : "",section++);
3806 if (flags && prefix) { /* want to search MIME header too? */
3807 st.data = (unsigned char *) mail_fetch_mime (stream,msgno,sect,&st.size,
3808 FT_INTERNAL | FT_PEEK);
3809 if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
3810 else {
3811 /* make UTF-8 version of header */
3812 utf8_mime2text (&st,&h,U8T_CANONICAL);
3813 ret = mail_search_string_work (&h,&stream->private.search.string);
3814 if (h.data != st.data) fs_give ((void **) &h.data);
3817 if (!ret) switch (body->type) {
3818 case TYPEMULTIPART:
3819 /* extend prefix if not first time */
3820 s = prefix ? strcat (sect,".") : "";
3821 for (i = 1,part = body->nested.part; part && !ret; i++,part = part->next)
3822 ret = mail_search_body (stream,msgno,&part->body,s,i,flags);
3823 break;
3824 case TYPEMESSAGE:
3825 if (!strcmp (body->subtype,"RFC822")) {
3826 if (flags) { /* want to search nested message header? */
3827 st.data = (unsigned char *)
3828 mail_fetch_header (stream,msgno,sect,NIL,&st.size,
3829 FT_INTERNAL | FT_PEEK);
3830 if (stream->dtb->flags & DR_LOWMEM) ret =stream->private.search.result;
3831 else {
3832 /* make UTF-8 version of header */
3833 utf8_mime2text (&st,&h,U8T_CANONICAL);
3834 ret = mail_search_string_work (&h,&stream->private.search.string);
3835 if (h.data != st.data) fs_give ((void **) &h.data);
3838 if ((body = body->nested.msg->body) != NULL)
3839 ret = (body->type == TYPEMULTIPART) ?
3840 mail_search_body (stream,msgno,body,(prefix ? prefix : ""),
3841 section - 1,flags) :
3842 mail_search_body (stream,msgno,body,strcat (sect,"."),1,flags);
3843 break;
3845 /* non-MESSAGE/RFC822 falls into text case */
3847 case TYPETEXT:
3848 s = mail_fetch_body (stream,msgno,sect,&i,FT_INTERNAL | FT_PEEK);
3849 if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
3850 else {
3851 for (t = NIL,param = body->parameter; param && !t; param = param->next)
3852 if (!strcmp (param->attribute,"CHARSET")) t = param->value;
3853 switch (body->encoding) { /* what encoding? */
3854 case ENCBASE64:
3855 if ((st.data = (unsigned char *)
3856 rfc822_base64 ((unsigned char *) s,i,&st.size)) != NULL) {
3857 ret = mail_search_string (&st,t,&stream->private.search.string);
3858 fs_give ((void **) &st.data);
3860 break;
3861 case ENCQUOTEDPRINTABLE:
3862 if ((st.data = rfc822_qprint ((unsigned char *) s,i,&st.size)) != NULL) {
3863 ret = mail_search_string (&st,t,&stream->private.search.string);
3864 fs_give ((void **) &st.data);
3866 break;
3867 default:
3868 st.data = (unsigned char *) s;
3869 st.size = i;
3870 ret = mail_search_string (&st,t,&stream->private.search.string);
3871 break;
3874 break;
3876 return ret;
3879 /* Mail search text
3880 * Accepts: sized text to search
3881 * character set of sized text
3882 * string list of search keys
3883 * Returns: T if search found a match
3886 long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st)
3888 SIZEDTEXT u;
3889 long ret;
3890 STRINGLIST **sc = st;
3891 /* convert to UTF-8 as best we can */
3892 if (!utf8_text (s,charset,&u,U8T_CANONICAL))
3893 utf8_text (s,NIL,&u,U8T_CANONICAL);
3894 ret = mail_search_string_work (&u,st);
3895 if (u.data != s->data) fs_give ((void **) &u.data);
3896 return ret;
3900 /* Mail search text worker routine
3901 * Accepts: sized text to search
3902 * string list of search keys
3903 * Returns: T if search found a match
3906 long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st)
3908 void *t;
3909 STRINGLIST **sc = st;
3910 while (*sc) { /* run down criteria list */
3911 if (ssearch (s->data,s->size,(*sc)->text.data,(*sc)->text.size)) {
3912 t = (void *) (*sc); /* found one, need to flush this */
3913 *sc = (*sc)->next; /* remove it from the list */
3914 fs_give (&t); /* flush the buffer */
3916 else sc = &(*sc)->next; /* move to next in list */
3918 return *st ? NIL : LONGT;
3922 /* Mail search keyword
3923 * Accepts: MAIL stream
3924 * elt to get flags from
3925 * keyword list
3926 * T for keyword search, NIL for unkeyword search
3927 * Returns: T if search found a match
3930 long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st,
3931 long flag)
3933 int i,j;
3934 unsigned long f = 0;
3935 unsigned long tf;
3936 do {
3937 for (i = 0; (j = (i < NUSERFLAGS) && stream->user_flags[i]); ++i)
3938 if (!compare_csizedtext (stream->user_flags[i],&st->text)) {
3939 f |= (1 << i);
3940 break;
3942 if (flag && !j) return NIL;
3943 } while ((st = st->next) != NULL);
3944 tf = elt->user_flags & f; /* get set flags which match */
3945 return flag ? (f == tf) : !tf;
3948 /* Mail search an address list
3949 * Accepts: address list
3950 * string list
3951 * Returns: T if search found a match
3954 #define SEARCHBUFLEN (size_t) 2000
3955 #define SEARCHBUFSLOP (size_t) 5
3957 long mail_search_addr (ADDRESS *adr,STRINGLIST *st)
3959 ADDRESS *a,tadr;
3960 SIZEDTEXT txt;
3961 char tmp[SENDBUFLEN + 1];
3962 size_t i = SEARCHBUFLEN;
3963 size_t k;
3964 long ret = NIL;
3965 if (adr) {
3966 txt.data = (unsigned char *) fs_get (i + SEARCHBUFSLOP);
3967 /* never an error or next */
3968 tadr.error = NIL,tadr.next = NIL;
3969 /* write address list */
3970 for (txt.size = 0,a = adr; a; a = a->next) {
3971 k = (tadr.mailbox = a->mailbox) ? 4 + 2*strlen (a->mailbox) : 3;
3972 if ((tadr.personal = a->personal) != NULL) k += 3 + 2*strlen (a->personal);
3973 if ((tadr.adl = a->adl) != NULL) k += 3 + 2*strlen (a->adl);
3974 if ((tadr.host = a->host) != NULL) k += 3 + 2*strlen (a->host);
3975 if (tadr.personal || tadr.adl) k += 2;
3976 if (k < (SENDBUFLEN-10)) {/* ignore ridiculous addresses */
3977 tmp[0] = '\0';
3978 rfc822_write_address (tmp,&tadr);
3979 /* resize buffer if necessary */
3980 if (((k = strlen (tmp)) + txt.size) > i)
3981 fs_resize ((void **) &txt.data,SEARCHBUFSLOP + (i += SEARCHBUFLEN));
3982 /* add new address */
3983 memcpy (txt.data + txt.size,tmp,k);
3984 txt.size += k;
3985 /* another address follows */
3986 if (a->next) txt.data[txt.size++] = ',';
3989 txt.data[txt.size] = '\0'; /* tie off string */
3990 ret = mail_search_header (&txt,st);
3991 fs_give ((void **) &txt.data);
3993 return ret;
3996 /* Get string for low-memory searching
3997 * Accepts: readin function pointer
3998 * stream to use
3999 * number of bytes
4000 * gets data packet
4002 * mail stream
4003 * message number
4004 * descriptor string
4005 * option flags
4006 * Returns: NIL, always
4009 #define SEARCHSLOP 128
4011 char *mail_search_gets (readfn_t f,void *stream,unsigned long size,
4012 GETS_DATA *md)
4014 unsigned long i;
4015 char tmp[MAILTMPLEN+SEARCHSLOP+1];
4016 SIZEDTEXT st;
4017 /* better not be called unless searching */
4018 if (!md->stream->private.search.string) {
4019 sprintf (tmp,"Search botch, mbx = %.80s, %s = %lu[%.80s]",
4020 md->stream->mailbox,
4021 (md->flags & FT_UID) ? "UID" : "msg",md->msgno,md->what);
4022 fatal (tmp);
4024 /* initially no match for search */
4025 md->stream->private.search.result = NIL;
4026 /* make sure buffer clear */
4027 memset (st.data = (unsigned char *) tmp,'\0',
4028 (size_t) MAILTMPLEN+SEARCHSLOP+1);
4029 /* read first buffer */
4030 (*f) (stream,st.size = i = min (size,(long) MAILTMPLEN),tmp);
4031 /* search for text */
4032 if (mail_search_string (&st,NIL,&md->stream->private.search.string))
4033 md->stream->private.search.result = T;
4034 else if (size -= i) { /* more to do, blat slop down */
4035 memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP);
4036 do { /* read subsequent buffers one at a time */
4037 (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP);
4038 st.size = i + SEARCHSLOP;
4039 if (mail_search_string (&st,NIL,&md->stream->private.search.string))
4040 md->stream->private.search.result = T;
4041 else memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP);
4043 while ((size -= i) && !md->stream->private.search.result);
4045 if (size) { /* toss out everything after that */
4046 do (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp);
4047 while (size -= i);
4049 return NIL;
4052 /* Mail parse search criteria
4053 * Accepts: criteria
4054 * Returns: search program if parse successful, else NIL
4057 SEARCHPGM *mail_criteria (char *criteria)
4059 SEARCHPGM *pgm = NIL;
4060 char *criterion,*r,tmp[MAILTMPLEN];
4061 int f;
4062 if (criteria) { /* only if criteria defined */
4063 /* make writeable copy of criteria */
4064 criteria = cpystr (criteria);
4065 /* for each criterion */
4066 for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
4067 criterion; (criterion = strtok_r (NIL," ",&r))) {
4068 f = NIL; /* init then scan the criterion */
4069 switch (*ucase (criterion)) {
4070 case 'A': /* possible ALL, ANSWERED */
4071 if (!strcmp (criterion+1,"LL")) f = T;
4072 else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
4073 break;
4074 case 'B': /* possible BCC, BEFORE, BODY */
4075 if (!strcmp (criterion+1,"CC"))
4076 f = mail_criteria_string (&pgm->bcc,&r);
4077 else if (!strcmp (criterion+1,"EFORE"))
4078 f = mail_criteria_date (&pgm->before,&r);
4079 else if (!strcmp (criterion+1,"ODY"))
4080 f = mail_criteria_string (&pgm->body,&r);
4081 break;
4082 case 'C': /* possible CC */
4083 if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
4084 break;
4085 case 'D': /* possible DELETED */
4086 if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
4087 break;
4088 case 'F': /* possible FLAGGED, FROM */
4089 if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
4090 else if (!strcmp (criterion+1,"ROM"))
4091 f = mail_criteria_string (&pgm->from,&r);
4092 break;
4093 case 'K': /* possible KEYWORD */
4094 if (!strcmp (criterion+1,"EYWORD"))
4095 f = mail_criteria_string (&pgm->keyword,&r);
4096 break;
4098 case 'N': /* possible NEW */
4099 if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
4100 break;
4101 case 'O': /* possible OLD, ON */
4102 if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
4103 else if (!strcmp (criterion+1,"N"))
4104 f = mail_criteria_date (&pgm->on,&r);
4105 break;
4106 case 'R': /* possible RECENT */
4107 if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
4108 break;
4109 case 'S': /* possible SEEN, SINCE, SUBJECT */
4110 if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
4111 else if (!strcmp (criterion+1,"INCE"))
4112 f = mail_criteria_date (&pgm->since,&r);
4113 else if (!strcmp (criterion+1,"UBJECT"))
4114 f = mail_criteria_string (&pgm->subject,&r);
4115 break;
4116 case 'T': /* possible TEXT, TO */
4117 if (!strcmp (criterion+1,"EXT"))
4118 f = mail_criteria_string (&pgm->text,&r);
4119 else if (!strcmp (criterion+1,"O"))
4120 f = mail_criteria_string (&pgm->to,&r);
4121 break;
4122 case 'U': /* possible UN* */
4123 if (criterion[1] == 'N') {
4124 if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
4125 else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
4126 else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
4127 else if (!strcmp (criterion+2,"KEYWORD"))
4128 f = mail_criteria_string (&pgm->unkeyword,&r);
4129 else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
4131 break;
4132 default: /* we will barf below */
4133 break;
4135 if (!f) { /* if can't identify criterion */
4136 sprintf (tmp,"Unknown search criterion: %.30s",criterion);
4137 MM_LOG (tmp,ERROR);
4138 mail_free_searchpgm (&pgm);
4139 break;
4142 /* no longer need copy of criteria */
4143 fs_give ((void **) &criteria);
4145 return pgm;
4148 /* Parse a date
4149 * Accepts: pointer to date integer to return
4150 * pointer to strtok state
4151 * Returns: T if successful, else NIL
4154 int mail_criteria_date (unsigned short *date,char **r)
4156 STRINGLIST *s = NIL;
4157 MESSAGECACHE elt;
4158 /* parse the date and return fn if OK */
4159 int ret = (mail_criteria_string (&s,r) &&
4160 mail_parse_date (&elt,(char *) s->text.data) &&
4161 (*date = mail_shortdate (elt.year,elt.month,elt.day))) ?
4162 T : NIL;
4163 if (s) mail_free_stringlist (&s);
4164 return ret;
4167 /* Calculate shortdate from elt values
4168 * Accepts: year (0 = BASEYEAR)
4169 * month (1 = January)
4170 * day
4171 * Returns: shortdate
4174 unsigned short mail_shortdate (unsigned int year,unsigned int month,
4175 unsigned int day)
4177 return (year << 9) + (month << 5) + day;
4180 /* Parse a string
4181 * Accepts: pointer to stringlist
4182 * pointer to strtok state
4183 * Returns: T if successful, else NIL
4186 int mail_criteria_string (STRINGLIST **s,char **r)
4188 unsigned long n;
4189 char e,*d,*end = " ",*c = strtok_r (NIL,"",r);
4190 if (!c) return NIL; /* missing argument */
4191 switch (*c) { /* see what the argument is */
4192 case '{': /* literal string */
4193 n = strtoul (c+1,&d,10); /* get its length */
4194 if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') &&
4195 (!(*(c = d + n)) || (*c == ' '))) {
4196 e = *--c; /* store old delimiter */
4197 *c = '\377'; /* make sure not a space */
4198 strtok_r (c," ",r); /* reset the strtok mechanism */
4199 *c = e; /* put character back */
4200 break;
4202 case '\0': /* catch bogons */
4203 case ' ':
4204 return NIL;
4205 case '"': /* quoted string */
4206 if (strchr (c+1,'"')) end = "\"";
4207 else return NIL; /* falls through */
4208 default: /* atomic string */
4209 if ((d = strtok_r (c,end,r)) != NULL) n = strlen (d);
4210 else return NIL;
4211 break;
4213 while (*s) s = &(*s)->next; /* find tail of list */
4214 *s = mail_newstringlist (); /* make new entry */
4215 /* return the data */
4216 (*s)->text.data = (unsigned char *) cpystr (d);
4217 (*s)->text.size = n;
4218 return T;
4221 /* Mail parse set from string
4222 * Accepts: string to parse
4223 * pointer to updated string pointer for return
4224 * Returns: set with pointer updated, or NIL if error
4227 SEARCHSET *mail_parse_set (char *s,char **ret)
4229 SEARCHSET *cur;
4230 SEARCHSET *set = NIL;
4231 while (isdigit (*s)) {
4232 if (!set) cur = set = mail_newsearchset ();
4233 else cur = cur->next = mail_newsearchset ();
4234 /* parse value */
4235 if (!(cur->first = strtoul (s,&s,10)) ||
4236 ((*s == ':') && !(isdigit (*++s) && (cur->last = strtoul (s,&s,10)))))
4237 break; /* bad value or range */
4238 if (*s == ',') ++s; /* point to next value if more */
4239 else { /* end of set */
4240 *ret = s; /* set return pointer */
4241 return set; /* return set */
4244 mail_free_searchset (&set); /* failure, punt partial set */
4245 return NIL;
4249 /* Mail append to set
4250 * Accepts: head of search set or NIL to do nothing
4251 * message to add
4252 * Returns: tail of search set or NIL if did nothing
4255 SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno)
4257 if (set) { /* find tail */
4258 while (set->next) set = set->next;
4259 /* start of set if no first member */
4260 if (!set->first) set->first = msgno;
4261 else if (msgno == (set->last ? set->last : set->first) + 1)
4262 set->last = msgno; /* extend range if 1 past current */
4263 else (set = set->next = mail_newsearchset ())->first = msgno;
4265 return set;
4268 /* Mail sort messages
4269 * Accepts: mail stream
4270 * character set
4271 * search program
4272 * sort program
4273 * option flags
4274 * Returns: vector of sorted message sequences or NIL if error
4277 unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
4278 SORTPGM *pgm,long flags)
4280 unsigned long *ret = NIL;
4281 if (stream->dtb) /* do the driver's action */
4282 ret = (*(stream->dtb->sort ? stream->dtb->sort : mail_sort_msgs))
4283 (stream,charset,spg,pgm,flags);
4284 /* flush search/sort programs if requested */
4285 if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
4286 if (flags & SO_FREE) mail_free_sortpgm (&pgm);
4287 return ret;
4290 /* Mail sort messages work routine
4291 * Accepts: mail stream
4292 * character set
4293 * search program
4294 * sort program
4295 * option flags
4296 * Returns: vector of sorted message sequences or NIL if error
4299 unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
4300 SORTPGM *pgm,long flags)
4302 unsigned long i;
4303 SORTCACHE **sc;
4304 unsigned long *ret = NIL;
4305 if (spg) { /* only if a search needs to be done */
4306 int silent = stream->silent;
4307 stream->silent = T; /* don't pass up mm_searched() events */
4308 /* search for messages */
4309 mail_search_full (stream,charset,spg,NIL);
4310 stream->silent = silent; /* restore silence state */
4312 /* initialize progress counters */
4313 pgm->nmsgs = pgm->progress.cached = 0;
4314 /* pass 1: count messages to sort */
4315 for (i = 1; i <= stream->nmsgs; ++i)
4316 if (mail_elt (stream,i)->searched) pgm->nmsgs++;
4317 if (pgm->nmsgs) { /* pass 2: sort cache */
4318 sc = mail_sort_loadcache (stream,pgm);
4319 /* pass 3: sort messages */
4320 if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
4321 fs_give ((void **) &sc); /* don't need sort vector any more */
4323 /* empty sort results */
4324 else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
4325 sizeof (unsigned long));
4326 /* also return via callback if requested */
4327 if (mailsortresults) (*mailsortresults) (stream,ret,pgm->nmsgs);
4328 return ret; /* return sort results */
4331 /* Mail sort sortcache vector
4332 * Accepts: mail stream
4333 * sort program
4334 * sortcache vector
4335 * option flags
4336 * Returns: vector of sorted message sequences or NIL if error
4339 unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc,
4340 long flags)
4342 unsigned long i,*ret;
4343 /* pass 3: sort messages */
4344 qsort ((void *) sc,pgm->nmsgs,sizeof (SORTCACHE *),mail_sort_compare);
4345 /* optional post sorting */
4346 if (pgm->postsort) (*pgm->postsort) ((void *) sc);
4347 /* pass 4: return results */
4348 ret = (unsigned long *) fs_get ((pgm->nmsgs+1) * sizeof (unsigned long));
4349 if (flags & SE_UID) /* UID or msgno? */
4350 for (i = 0; i < pgm->nmsgs; i++) ret[i] = mail_uid (stream,sc[i]->num);
4351 else for (i = 0; i < pgm->nmsgs; i++) ret[i] = sc[i]->num;
4352 ret[pgm->nmsgs] = 0; /* tie off message list */
4353 return ret;
4356 /* Mail load sortcache
4357 * Accepts: mail stream, already searched
4358 * sort program
4359 * Returns: vector of sortcache pointers matching search
4362 static STRINGLIST maildateline = {{(unsigned char *) "date",4},NIL};
4363 static STRINGLIST mailrnfromline = {{(unsigned char *) ">from",5},NIL};
4364 static STRINGLIST mailfromline = {{(unsigned char *) "from",4},
4365 &mailrnfromline};
4366 static STRINGLIST mailtonline = {{(unsigned char *) "to",2},NIL};
4367 static STRINGLIST mailccline = {{(unsigned char *) "cc",2},NIL};
4368 static STRINGLIST mailsubline = {{(unsigned char *) "subject",7},NIL};
4370 SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm)
4372 char *t,*v,*x,tmp[MAILTMPLEN];
4373 SORTPGM *pg;
4374 SORTCACHE *s,**sc;
4375 MESSAGECACHE *elt,telt;
4376 ENVELOPE *env;
4377 ADDRESS *adr = NIL;
4378 unsigned long i = (pgm->nmsgs) * sizeof (SORTCACHE *);
4379 sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
4380 /* see what needs to be loaded */
4381 for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
4382 if ((elt = mail_elt (stream,i))->searched) {
4383 sc[pgm->progress.cached++] =
4384 s = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
4385 s->pgm = pgm; /* note sort program */
4386 s->num = i;
4387 /* get envelope if cached */
4388 if (stream->scache) env = (i == stream->msgno) ? stream->env : NIL;
4389 else env = elt->private.msg.env;
4390 for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
4391 case SORTARRIVAL: /* sort by arrival date */
4392 if (!s->arrival) {
4393 /* internal date unknown but can get? */
4394 if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
4395 sprintf (tmp,"%lu",i);
4396 mail_fetch_fast (stream,tmp,NIL);
4398 /* wrong thing before 3-Jan-1970 */
4399 s->arrival = elt->day ? mail_longdate (elt) : 1;
4400 s->dirty = T;
4402 break;
4403 case SORTSIZE: /* sort by message size */
4404 if (!s->size) {
4405 if (!elt->rfc822_size) {
4406 sprintf (tmp,"%lu",i);
4407 mail_fetch_fast (stream,tmp,NIL);
4409 s->size = elt->rfc822_size ? elt->rfc822_size : 1;
4410 s->dirty = T;
4412 break;
4414 case SORTDATE: /* sort by date */
4415 if (!s->date) {
4416 if (env) t = env->date;
4417 else if ((t = mail_fetch_header (stream,i,NIL,&maildateline,NIL,
4418 FT_INTERNAL | FT_PEEK)) &&
4419 (t = strchr (t,':')))
4420 for (x = ++t; (x = strpbrk (x,"\012\015")) != NULL; x++)
4421 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4422 case ' ': /* erase continuation newlines */
4423 case '\t':
4424 memmove (x,v,strlen (v));
4425 break;
4426 default: /* tie off extraneous text */
4427 *x = x[1] = '\0';
4429 /* skip leading whitespace */
4430 if (t) while ((*t == ' ') || (*t == '\t')) t++;
4431 /* parse date from Date: header */
4432 if (!(t && mail_parse_date (&telt,t) &&
4433 (s->date = mail_longdate (&telt)))) {
4434 /* failed, use internal date */
4435 if (!(s->date = s->arrival)) {
4436 /* internal date unknown but can get? */
4437 if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
4438 sprintf (tmp,"%lu",i);
4439 mail_fetch_fast (stream,tmp,NIL);
4441 /* wrong thing before 3-Jan-1970 */
4442 s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
4445 s->dirty = T;
4447 break;
4449 case SORTFROM: /* sort by first from */
4450 if (!s->from) {
4451 if (env) s->from = env->from && env->from->mailbox ?
4452 cpystr (env->from->mailbox) : NIL;
4453 else if ((t = mail_fetch_header (stream,i,NIL,&mailfromline,NIL,
4454 FT_INTERNAL | FT_PEEK)) &&
4455 (t = strchr (t,':'))) {
4456 for (x = ++t; (x = strpbrk (x,"\012\015")) != NULL; x++)
4457 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4458 case ' ': /* erase continuation newlines */
4459 case '\t':
4460 memmove (x,v,strlen (v));
4461 break;
4462 case 'f': /* continuation but with extra "From:" */
4463 case 'F':
4464 if ((v = strchr (v,':')) != NULL) {
4465 memmove (x,v+1,strlen (v+1));
4466 break;
4468 default: /* tie off extraneous text */
4469 *x = x[1] = '\0';
4471 rfc822_parse_adrlist (&adr,t,BADHOST);
4472 if (adr) {
4473 s->from = adr->mailbox;
4474 adr->mailbox = NIL;
4475 mail_free_address (&adr);
4478 if (!s->from) s->from = cpystr ("");
4479 s->dirty = T;
4481 break;
4483 case SORTTO: /* sort by first to */
4484 if (!s->to) {
4485 if (env) s->to = env->to && env->to->mailbox ?
4486 cpystr (env->to->mailbox) : NIL;
4487 else if ((t = mail_fetch_header (stream,i,NIL,&mailtonline,NIL,
4488 FT_INTERNAL | FT_PEEK)) &&
4489 (t = strchr (t,':'))) {
4490 for (x = ++t; (x = strpbrk (x,"\012\015")) != NULL; x++)
4491 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4492 case ' ': /* erase continuation newlines */
4493 case '\t':
4494 memmove (x,v,strlen (v));
4495 break;
4496 case 't': /* continuation but with extra "To:" */
4497 case 'T':
4498 if ((v = strchr (v,':')) != NULL) {
4499 memmove (x,v+1,strlen (v+1));
4500 break;
4502 default: /* tie off extraneous text */
4503 *x = x[1] = '\0';
4505 rfc822_parse_adrlist (&adr,t,BADHOST);
4506 if (adr) {
4507 s->to = adr->mailbox;
4508 adr->mailbox = NIL;
4509 mail_free_address (&adr);
4512 if (!s->to) s->to = cpystr ("");
4513 s->dirty = T;
4515 break;
4517 case SORTCC: /* sort by first cc */
4518 if (!s->cc) {
4519 if (env) s->cc = env->cc && env->cc->mailbox ?
4520 cpystr (env->cc->mailbox) : NIL;
4521 else if ((t = mail_fetch_header (stream,i,NIL,&mailccline,NIL,
4522 FT_INTERNAL | FT_PEEK)) &&
4523 (t = strchr (t,':'))) {
4524 for (x = ++t; (x = strpbrk (x,"\012\015")) != NULL; x++)
4525 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4526 case ' ': /* erase continuation newlines */
4527 case '\t':
4528 memmove (x,v,strlen (v));
4529 break;
4530 case 'c': /* continuation but with extra "cc:" */
4531 case 'C':
4532 if ((v = strchr (v,':')) != NULL) {
4533 memmove (x,v+1,strlen (v+1));
4534 break;
4536 default: /* tie off extraneous text */
4537 *x = x[1] = '\0';
4539 rfc822_parse_adrlist (&adr,t,BADHOST);
4540 if (adr) {
4541 s->cc = adr->mailbox;
4542 adr->mailbox = NIL;
4543 mail_free_address (&adr);
4546 if (!s->cc) s->cc = cpystr ("");
4547 s->dirty = T;
4549 break;
4551 case SORTSUBJECT: /* sort by subject */
4552 if (!s->subject) {
4553 /* get subject from envelope if have one */
4554 if (env) t = env->subject ? env->subject : "";
4555 /* otherwise snarf from header text */
4556 else if ((t = mail_fetch_header (stream,i,NIL,&mailsubline,
4557 NIL,FT_INTERNAL | FT_PEEK)) &&
4558 (t = strchr (t,':')))
4559 for (x = ++t; (x = strpbrk (x,"\012\015")) != NULL; x++)
4560 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4561 case ' ': /* erase continuation newlines */
4562 case '\t':
4563 memmove (x,v,strlen (v));
4564 break;
4565 default: /* tie off extraneous text */
4566 *x = x[1] = '\0';
4568 else t = ""; /* empty subject */
4569 /* strip and cache subject */
4570 s->refwd = mail_strip_subject (t,&s->subject);
4571 s->dirty = T;
4573 break;
4574 default:
4575 fatal ("Unknown sort function");
4578 return sc;
4581 /* Strip subjects of extra spaces and leading and trailing cruft for sorting
4582 * Accepts: unstripped subject
4583 * pointer to return stripped subject, in cpystr form
4584 * Returns: T if subject had a re/fwd, NIL otherwise
4587 unsigned int mail_strip_subject (char *t,char **ret)
4589 SIZEDTEXT src,dst;
4590 unsigned long i,slen;
4591 char c,*s,*x;
4592 unsigned int refwd = NIL;
4593 if ((src.size = strlen (t)) != 0) { /* have non-empty subject? */
4594 src.data = (unsigned char *) t;
4595 /* Step 1 */
4596 /* make copy, convert MIME2 if needed */
4597 *ret = s = (utf8_mime2text (&src,&dst,U8T_CANONICAL) &&
4598 (src.data != dst.data)) ? (char *) dst.data : cpystr (t);
4599 /* convert spaces to tab, strip extra spaces */
4600 for (x = t = s, c = 'x'; *t; t++) {
4601 if (c != ' ') c = *x++ = ((*t == '\t') ? ' ' : *t);
4602 else if ((*t != '\t') && (*t != ' ')) c = *x++ = *t;
4604 *x = '\0'; /* tie off string */
4605 /* Step 2 */
4606 for (slen = dst.size; s; slen = strlen (s)) {
4607 for (t = s + slen; t > s; ) switch (t[-1]) {
4608 case ' ': case '\t': /* WSP */
4609 *--t = '\0'; /* just remove it */
4610 break;
4611 case ')': /* possible "(fwd)" */
4612 if ((t >= (s + 5)) && (t[-5] == '(') &&
4613 ((t[-4] == 'F') || (t[-4] == 'f')) &&
4614 ((t[-3] == 'W') || (t[-3] == 'w')) &&
4615 ((t[-2] == 'D') || (t[-2] == 'd'))) {
4616 *(t -= 5) = '\0'; /* remove "(fwd)" */
4617 refwd = T; /* note a re/fwd */
4618 break;
4620 default: /* not a subj-trailer */
4621 t = s;
4622 break;
4624 /* Steps 3-5 */
4625 for (t = s; t; ) switch (*s) {
4626 case ' ': case '\t': /* WSP */
4627 s = t = mail_strip_subject_wsp (s + 1);
4628 break;
4629 case 'r': case 'R': /* possible "re" */
4630 if (((s[1] == 'E') || (s[1] == 'e')) &&
4631 (t = mail_strip_subject_wsp (s + 2)) &&
4632 (t = mail_strip_subject_blob (t)) && (*t == ':')) {
4633 s = ++t; /* found "re" */
4634 refwd = T; /* definitely a re/fwd at this point */
4636 else t = NIL; /* found subj-middle */
4637 break;
4638 case 'f': case 'F': /* possible "fw" or "fwd" */
4639 if (((s[1] == 'w') || (s[1] == 'W')) &&
4640 (((s[2] == 'd') || (s[2] == 'D')) ?
4641 (t = mail_strip_subject_wsp (s + 3)) :
4642 (t = mail_strip_subject_wsp (s + 2))) &&
4643 (t = mail_strip_subject_blob (t)) && (*t == ':')) {
4644 s = ++t; /* found "fwd" */
4645 refwd = T; /* definitely a re/fwd at this point */
4647 else t = NIL; /* found subj-middle */
4648 break;
4649 case '[': /* possible subj-blob */
4650 if ((t = mail_strip_subject_blob (s)) && *t) s = t;
4651 else t = NIL; /* found subj-middle */
4652 break;
4653 default:
4654 t = NIL; /* found subj-middle */
4655 break;
4657 /* Step 6 */
4658 /* Netscape-style "[Fwd: ...]"? */
4659 if ((*s == '[') && ((s[1] == 'F') || (s[1] == 'f')) &&
4660 ((s[2] == 'W') || (s[2] == 'w')) &&
4661 ((s[3] == 'D') || (s[3] == 'd')) && (s[4] == ':') &&
4662 (s[i = strlen (s) - 1] == ']')) {
4663 s[i] = '\0'; /* flush closing "]" */
4664 s += 5; /* and leading "[Fwd:" */
4665 refwd = T; /* definitely a re/fwd at this point */
4667 else break; /* don't need to loop back to step 2 */
4669 if (s != (t = *ret)) { /* removed leading text? */
4670 s = *ret = cpystr (s); /* yes, make a fresh return copy */
4671 fs_give ((void **) &t); /* flush old copy */
4674 else *ret = cpystr (""); /* empty subject */
4675 return refwd; /* return re/fwd state */
4678 /* Strip subject wsp helper routine
4679 * Accepts: text
4680 * Returns: pointer to text after blob
4683 char *mail_strip_subject_wsp (char *s)
4685 while ((*s == ' ') || (*s == '\t')) s++;
4686 return s;
4690 /* Strip subject blob helper routine
4691 * Accepts: text
4692 * Returns: pointer to text after any blob, NIL if blob-like but not blob
4695 char *mail_strip_subject_blob (char *s)
4697 if (*s != '[') return s; /* not a blob, ignore */
4698 /* search for end of blob */
4699 while (*++s != ']') if ((*s == '[') || !*s) return NIL;
4700 return mail_strip_subject_wsp (s + 1);
4703 /* Sort compare messages
4704 * Accept: first message sort cache element
4705 * second message sort cache element
4706 * Returns: -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2
4709 int mail_sort_compare (const void *a1,const void *a2)
4711 int i = 0;
4712 SORTCACHE *s1 = *(SORTCACHE **) a1;
4713 SORTCACHE *s2 = *(SORTCACHE **) a2;
4714 SORTPGM *pgm = s1->pgm;
4715 if (!s1->sorted) { /* this one sorted yet? */
4716 s1->sorted = T;
4717 pgm->progress.sorted++; /* another sorted message */
4719 if (!s2->sorted) { /* this one sorted yet? */
4720 s2->sorted = T;
4721 pgm->progress.sorted++; /* another sorted message */
4723 do {
4724 switch (pgm->function) { /* execute search program */
4725 case SORTDATE: /* sort by date */
4726 i = compare_ulong (s1->date,s2->date);
4727 break;
4728 case SORTARRIVAL: /* sort by arrival date */
4729 i = compare_ulong (s1->arrival,s2->arrival);
4730 break;
4731 case SORTSIZE: /* sort by message size */
4732 i = compare_ulong (s1->size,s2->size);
4733 break;
4734 case SORTFROM: /* sort by first from */
4735 i = compare_string (s1->from,s2->from);
4736 break;
4737 case SORTTO: /* sort by first to */
4738 i = compare_string (s1->to,s2->to);
4739 break;
4740 case SORTCC: /* sort by first cc */
4741 i = compare_string (s1->cc,s2->cc);
4742 break;
4743 case SORTSUBJECT: /* sort by subject */
4744 i = compare_string (s1->subject,s2->subject);
4745 break;
4747 if (pgm->reverse) i = -i; /* flip results if necessary */
4749 while ((pgm = i ? NIL : pgm->next) != NULL);
4750 /* return result, avoid 0 if at all possible */
4751 return i ? i : compare_ulong (s1->num,s2->num);
4754 /* Return message date as an unsigned long seconds since time began
4755 * Accepts: message cache pointer
4756 * Returns: unsigned long of date
4758 * This routine, like most UNIX systems, is clueless about leap seconds.
4759 * Thus, it treats 23:59:60 as equivalent to 00:00:00 the next day.
4761 * This routine forces any early hours on 1-Jan-1970 in oriental timezones
4762 * to be 1-Jan-1970 00:00:00 UTC, so as to avoid negative longdates.
4765 unsigned long mail_longdate (MESSAGECACHE *elt)
4767 unsigned long m = elt->month ? elt->month : 1;
4768 unsigned long yr = elt->year + BASEYEAR;
4769 /* number of days since time began */
4770 unsigned long ret = (elt->day ? (elt->day - 1) : 0)
4771 + 30 * (m - 1) + ((m + (m > 8)) / 2)
4772 #ifndef USEJULIANCALENDAR
4773 #ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
4774 + ((yr / 400) - (BASEYEAR / 400)) - ((yr / 100) - (BASEYEAR / 100))
4775 #ifdef Y4KBUGFIX
4776 - ((yr / 4000) - (BASEYEAR / 4000))
4777 #endif
4778 - ((m < 3) ?
4779 !(yr % 4) && ((yr % 100) || (!(yr % 400)
4780 #ifdef Y4KBUGFIX
4781 && (yr % 4000)
4782 #endif
4783 )) : 2)
4784 #else /* Orthodox calendar */
4785 + ((2*(yr / 900)) - (2*(BASEYEAR / 900)))
4786 + (((yr % 900) >= 200) - ((BASEYEAR % 900) >= 200))
4787 + (((yr % 900) >= 600) - ((BASEYEAR % 900) >= 600))
4788 - ((yr / 100) - (BASEYEAR / 100))
4789 - ((m < 3) ?
4790 !(yr % 4) && ((yr % 100) || ((yr % 900) == 200) || ((yr % 900) == 600))
4791 : 2)
4792 #endif
4793 #endif
4794 + elt->year * 365 + (((unsigned long) (elt->year + (BASEYEAR % 4))) / 4);
4795 ret *= 24; ret += elt->hours; /* date value in hours */
4796 ret *= 60; ret +=elt->minutes;/* date value in minutes */
4797 yr = (elt->zhours * 60) + elt->zminutes;
4798 if (elt->zoccident) ret += yr;/* occidental timezone, make UTC */
4799 else if (ret < yr) return 0; /* still 31-Dec-1969 in UTC */
4800 else ret -= yr; /* oriental timezone, make UTC */
4801 ret *= 60; ret += elt->seconds;
4802 return ret;
4805 /* Mail thread messages
4806 * Accepts: mail stream
4807 * thread type
4808 * character set
4809 * search program
4810 * option flags
4811 * Returns: thread node tree or NIL if error
4814 THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset,
4815 SEARCHPGM *spg,long flags)
4817 THREADNODE *ret = NIL;
4818 if (stream->dtb) /* must have a live driver */
4819 ret = stream->dtb->thread ? /* do driver's action if available */
4820 (*stream->dtb->thread) (stream,type,charset,spg,flags) :
4821 mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
4822 /* flush search/sort programs if requested */
4823 if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
4824 return ret;
4828 /* Mail thread messages
4829 * Accepts: mail stream
4830 * thread type
4831 * character set
4832 * search program
4833 * option flags
4834 * sorter routine
4835 * Returns: thread node tree or NIL if error
4838 THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset,
4839 SEARCHPGM *spg,long flags,sorter_t sorter)
4841 THREADER *t;
4842 for (t = &mailthreadlist; t; t = t->next)
4843 if (!compare_cstring (type,t->name)) {
4844 THREADNODE *ret = (*t->dispatch) (stream,charset,spg,flags,sorter);
4845 if (mailthreadresults) (*mailthreadresults) (stream,ret);
4846 return ret;
4848 MM_LOG ("No such thread type",ERROR);
4849 return NIL;
4852 /* Mail thread ordered subject
4853 * Accepts: mail stream
4854 * character set
4855 * search program
4856 * option flags
4857 * sorter routine
4858 * Returns: thread node tree
4861 THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset,
4862 SEARCHPGM *spg,long flags,
4863 sorter_t sorter)
4865 THREADNODE *thr = NIL;
4866 THREADNODE *cur,*top,**tc;
4867 SORTPGM pgm,pgm2;
4868 SORTCACHE *s;
4869 unsigned long i,j,*lst,*ls;
4870 /* sort by subject+date */
4871 memset (&pgm,0,sizeof (SORTPGM));
4872 memset (&pgm2,0,sizeof (SORTPGM));
4873 pgm.function = SORTSUBJECT;
4874 pgm.next = &pgm2;
4875 pgm2.function = SORTDATE;
4876 if ((lst = (*sorter) (stream,charset,spg,&pgm,flags & ~(SE_FREE | SE_UID))) != NULL){
4877 if (*(ls = lst)) { /* create thread */
4878 /* note first subject */
4879 cur = top = thr = mail_newthreadnode
4880 ((SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE));
4881 /* note its number */
4882 cur->num = (flags & SE_UID) ? mail_uid (stream,*lst) : *lst;
4883 i = 1; /* number of threads */
4884 while (*ls) { /* build tree */
4885 /* subjects match? */
4886 s = (SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE);
4887 if (compare_cstring (top->sc->subject,s->subject)) {
4888 i++; /* have a new thread */
4889 top = top->branch = cur = mail_newthreadnode (s);
4891 /* start a child of the top */
4892 else if (cur == top) cur = cur->next = mail_newthreadnode (s);
4893 /* sibling of child */
4894 else cur = cur->branch = mail_newthreadnode (s);
4895 /* set to msgno or UID as needed */
4896 cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
4898 /* make threadnode cache */
4899 tc = (THREADNODE **) fs_get (i * sizeof (THREADNODE *));
4900 /* load threadnode cache */
4901 for (j = 0, cur = thr; cur; cur = cur->branch) tc[j++] = cur;
4902 if (i != j) fatal ("Threadnode cache confusion");
4903 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
4904 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
4905 tc[j]->branch = NIL; /* end of root */
4906 thr = tc[0]; /* head of data */
4907 fs_give ((void **) &tc);
4909 fs_give ((void **) &lst);
4911 return thr;
4914 /* Mail thread references
4915 * Accepts: mail stream
4916 * character set
4917 * search program
4918 * option flags
4919 * sorter routine
4920 * Returns: thread node tree
4923 #define REFHASHSIZE 1009 /* arbitrary prime for hash table size */
4925 /* Reference threading container, as described in Jamie Zawinski's web page
4926 * (http://www.jwz.org/doc/threading.html) for this algorithm. These are
4927 * stored as extended data in the hash table (called "id_table" in JWZ's
4928 * document) and are maintained by the hash table routines. The hash table
4929 * routines implement extended data as additional void* words at the end of
4930 * each bucket, hence these strange macros instead of a struct which would
4931 * have been more straightforward.
4934 #define THREADLINKS 3 /* number of thread links */
4936 #define CACHE(data) ((SORTCACHE *) (data)[0])
4937 #define PARENT(data) ((container_t) (data)[1])
4938 #define SETPARENT(data,value) ((container_t) (data[1] = value))
4939 #define SIBLING(data) ((container_t) (data)[2])
4940 #define SETSIBLING(data,value) ((container_t) (data[2] = value))
4941 #define CHILD(data) ((container_t) (data)[3])
4942 #define SETCHILD(data,value) ((container_t) (data[3] = value))
4944 THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset,
4945 SEARCHPGM *spg,long flags,sorter_t sorter)
4947 MESSAGECACHE *elt,telt;
4948 ENVELOPE *env;
4949 SORTCACHE *s;
4950 STRINGLIST *st;
4951 HASHENT *he;
4952 THREADNODE **tc,*cur,*lst,*nxt,*sis,*msg;
4953 container_t con,nxc,prc,sib;
4954 void **sub;
4955 char *t,tmp[MAILTMPLEN];
4956 unsigned long j,nmsgs;
4957 unsigned long i = stream->nmsgs * sizeof (SORTCACHE *);
4958 SORTCACHE **sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
4959 HASHTAB *ht = hash_create (REFHASHSIZE);
4960 THREADNODE *root = NIL;
4961 if (spg) { /* only if a search needs to be done */
4962 int silent = stream->silent;
4963 stream->silent = T; /* don't pass up mm_searched() events */
4964 /* search for messages */
4965 mail_search_full (stream,charset,spg,NIL);
4966 stream->silent = silent; /* restore silence state */
4969 /* create SORTCACHE vector of requested msgs */
4970 for (i = 1, nmsgs = 0; i <= stream->nmsgs; ++i)
4971 if (mail_elt (stream,i)->searched)
4972 (sc[nmsgs++] = (SORTCACHE *)(*mailcache)(stream,i,CH_SORTCACHE))->num =i;
4973 /* separate pass so can do overview fetch lookahead */
4974 for (i = 0; i < nmsgs; ++i) { /* for each requested message */
4975 /* is anything missing in its SORTCACHE? */
4976 if (!((s = sc[i])->date && s->subject && s->message_id && s->references)) {
4977 /* driver has an overview mechanism? */
4978 if (stream->dtb && stream->dtb->overview) {
4979 /* yes, find following unloaded entries */
4980 for (j = i + 1; (j < nmsgs) && !sc[j]->references; ++j);
4981 sprintf (tmp,"%lu",mail_uid (stream,s->num));
4982 if (i != --j) /* end of range different? */
4983 sprintf (tmp + strlen (tmp),":%lu",mail_uid (stream,sc[j]->num));
4984 /* load via overview mechanism */
4985 mail_fetch_overview (stream,tmp,mail_thread_loadcache);
4987 /* still missing data? */
4988 if (!s->date || !s->subject || !s->message_id || !s->references) {
4989 /* try to load data from envelope */
4990 if ((env = mail_fetch_structure (stream,s->num,NIL,NIL)) != NULL) {
4991 if (!s->date && env->date && mail_parse_date (&telt,env->date))
4992 s->date = mail_longdate (&telt);
4993 if (!s->subject && env->subject)
4994 s->refwd =
4995 mail_strip_subject (env->subject,&s->subject);
4996 if (!s->message_id && env->message_id && *env->message_id)
4997 s->message_id = mail_thread_parse_msgid (env->message_id,NIL);
4998 if (!s->references && /* use References: or In-Reply-To: */
4999 !(s->references =
5000 mail_thread_parse_references (env->references,T)))
5001 s->references = mail_thread_parse_references(env->in_reply_to,NIL);
5003 /* last resort */
5004 if (!s->date && !(s->date = s->arrival)) {
5005 /* internal date unknown but can get? */
5006 if (!(elt = mail_elt (stream,s->num))->day &&
5007 !(stream->dtb->flags & DR_NOINTDATE)) {
5008 sprintf (tmp,"%lu",s->num);
5009 mail_fetch_fast (stream,tmp,NIL);
5011 /* wrong thing before 3-Jan-1970 */
5012 s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
5014 if (!s->subject) s->subject = cpystr ("");
5015 if (!s->references) s->references = mail_newstringlist ();
5016 s->dirty = T;
5020 /* Step 1 (preliminary) */
5021 /* generate unique string */
5022 sprintf (tmp,"%s.%lx.%lx@%s",stream->mailbox,stream->uid_validity,
5023 mail_uid (stream,s->num),mylocalhost ());
5024 /* flush old unique string if not message-id */
5025 if (s->unique && (s->unique != s->message_id))
5026 fs_give ((void **) &s->unique);
5027 s->unique = s->message_id ? /* don't permit Message ID duplicates */
5028 (hash_lookup (ht,s->message_id) ? cpystr (tmp) : s->message_id) :
5029 (s->message_id = cpystr (tmp));
5030 /* add unique string to hash table */
5031 hash_add (ht,s->unique,s,THREADLINKS);
5033 /* Step 1 */
5034 for (i = 0; i < nmsgs; ++i) { /* for each message in sortcache */
5035 /* Step 1A */
5036 if ((st = (s = sc[i])->references) && st->text.data)
5037 for (con = hash_lookup_and_add (ht,(char *) st->text.data,NIL,
5038 THREADLINKS); (st = st->next) != NULL; con = nxc) {
5039 nxc = hash_lookup_and_add (ht,(char *) st->text.data,NIL,THREADLINKS);
5040 /* only if no parent & won't introduce loop */
5041 if (!PARENT (nxc) && !mail_thread_check_child (con,nxc)) {
5042 SETPARENT (nxc,con); /* establish parent/child link */
5043 /* other children become sibling of this one */
5044 SETSIBLING (nxc,CHILD (con));
5045 SETCHILD (con,nxc); /* set as child of parent */
5048 else con = NIL; /* else message has no ancestors */
5049 /* Step 1B */
5050 if ((prc = PARENT ((nxc = hash_lookup (ht,s->unique)))) &&
5051 (prc != con)) { /* break links if have a different parent */
5052 SETPARENT (nxc,NIL); /* easy if direct child */
5053 if (nxc == CHILD (prc)) SETCHILD (prc,SIBLING (nxc));
5054 else { /* otherwise hunt through sisters */
5055 for (sib = CHILD (prc); nxc != SIBLING (sib); sib = SIBLING (sib));
5056 SETSIBLING (sib,SIBLING (nxc));
5058 SETSIBLING (nxc,NIL); /* no more little sisters either */
5059 prc = NIL; /* no more parent set */
5061 /* need to set parent, and parent is good? */
5062 if (!prc && !mail_thread_check_child (con,nxc)) {
5063 SETPARENT (nxc,con); /* establish parent/child link */
5064 if (con) { /* if non-root parent, set parent's child */
5065 if (CHILD (con)) { /* have a child already */
5066 /* find youngest daughter */
5067 for (con = CHILD (con); SIBLING (con); con = SIBLING (con));
5068 SETSIBLING (con,nxc); /* add new baby sister */
5070 else SETCHILD (con,nxc);/* set as only child */
5074 fs_give ((void **) &sc); /* finished with sortcache vector */
5076 /* Step 2 */
5077 /* search hash table for parentless messages */
5078 for (i = 0, prc = con = NIL; i < ht->size; i++)
5079 for (he = ht->table[i]; he; he = he->next)
5080 if (!PARENT ((nxc = he->data))) {
5081 /* sibling of previous parentless message */
5082 if (con) con = SETSIBLING (con,nxc);
5083 else prc = con = nxc; /* first parentless message */
5085 /* Once the dummy containers are pruned, we no longer need the parent
5086 * information, so we can convert the containers to THREADNODEs. Since
5087 * we don't need the id_table any more either, we can reset the hash table
5088 * and reuse it as a subject_table. Resetting the hash table will also
5089 * destroy the containers.
5091 /* Step 3 */
5092 /* prune dummies, convert to threadnode */
5093 root = mail_thread_c2node (stream,mail_thread_prune_dummy (prc,NIL),flags);
5094 /* Step 4 */
5095 /* make buffer for sorting */
5096 tc = (THREADNODE **) fs_get (nmsgs * sizeof (THREADNODE *));
5097 /* load threadcache and count nodes to sort */
5098 for (i = 0, cur = root; cur ; cur = cur->branch) tc[i++] = cur;
5099 if (i > 1) { /* only if need to sort */
5100 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
5101 /* relink siblings */
5102 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
5103 tc[j]->branch = NIL; /* end of root */
5104 root = tc[0]; /* establish new root */
5106 /* Step 5A */
5107 hash_reset (ht); /* discard containers, reset ht */
5108 /* Step 5B */
5109 for (cur = root; cur; cur = cur->branch)
5110 if ((t = (nxt = (cur->sc ? cur : cur->next))->sc->subject) && *t) {
5111 /* add new subject to hash table */
5112 if (!(sub = hash_lookup (ht,t))) hash_add (ht,t,cur,0);
5113 /* if one in table not dummy and */
5114 else if ((s = (lst = (THREADNODE *) sub[0])->sc) &&
5115 /* current dummy, or not re/fwd and table is */
5116 (!cur->sc || (!nxt->sc->refwd && s->refwd)))
5117 sub[0] = (void *) cur; /* replace with this message */
5120 /* Step 5C */
5121 for (cur = root, sis = NIL; cur; cur = msg) {
5122 /* do nothing if current message or no sub */
5123 if (!(t = (cur->sc ? cur : cur->next)->sc->subject) || !*t ||
5124 ((lst = (THREADNODE *) (sub = hash_lookup (ht,t))[0]) == cur))
5125 msg = (sis = cur)->branch;
5126 else if (!lst->sc) { /* is message in the table a dummy? */
5127 /* find youngest daughter of msg in table */
5128 for (msg = lst->next; msg->branch; msg = msg->branch);
5129 if (!cur->sc) { /* current message a dummy? */
5130 msg->branch = cur->next;/* current's daughter now dummy's youngest */
5131 msg = cur->branch; /* continue scan at younger sister */
5132 /* now delete this node */
5133 cur->branch = cur->next = NIL;
5134 mail_free_threadnode (&cur);
5136 else { /* current message not a dummy */
5137 msg->branch = cur; /* append as youngest daughter */
5138 msg = cur->branch; /* continue scan at younger sister */
5139 cur->branch = NIL; /* lose our younger sisters */
5142 else { /* no dummies, is current re/fwd, table not? */
5143 if (cur->sc->refwd && !lst->sc->refwd) {
5144 if (lst->next) { /* find youngest daughter of msg in table */
5145 for (msg = lst->next; msg->branch; msg = msg->branch);
5146 msg->branch = cur; /* append as youngest daughter */
5148 else lst->next = cur; /* no children, so make the eldest daughter */
5151 else { /* no re/fwd, create a new dummy */
5152 msg = mail_newthreadnode (NIL);
5153 if (lst == root) { /* msg in table is root? */
5154 root = lst->branch; /* younger sister becomes new root */
5155 /* no longer older sister either */
5156 if (lst == sis) sis = NIL;
5158 else { /* find older sister of msg in table */
5159 for (nxt = root; lst != nxt->branch; nxt = nxt->branch);
5160 /* remove from older sister */
5161 nxt->branch = lst->branch;
5163 msg->next = lst; /* msg in table becomes child */
5164 lst->branch = cur; /* current now little sister of msg in table */
5165 if (sis) { /* have an elder sister? */
5166 if (sis == lst) /* rescan if lost her */
5167 for (sis = root; cur != sis->branch; sis = sis->branch);
5168 sis->branch = msg; /* make dummy younger sister of big sister */
5170 else root = msg; /* otherwise this is the new root */
5171 sub[0] = sis = msg; /* set new msg in table and new big sister */
5173 msg = cur->branch; /* continue scan at younger sister */
5174 cur->branch = NIL; /* lose our younger sisters */
5176 if (sis) sis->branch = msg; /* older sister gets this as younger sister */
5177 else root = msg; /* otherwise this is the new root */
5179 hash_destroy (&ht); /* finished with hash table */
5180 /* Step 6 */
5181 /* sort threads */
5182 root = mail_thread_sort (root,tc);
5183 fs_give ((void **) &tc); /* finished with sort buffer */
5184 return root; /* return sorted list */
5187 /* Fetch overview callback to load sortcache for threading
5188 * Accepts: MAIL stream
5189 * UID of this message
5190 * overview of this message
5191 * msgno of this message
5194 void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov,
5195 unsigned long msgno)
5197 if (msgno && ov) { /* just in case */
5198 MESSAGECACHE telt, *elt;
5199 ENVELOPE *env;
5200 SORTCACHE *s = (SORTCACHE *) (*mailcache) (stream,msgno,CH_SORTCACHE);
5201 if (!s->subject && ov->subject) {
5202 s->refwd = mail_strip_subject (ov->subject,&s->subject);
5203 s->dirty = T;
5205 if (!s->from && ov->from && ov->from->mailbox) {
5206 s->from = cpystr (ov->from->mailbox);
5207 s->dirty = T;
5209 if (!s->date && ov->date && mail_parse_date (&telt,ov->date)) {
5210 s->date = mail_longdate (&telt);
5211 s->dirty = T;
5213 if (!s->message_id && ov->message_id) {
5214 s->message_id = mail_thread_parse_msgid (ov->message_id,NIL);
5215 s->dirty = T;
5217 if (!s->references &&
5218 !(s->references = mail_thread_parse_references (ov->references,T))
5219 && stream->dtb && !strcmp(stream->dtb->name, "imap")
5220 && (elt = mail_elt (stream, msgno)) != NULL
5221 && (env = elt->private.msg.env) != NULL
5222 && env->in_reply_to
5223 && !(s->references = mail_thread_parse_references(env->in_reply_to, NIL))) {
5224 /* don't do In-Reply-To with NNTP mailboxes */
5225 s->references = mail_newstringlist ();
5226 s->dirty = T;
5228 if (!s->size && ov->optional.octets) {
5229 s->size = ov->optional.octets;
5230 s->dirty = T;
5235 /* Thread parse Message ID
5236 * Accepts: pointer to purported Message ID
5237 * pointer to return pointer
5238 * Returns: Message ID or NIL, return pointer updated
5241 char *mail_thread_parse_msgid (char *s,char **ss)
5243 char *ret = NIL;
5244 char *t = NIL;
5245 ADDRESS *adr;
5246 if (s) { /* only for non-NIL strings */
5247 rfc822_skipws (&s); /* skip whitespace */
5248 /* ignore phrases */
5249 if (((*s == '<') || (s = rfc822_parse_phrase (s))) &&
5250 (adr = rfc822_parse_routeaddr (s,&t,BADHOST))) {
5251 /* make return msgid */
5252 if (adr->mailbox && adr->host)
5253 sprintf (ret = (char *) fs_get (strlen (adr->mailbox) +
5254 strlen (adr->host) + 2),"%s@%s",
5255 adr->mailbox,adr->host);
5256 mail_free_address (&adr); /* don't need temporary address */
5259 if (ss) *ss = t; /* update return pointer */
5260 return ret;
5264 /* Thread parse references
5265 * Accepts: pointer to purported references
5266 * parse multiple references flag
5267 * Returns: references or NIL
5270 STRINGLIST *mail_thread_parse_references (char *s,long flag)
5272 char *t;
5273 STRINGLIST *ret = NIL;
5274 STRINGLIST *cur;
5275 /* found first reference? */
5276 if ((t = mail_thread_parse_msgid (s,&s)) != NULL) {
5277 (ret = mail_newstringlist ())->text.data = (unsigned char *) t;
5278 ret->text.size = strlen (t);
5279 if (flag) /* parse subsequent references */
5280 for (cur = ret; (t = mail_thread_parse_msgid (s,&s)) != NULL; cur = cur->next) {
5281 (cur->next = mail_newstringlist ())->text.data = (unsigned char *) t;
5282 cur->next->text.size = strlen (t);
5285 return ret;
5288 /* Prune dummy messages
5289 * Accepts: candidate container to prune
5290 * older sibling of container, if any
5291 * Returns: container in this position, possibly pruned
5292 * All children and younger siblings are also pruned
5295 container_t mail_thread_prune_dummy (container_t msg,container_t ane)
5297 /* prune container and children */
5298 container_t ret = msg ? mail_thread_prune_dummy_work (msg,ane) : NIL;
5299 /* prune all younger sisters */
5300 if (ret) for (ane = ret; ane && (msg = SIBLING (ane)); ane = msg)
5301 msg = mail_thread_prune_dummy_work (msg,ane);
5302 return ret;
5306 /* Prune dummy messages worker routine
5307 * Accepts: candidate container to prune
5308 * older sibling of container, if any
5309 * Returns: container in this position, possibly pruned
5310 * All children are also pruned
5313 container_t mail_thread_prune_dummy_work (container_t msg,container_t ane)
5315 container_t cur;
5316 /* get children, if any */
5317 container_t nxt = mail_thread_prune_dummy (CHILD (msg),NIL);
5318 /* just update children if container has msg */
5319 if (CACHE (msg)) SETCHILD (msg,nxt);
5320 else if (!nxt) { /* delete dummy with no children */
5321 nxt = SIBLING (msg); /* get younger sister */
5322 if (ane) SETSIBLING (ane,nxt);
5323 /* prune younger sister if exists */
5324 msg = nxt ? mail_thread_prune_dummy_work (nxt,ane) : NIL;
5326 /* not if parent root & multiple children */
5327 else if ((cur = PARENT (msg)) || !SIBLING (nxt)) {
5328 /* OK to promote, try younger sister of aunt */
5329 if (ane) SETSIBLING (ane,nxt);
5330 /* otherwise promote to child of grandmother */
5331 else if (cur) SETCHILD (cur,nxt);
5332 SETPARENT (nxt,cur); /* set parent as well */
5333 /* look for end of siblings in new container */
5334 for (cur = nxt; SIBLING (cur); cur = SIBLING (cur));
5335 /* reattach deleted container's siblings */
5336 SETSIBLING (cur,SIBLING (msg));
5337 /* prune and return new container */
5338 msg = mail_thread_prune_dummy_work (nxt,ane);
5340 else SETCHILD (msg,nxt); /* in case child pruned */
5341 return msg; /* return this message */
5344 /* Test that purported mother is not a child of purported daughter
5345 * Accepts: mother
5346 * purported daughter
5347 * Returns: T if circular parentage exists, else NIL
5350 long mail_thread_check_child (container_t mother,container_t daughter)
5352 if (mother) { /* only if mother non-NIL */
5353 if (mother == daughter) return T;
5354 for (daughter = CHILD (daughter); daughter; daughter = SIBLING (daughter))
5355 if (mail_thread_check_child (mother,daughter)) return T;
5357 return NIL;
5361 /* Generate threadnodes from containers
5362 * Accepts: Mail stream
5363 * container
5364 * flags
5365 * Return: threadnode list
5368 THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags)
5370 THREADNODE *ret,*cur;
5371 SORTCACHE *s;
5372 container_t nxt;
5373 /* for each container */
5374 for (ret = cur = NIL; con; con = SIBLING (con)) {
5375 s = CACHE (con); /* yes, get its sortcache */
5376 /* create node for it */
5377 if (ret) cur = cur->branch = mail_newthreadnode (s);
5378 else ret = cur = mail_newthreadnode (s);
5379 /* attach sequence or UID for non-dummy */
5380 if (s) cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
5381 /* attach the children */
5382 if ((nxt = CHILD (con)) != NULL) cur->next = mail_thread_c2node (stream,nxt,flags);
5384 return ret;
5387 /* Sort thread tree by date
5388 * Accepts: thread tree to sort
5389 * qsort vector to sort
5390 * Returns: sorted thread tree
5393 THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc)
5395 unsigned long i,j;
5396 THREADNODE *cur;
5397 /* sort children of each thread */
5398 for (cur = thr; cur; cur = cur->branch)
5399 if (cur->next) cur->next = mail_thread_sort (cur->next,tc);
5400 /* Must do this in a separate pass since recursive call will clobber tc */
5401 /* load threadcache and count nodes to sort */
5402 for (i = 0, cur = thr; cur; cur = cur->branch) tc[i++] = cur;
5403 if (i > 1) { /* only if need to sort */
5404 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
5405 /* relink root siblings */
5406 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
5407 tc[j]->branch = NIL; /* end of root */
5409 return i ? tc[0] : NIL; /* return new head of list */
5413 /* Thread compare date
5414 * Accept: first message sort cache element
5415 * second message sort cache element
5416 * Returns: -1 if a1 < a2, 1 if a1 > a2
5418 * This assumes that a sort cache element is either a message (with a
5419 * sortcache entry) or a dummy with a message (with sortcache entry) child.
5420 * This is true of both the ORDEREDSUBJECT (no dummies) and REFERENCES
5421 * (dummies only at top-level, and with non-dummy children).
5423 * If a new algorithm allows a dummy parent to have a dummy child, this
5424 * routine must be changed if it is to be used by that algorithm.
5426 * Messages with bogus dates are always sorted at the top.
5429 int mail_thread_compare_date (const void *a1,const void *a2)
5431 THREADNODE *t1 = *(THREADNODE **) a1;
5432 THREADNODE *t2 = *(THREADNODE **) a2;
5433 SORTCACHE *s1 = t1->sc ? t1->sc : t1->next->sc;
5434 SORTCACHE *s2 = t2->sc ? t2->sc : t2->next->sc;
5435 int ret = compare_ulong (s1->date,s2->date);
5436 /* use number as final tie-breaker */
5437 return ret ? ret : compare_ulong (s1->num,s2->num);
5440 /* Mail parse sequence
5441 * Accepts: mail stream
5442 * sequence to parse
5443 * Returns: T if parse successful, else NIL
5446 long mail_sequence (MAILSTREAM *stream,unsigned char *sequence)
5448 unsigned long i,j,x;
5449 for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
5450 while (sequence && *sequence){/* while there is something to parse */
5451 if (*sequence == '*') { /* maximum message */
5452 if (stream->nmsgs) i = stream->nmsgs;
5453 else {
5454 MM_LOG ("No messages, so no maximum message number",ERROR);
5455 return NIL;
5457 sequence++; /* skip past * */
5459 /* parse and validate message number */
5460 else if (!isdigit (*sequence)) {
5461 MM_LOG ("Syntax error in sequence",ERROR);
5462 return NIL;
5464 else if (!(i = strtoul (sequence,(char **) &sequence,10)) ||
5465 (i > stream->nmsgs)) {
5466 MM_LOG ("Sequence out of range",ERROR);
5467 return NIL;
5469 switch (*sequence) { /* see what the delimiter is */
5470 case ':': /* sequence range */
5471 if (*++sequence == '*') { /* maximum message */
5472 if (stream->nmsgs) j = stream->nmsgs;
5473 else {
5474 MM_LOG ("No messages, so no maximum message number",ERROR);
5475 return NIL;
5477 sequence++; /* skip past * */
5479 /* parse end of range */
5480 else if (!(j = strtoul (sequence,(char **) &sequence,10)) ||
5481 (j > stream->nmsgs)) {
5482 MM_LOG ("Sequence range invalid",ERROR);
5483 return NIL;
5485 if (*sequence && *sequence++ != ',') {
5486 MM_LOG ("Sequence range syntax error",ERROR);
5487 return NIL;
5489 if (i > j) { /* swap the range if backwards */
5490 x = i; i = j; j = x;
5492 /* mark each item in the sequence */
5493 while (i <= j) mail_elt (stream,j--)->sequence = T;
5494 break;
5495 case ',': /* single message */
5496 ++sequence; /* skip the delimiter, fall into end case */
5497 case '\0': /* end of sequence, mark this message */
5498 mail_elt (stream,i)->sequence = T;
5499 break;
5500 default: /* anything else is a syntax error! */
5501 MM_LOG ("Sequence syntax error",ERROR);
5502 return NIL;
5505 return T; /* successfully parsed sequence */
5508 /* Parse flag list
5509 * Accepts: MAIL stream
5510 * flag list as a character string
5511 * pointer to user flags to return
5512 * Returns: system flags
5515 long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf)
5517 char *t,*n,*s,tmp[MAILTMPLEN],msg[MAILTMPLEN];
5518 short f = 0;
5519 long i,j;
5520 *uf = 0; /* initially no user flags */
5521 if (flag && *flag) { /* no-op if no flag string */
5522 /* check if a list and make sure valid */
5523 if (((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) ||
5524 (strlen (flag) >= MAILTMPLEN)) {
5525 MM_LOG ("Bad flag list",ERROR);
5526 return NIL;
5528 /* copy the flag string w/o list construct */
5529 strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i)));
5530 tmp[j] = '\0';
5531 while ((t = n) && *t) { /* parse the flags */
5532 /* find end of flag */
5533 if ((n = strchr (t,' ')) != NULL) *n++ = '\0';
5534 if (*t == '\\') { /* system flag? */
5535 if (!compare_cstring (t+1,"SEEN")) f |= fSEEN;
5536 else if (!compare_cstring (t+1,"DELETED")) f |= fDELETED;
5537 else if (!compare_cstring (t+1,"FLAGGED")) f |= fFLAGGED;
5538 else if (!compare_cstring (t+1,"ANSWERED")) f |= fANSWERED;
5539 else if (!compare_cstring (t+1,"DRAFT")) f |= fDRAFT;
5540 else {
5541 sprintf (msg,"Unsupported system flag: %.80s",t);
5542 MM_LOG (msg,WARN);
5546 else { /* keyword flag */
5547 for (i = j = 0; /* user flag, search through table */
5548 !i && (j < NUSERFLAGS) && (s = stream->user_flags[j]); ++j)
5549 if (!compare_cstring (t,s)) *uf |= i = 1 << j;
5550 if (!i) { /* flag not found, can it be created? */
5551 if (stream->kwd_create && (j < NUSERFLAGS) && *t &&
5552 (strlen (t) <= MAXUSERFLAG)) {
5553 for (s = t; t && *s; s++) switch (*s) {
5554 default: /* all other characters */
5555 /* SPACE, CTL, or not CHAR */
5556 if ((*s > ' ') && (*s < 0x7f)) break;
5557 case '*': case '%': /* list_wildcards */
5558 case '"': case '\\':/* quoted-specials */
5559 /* atom_specials */
5560 case '(': case ')': case '{':
5561 case ']': /* resp-specials */
5562 sprintf (msg,"Invalid flag: %.80s",t);
5563 MM_LOG (msg,WARN);
5564 t = NIL;
5566 if (t) { /* only if valid */
5567 *uf |= 1 << j; /* set the bit */
5568 stream->user_flags[j] = cpystr (t);
5569 /* if out of user flags */
5570 if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
5573 else {
5574 if (*t) sprintf (msg,"Unknown flag: %.80s",t);
5575 else strcpy (msg,"Empty flag invalid");
5576 MM_LOG (msg,WARN);
5582 return f;
5585 /* Mail check network stream for usability with new name
5586 * Accepts: MAIL stream
5587 * candidate new name
5588 * Returns: T if stream can be used, NIL otherwise
5591 long mail_usable_network_stream (MAILSTREAM *stream,char *name)
5593 NETMBX smb,nmb,omb;
5594 char *s = NIL;
5595 long ret= (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL) &&
5596 mail_valid_net_parse (name,&nmb) &&
5597 mail_valid_net_parse (stream->mailbox,&smb) &&
5598 mail_valid_net_parse (stream->original_mailbox,&omb) &&
5599 ((!compare_cstring (smb.host,trustdns ?
5600 (s = tcp_canonical (nmb.host)) : nmb.host) &&
5601 !strcmp (smb.service,nmb.service) &&
5602 (!nmb.port || (smb.port == nmb.port)) &&
5603 (nmb.anoflag == stream->anonymous) &&
5604 (!nmb.user[0] || !strcmp (smb.user,nmb.user))) ||
5605 (!compare_cstring (omb.host,nmb.host) &&
5606 !strcmp (omb.service,nmb.service) &&
5607 (!nmb.port || (omb.port == nmb.port)) &&
5608 (nmb.anoflag == stream->anonymous) &&
5609 (!nmb.user[0] || !strcmp (omb.user,nmb.user))))) ? LONGT : NIL;
5610 if(s) fs_give((void **) &s);
5611 return ret;
5614 /* Mail data structure instantiation routines */
5617 /* Mail instantiate cache elt
5618 * Accepts: initial message number
5619 * Returns: new cache elt
5622 MESSAGECACHE *mail_new_cache_elt (unsigned long msgno)
5624 MESSAGECACHE *elt = (MESSAGECACHE *) memset (fs_get (sizeof (MESSAGECACHE)),
5625 0,sizeof (MESSAGECACHE));
5626 elt->lockcount = 1; /* initially only cache references it */
5627 elt->msgno = msgno; /* message number */
5628 return elt;
5632 /* Mail instantiate envelope
5633 * Returns: new envelope
5636 ENVELOPE *mail_newenvelope (void)
5638 return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE));
5642 /* Mail instantiate address
5643 * Returns: new address
5646 ADDRESS *mail_newaddr (void)
5648 return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS));
5651 /* Mail instantiate body
5652 * Returns: new body
5655 BODY *mail_newbody (void)
5657 return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
5661 /* Mail initialize body
5662 * Accepts: body
5663 * Returns: body
5666 BODY *mail_initbody (BODY *body)
5668 memset ((void *) body,0,sizeof (BODY));
5669 body->type = TYPETEXT; /* content type */
5670 body->encoding = ENC7BIT; /* content encoding */
5671 return body;
5675 /* Mail instantiate body parameter
5676 * Returns: new body part
5679 PARAMETER *mail_newbody_parameter (void)
5681 return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER));
5685 /* Mail instantiate body part
5686 * Returns: new body part
5689 PART *mail_newbody_part (void)
5691 PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART));
5692 mail_initbody (&part->body); /* initialize the body */
5693 return part;
5697 /* Mail instantiate body message part
5698 * Returns: new body message part
5701 MESSAGE *mail_newmsg (void)
5703 return (MESSAGE *) memset (fs_get (sizeof (MESSAGE)),0,sizeof (MESSAGE));
5706 /* Mail instantiate string list
5707 * Returns: new string list
5710 STRINGLIST *mail_newstringlist (void)
5712 return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0,
5713 sizeof (STRINGLIST));
5717 /* Mail instantiate new search program
5718 * Returns: new search program
5721 SEARCHPGM *mail_newsearchpgm (void)
5723 return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM));
5727 /* Mail instantiate new search program
5728 * Accepts: header line name
5729 * Returns: new search program
5732 SEARCHHEADER *mail_newsearchheader (char *line,char *text)
5734 SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)),
5735 0,sizeof (SEARCHHEADER));
5736 hdr->line.size = strlen ((char *) (hdr->line.data =
5737 (unsigned char *) cpystr (line)));
5738 hdr->text.size = strlen ((char *) (hdr->text.data =
5739 (unsigned char *) cpystr (text)));
5740 return hdr;
5744 /* Mail instantiate new search set
5745 * Returns: new search set
5748 SEARCHSET *mail_newsearchset (void)
5750 return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET));
5754 /* Mail instantiate new search or
5755 * Returns: new search or
5758 SEARCHOR *mail_newsearchor (void)
5760 SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0,
5761 sizeof (SEARCHOR));
5762 or->first = mail_newsearchpgm ();
5763 or->second = mail_newsearchpgm ();
5764 return or;
5767 /* Mail instantiate new searchpgmlist
5768 * Returns: new searchpgmlist
5771 SEARCHPGMLIST *mail_newsearchpgmlist (void)
5773 SEARCHPGMLIST *pgl = (SEARCHPGMLIST *)
5774 memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST));
5775 pgl->pgm = mail_newsearchpgm ();
5776 return pgl;
5780 /* Mail instantiate new sortpgm
5781 * Returns: new sortpgm
5784 SORTPGM *mail_newsortpgm (void)
5786 return (SORTPGM *) memset (fs_get (sizeof (SORTPGM)),0,sizeof (SORTPGM));
5790 /* Mail instantiate new threadnode
5791 * Accepts: sort cache for thread node
5792 * Returns: new threadnode
5795 THREADNODE *mail_newthreadnode (SORTCACHE *sc)
5797 THREADNODE *thr = (THREADNODE *) memset (fs_get (sizeof (THREADNODE)),0,
5798 sizeof (THREADNODE));
5799 if (sc) thr->sc = sc; /* initialize sortcache */
5800 return thr;
5804 /* Mail instantiate new acllist
5805 * Returns: new acllist
5808 ACLLIST *mail_newacllist (void)
5810 return (ACLLIST *) memset (fs_get (sizeof (ACLLIST)),0,sizeof (ACLLIST));
5814 /* Mail instantiate new quotalist
5815 * Returns: new quotalist
5818 QUOTALIST *mail_newquotalist (void)
5820 return (QUOTALIST *) memset (fs_get (sizeof (QUOTALIST)),0,
5821 sizeof (QUOTALIST));
5824 /* Mail garbage collection routines */
5827 /* Mail garbage collect body
5828 * Accepts: pointer to body pointer
5831 void mail_free_body (BODY **body)
5833 if (*body) { /* only free if exists */
5834 mail_free_body_data (*body);/* free its data */
5835 fs_give ((void **) body); /* return body to free storage */
5840 /* Mail garbage collect body data
5841 * Accepts: body pointer
5844 void mail_free_body_data (BODY *body)
5846 switch (body->type) { /* free contents */
5847 case TYPEMULTIPART: /* multiple part */
5848 mail_free_body_part (&body->nested.part);
5849 break;
5850 case TYPEMESSAGE: /* encapsulated message */
5851 if (body->subtype && !strcmp (body->subtype,"RFC822")) {
5852 mail_free_stringlist (&body->nested.msg->lines);
5853 mail_gc_msg (body->nested.msg,GC_ENV | GC_TEXTS);
5855 if (body->nested.msg) fs_give ((void **) &body->nested.msg);
5856 break;
5857 default:
5858 break;
5860 if (body->subtype) fs_give ((void **) &body->subtype);
5861 mail_free_body_parameter (&body->parameter);
5862 if (body->id) fs_give ((void **) &body->id);
5863 if (body->description) fs_give ((void **) &body->description);
5864 if (body->disposition.type) fs_give ((void **) &body->disposition.type);
5865 if (body->disposition.parameter)
5866 mail_free_body_parameter (&body->disposition.parameter);
5867 if (body->language) mail_free_stringlist (&body->language);
5868 if (body->location) fs_give ((void **) &body->location);
5869 if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
5870 if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
5871 if (body->md5) fs_give ((void **) &body->md5);
5872 if (mailfreebodysparep && body->sparep)
5873 (*mailfreebodysparep) (&body->sparep);
5876 /* Mail garbage collect body parameter
5877 * Accepts: pointer to body parameter pointer
5880 void mail_free_body_parameter (PARAMETER **parameter)
5882 if (*parameter) { /* only free if exists */
5883 if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
5884 if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
5885 /* run down the list as necessary */
5886 mail_free_body_parameter (&(*parameter)->next);
5887 /* return body part to free storage */
5888 fs_give ((void **) parameter);
5893 /* Mail garbage collect body part
5894 * Accepts: pointer to body part pointer
5897 void mail_free_body_part (PART **part)
5899 if (*part) { /* only free if exists */
5900 mail_free_body_data (&(*part)->body);
5901 /* run down the list as necessary */
5902 mail_free_body_part (&(*part)->next);
5903 fs_give ((void **) part); /* return body part to free storage */
5907 /* Mail garbage collect message cache
5908 * Accepts: mail stream
5910 * The message cache is set to NIL when this function finishes.
5913 void mail_free_cache (MAILSTREAM *stream)
5915 /* do driver specific stuff first */
5916 mail_gc (stream,GC_ELT | GC_ENV | GC_TEXTS);
5917 /* flush the cache */
5918 (*mailcache) (stream,(long) 0,CH_INIT);
5922 /* Mail garbage collect cache element
5923 * Accepts: pointer to cache element pointer
5926 void mail_free_elt (MESSAGECACHE **elt)
5928 /* only free if exists and no sharers */
5929 if (*elt && !--(*elt)->lockcount) {
5930 mail_gc_msg (&(*elt)->private.msg,GC_ENV | GC_TEXTS);
5931 if (mailfreeeltsparep && (*elt)->sparep)
5932 (*mailfreeeltsparep) (&(*elt)->sparep);
5933 fs_give ((void **) elt);
5935 else *elt = NIL; /* else simply drop pointer */
5938 /* Mail garbage collect envelope
5939 * Accepts: pointer to envelope pointer
5942 void mail_free_envelope (ENVELOPE **env)
5944 if (*env) { /* only free if exists */
5945 if ((*env)->remail) fs_give ((void **) &(*env)->remail);
5946 mail_free_address (&(*env)->return_path);
5947 if ((*env)->date) fs_give ((void **) &(*env)->date);
5948 mail_free_address (&(*env)->from);
5949 mail_free_address (&(*env)->sender);
5950 mail_free_address (&(*env)->reply_to);
5951 if ((*env)->subject) fs_give ((void **) &(*env)->subject);
5952 mail_free_address (&(*env)->to);
5953 mail_free_address (&(*env)->cc);
5954 mail_free_address (&(*env)->bcc);
5955 if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
5956 if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
5957 if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
5958 if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to);
5959 if ((*env)->references) fs_give ((void **) &(*env)->references);
5960 if (mailfreeenvelopesparep && (*env)->sparep)
5961 (*mailfreeenvelopesparep) (&(*env)->sparep);
5962 fs_give ((void **) env); /* return envelope to free storage */
5967 /* Mail garbage collect address
5968 * Accepts: pointer to address pointer
5971 void mail_free_address (ADDRESS **address)
5973 if (*address) { /* only free if exists */
5974 if ((*address)->personal) fs_give ((void **) &(*address)->personal);
5975 if ((*address)->adl) fs_give ((void **) &(*address)->adl);
5976 if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
5977 if ((*address)->host) fs_give ((void **) &(*address)->host);
5978 if ((*address)->error) fs_give ((void **) &(*address)->error);
5979 if ((*address)->orcpt.type) fs_give ((void **) &(*address)->orcpt.type);
5980 if ((*address)->orcpt.addr) fs_give ((void **) &(*address)->orcpt.addr);
5981 mail_free_address (&(*address)->next);
5982 fs_give ((void **) address);/* return address to free storage */
5987 /* Mail garbage collect stringlist
5988 * Accepts: pointer to stringlist pointer
5991 void mail_free_stringlist (STRINGLIST **string)
5993 if (*string) { /* only free if exists */
5994 if ((*string)->text.data) fs_give ((void **) &(*string)->text.data);
5995 mail_free_stringlist (&(*string)->next);
5996 fs_give ((void **) string); /* return string to free storage */
6000 /* Mail garbage collect searchpgm
6001 * Accepts: pointer to searchpgm pointer
6004 void mail_free_searchpgm (SEARCHPGM **pgm)
6006 if (*pgm) { /* only free if exists */
6007 mail_free_searchset (&(*pgm)->msgno);
6008 mail_free_searchset (&(*pgm)->uid);
6009 mail_free_searchor (&(*pgm)->or);
6010 mail_free_searchpgmlist (&(*pgm)->not);
6011 mail_free_searchheader (&(*pgm)->header);
6012 mail_free_stringlist (&(*pgm)->bcc);
6013 mail_free_stringlist (&(*pgm)->body);
6014 mail_free_stringlist (&(*pgm)->cc);
6015 mail_free_stringlist (&(*pgm)->from);
6016 mail_free_stringlist (&(*pgm)->keyword);
6017 mail_free_stringlist (&(*pgm)->subject);
6018 mail_free_stringlist (&(*pgm)->text);
6019 mail_free_stringlist (&(*pgm)->to);
6020 mail_free_stringlist (&(*pgm)->x_gm_ext1);
6021 fs_give ((void **) pgm); /* return program to free storage */
6026 /* Mail garbage collect searchheader
6027 * Accepts: pointer to searchheader pointer
6030 void mail_free_searchheader (SEARCHHEADER **hdr)
6032 if (*hdr) { /* only free if exists */
6033 if ((*hdr)->line.data) fs_give ((void **) &(*hdr)->line.data);
6034 if ((*hdr)->text.data) fs_give ((void **) &(*hdr)->text.data);
6035 mail_free_searchheader (&(*hdr)->next);
6036 fs_give ((void **) hdr); /* return header to free storage */
6041 /* Mail garbage collect searchset
6042 * Accepts: pointer to searchset pointer
6045 void mail_free_searchset (SEARCHSET **set)
6047 if (*set) { /* only free if exists */
6048 mail_free_searchset (&(*set)->next);
6049 fs_give ((void **) set); /* return set to free storage */
6053 /* Mail garbage collect searchor
6054 * Accepts: pointer to searchor pointer
6057 void mail_free_searchor (SEARCHOR **orl)
6059 if (*orl) { /* only free if exists */
6060 mail_free_searchpgm (&(*orl)->first);
6061 mail_free_searchpgm (&(*orl)->second);
6062 mail_free_searchor (&(*orl)->next);
6063 fs_give ((void **) orl); /* return searchor to free storage */
6068 /* Mail garbage collect search program list
6069 * Accepts: pointer to searchpgmlist pointer
6072 void mail_free_searchpgmlist (SEARCHPGMLIST **pgl)
6074 if (*pgl) { /* only free if exists */
6075 mail_free_searchpgm (&(*pgl)->pgm);
6076 mail_free_searchpgmlist (&(*pgl)->next);
6077 fs_give ((void **) pgl); /* return searchpgmlist to free storage */
6082 /* Mail garbage collect namespace
6083 * Accepts: pointer to namespace
6086 void mail_free_namespace (NAMESPACE **n)
6088 if (*n) {
6089 fs_give ((void **) &(*n)->name);
6090 mail_free_namespace (&(*n)->next);
6091 mail_free_body_parameter (&(*n)->param);
6092 fs_give ((void **) n); /* return namespace to free storage */
6096 /* Mail garbage collect sort program
6097 * Accepts: pointer to sortpgm pointer
6100 void mail_free_sortpgm (SORTPGM **pgm)
6102 if (*pgm) { /* only free if exists */
6103 mail_free_sortpgm (&(*pgm)->next);
6104 fs_give ((void **) pgm); /* return sortpgm to free storage */
6109 /* Mail garbage collect thread node
6110 * Accepts: pointer to threadnode pointer
6113 void mail_free_threadnode (THREADNODE **thr)
6115 if (*thr) { /* only free if exists */
6116 mail_free_threadnode (&(*thr)->branch);
6117 mail_free_threadnode (&(*thr)->next);
6118 fs_give ((void **) thr); /* return threadnode to free storage */
6123 /* Mail garbage collect acllist
6124 * Accepts: pointer to acllist pointer
6127 void mail_free_acllist (ACLLIST **al)
6129 if (*al) { /* only free if exists */
6130 if ((*al)->identifier) fs_give ((void **) &(*al)->identifier);
6131 if ((*al)->rights) fs_give ((void **) &(*al)->rights);
6132 mail_free_acllist (&(*al)->next);
6133 fs_give ((void **) al); /* return acllist to free storage */
6138 /* Mail garbage collect quotalist
6139 * Accepts: pointer to quotalist pointer
6142 void mail_free_quotalist (QUOTALIST **ql)
6144 if (*ql) { /* only free if exists */
6145 if ((*ql)->name) fs_give ((void **) &(*ql)->name);
6146 mail_free_quotalist (&(*ql)->next);
6147 fs_give ((void **) ql); /* return quotalist to free storage */
6151 /* Link authenicator
6152 * Accepts: authenticator to add to list
6155 void auth_link (AUTHENTICATOR *auth)
6157 if (!auth->valid || (*auth->valid) ()) {
6158 AUTHENTICATOR **a = &mailauthenticators;
6159 while (*a) a = &(*a)->next; /* find end of list of authenticators */
6160 *a = auth; /* put authenticator at the end */
6161 auth->next = NIL; /* this authenticator is the end of the list */
6166 /* Authenticate access
6167 * Accepts: mechanism name
6168 * responder function
6169 * argument count
6170 * argument vector
6171 * Returns: authenticated user name or NIL
6174 char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
6176 AUTHENTICATOR *auth;
6177 for (auth = mailauthenticators; auth; auth = auth->next)
6178 if (auth->server && !compare_cstring (auth->name,mechanism))
6179 return (!(auth->flags & AU_DISABLE) &&
6180 ((auth->flags & AU_SECURE) ||
6181 !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) ?
6182 (*auth->server) (resp,argc,argv) : NIL;
6183 return NIL; /* no authenticator found */
6186 /* Lookup authenticator index
6187 * Accepts: authenticator index
6188 * Returns: authenticator, or 0 if not found
6191 AUTHENTICATOR *mail_lookup_auth (unsigned long i)
6193 AUTHENTICATOR *auth = mailauthenticators;
6194 while (auth && --i) auth = auth->next;
6195 return auth;
6199 /* Lookup authenticator name
6200 * Accepts: authenticator name
6201 * required authenticator flags
6202 * Returns: index in authenticator chain, or 0 if not found
6205 unsigned int mail_lookup_auth_name (char *mechanism,long flags)
6207 int i;
6208 AUTHENTICATOR *auth;
6209 for (i = 1, auth = mailauthenticators; auth; i++, auth = auth->next)
6210 if (auth->client && !(flags & ~auth->flags) &&
6211 !(auth->flags & AU_DISABLE) && !compare_cstring (auth->name,mechanism))
6212 return i;
6213 return 0;
6216 /* Standard TCP/IP network driver */
6218 static NETDRIVER tcpdriver = {
6219 tcp_open, /* open connection */
6220 tcp_aopen, /* open preauthenticated connection */
6221 tcp_getline, /* get a line */
6222 tcp_getbuffer, /* get a buffer */
6223 tcp_soutr, /* output pushed data */
6224 tcp_sout, /* output string */
6225 tcp_close, /* close connection */
6226 tcp_host, /* return host name */
6227 tcp_remotehost, /* return remote host name */
6228 tcp_port, /* return port number */
6229 tcp_localhost, /* return local host name */
6230 tcp_getsize /* read a specific number of bytes */
6234 /* Network open
6235 * Accepts: NETMBX specifier to open
6236 * default network driver
6237 * default port
6238 * SSL driver
6239 * SSL service name
6240 * SSL driver port
6241 * Returns: Network stream if success, else NIL
6244 NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port,
6245 NETDRIVER *ssld,char *ssls,unsigned long sslp)
6247 NETSTREAM *stream = NIL;
6248 char tmp[MAILTMPLEN];
6249 unsigned long flags = mb->novalidate ? NET_NOVALIDATECERT : 0;
6250 flags |= mb->tls1 ? NET_TRYTLS1
6251 : mb->tls1_1 ? NET_TRYTLS1_1
6252 : mb->tls1_2 ? NET_TRYTLS1_2
6253 : mb->tls1_3 ? NET_TRYTLS1_3 : 0;
6254 if (strlen (mb->host) >= NETMAXHOST) {
6255 sprintf (tmp,"Invalid host name: %.80s",mb->host);
6256 MM_LOG (tmp,ERROR);
6258 /* use designated driver if given */
6259 else if (dv) stream = net_open_work (dv,mb->host,mb->service,port,mb->port,
6260 flags);
6261 else if (mb->sslflag && ssld) /* use ssl if sslflag lit */
6262 stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,flags);
6263 /* if trysslfirst and can open ssl... */
6264 else if ((mb->trysslflag || trysslfirst) && ssld &&
6265 (stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,
6266 flags | NET_SILENT | NET_TRYSSL))) {
6267 if (net_sout (stream,"",0)) mb->sslflag = T;
6268 else {
6269 net_close (stream); /* flush fake SSL stream */
6270 stream = NIL;
6273 /* default to TCP driver */
6274 else stream = net_open_work (&tcpdriver,mb->host,mb->service,port,mb->port,
6275 flags);
6276 return stream;
6279 /* Network open worker routine
6280 * Accepts: network driver
6281 * host name
6282 * service name to look up port
6283 * port number if service name not found
6284 * port number to override service name
6285 * flags (passed on top of port)
6286 * Returns: Network stream if success, else NIL
6289 NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service,
6290 unsigned long port,unsigned long portoverride,
6291 unsigned long flags)
6293 NETSTREAM *stream = NIL;
6294 void *tstream;
6295 if (service && (*service == '*')) {
6296 flags |= NET_NOOPENTIMEOUT; /* mark that no timeout is desired */
6297 ++service; /* no longer need the no timeout indicator */
6299 if (portoverride) { /* explicit port number? */
6300 service = NIL; /* yes, override service name */
6301 port = portoverride; /* use that instead of default port */
6303 if ((tstream = (*dv->open) (host,service,port | flags)) != NULL){
6304 stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
6305 stream->stream = tstream;
6306 stream->dtb = dv;
6308 return stream;
6312 /* Network authenticated open
6313 * Accepts: network driver
6314 * NETMBX specifier
6315 * service specifier
6316 * return user name buffer
6317 * Returns: Network stream if success else NIL
6320 NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *user)
6322 NETSTREAM *stream = NIL;
6323 void *tstream;
6324 if (!dv) dv = &tcpdriver; /* default to TCP driver */
6325 if ((tstream = (*dv->aopen) (mb,service,user)) != NULL) {
6326 stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
6327 stream->stream = tstream;
6328 stream->dtb = dv;
6330 return stream;
6333 /* Network receive line
6334 * Accepts: Network stream
6335 * Returns: text line string or NIL if failure
6338 char *net_getline (NETSTREAM *stream)
6340 return (*stream->dtb->getline) (stream->stream);
6344 char *net_getsize (NETSTREAM *stream, unsigned long size)
6346 return (*stream->dtb->getsize) (stream->stream, size);
6351 /* Network receive buffer
6352 * Accepts: Network stream (must be void * for use as readfn_t)
6353 * size in bytes
6354 * buffer to read into
6355 * Returns: T if success, NIL otherwise
6358 long net_getbuffer (void *st,unsigned long size,char *buffer)
6360 NETSTREAM *stream = (NETSTREAM *) st;
6361 return (*stream->dtb->getbuffer) (stream->stream,size,buffer);
6365 /* Network send null-terminated string
6366 * Accepts: Network stream
6367 * string pointer
6368 * Returns: T if success else NIL
6371 long net_soutr (NETSTREAM *stream,char *string)
6373 return (*stream->dtb->soutr) (stream->stream,string);
6377 /* Network send string
6378 * Accepts: Network stream
6379 * string pointer
6380 * byte count
6381 * Returns: T if success else NIL
6384 long net_sout (NETSTREAM *stream,char *string,unsigned long size)
6386 return (*stream->dtb->sout) (stream->stream,string,size);
6389 /* Network close
6390 * Accepts: Network stream
6393 void net_close (NETSTREAM *stream)
6395 if (stream->stream) (*stream->dtb->close) (stream->stream);
6396 fs_give ((void **) &stream);
6400 /* Network get host name
6401 * Accepts: Network stream
6402 * Returns: host name for this stream
6405 char *net_host (NETSTREAM *stream)
6407 return (*stream->dtb->host) (stream->stream);
6411 /* Network get remote host name
6412 * Accepts: Network stream
6413 * Returns: host name for this stream
6416 char *net_remotehost (NETSTREAM *stream)
6418 return (*stream->dtb->remotehost) (stream->stream);
6421 /* Network return port for this stream
6422 * Accepts: Network stream
6423 * Returns: port number for this stream
6426 unsigned long net_port (NETSTREAM *stream)
6428 return (*stream->dtb->port) (stream->stream);
6432 /* Network get local host name
6433 * Accepts: Network stream
6434 * Returns: local host name
6437 char *net_localhost (NETSTREAM *stream)
6439 return (*stream->dtb->localhost) (stream->stream);
6442 void free_c_client_module_globals(void)
6444 env_end();
6445 tcp_end();