* Version 2.11.6
[alpine.git] / imap / src / c-client / mail.c
blob9b5526b4a0dc9f23afc31ded7c9a8526ff4c539e
1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: Mailbox Access routines
17 * Author: Mark Crispin
18 * UW Technology
19 * University of Washington
20 * Seattle, WA 98195
21 * Internet: MRC@Washington.EDU
23 * Date: 22 November 1989
24 * Last Edited: 15 April 2008
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <time.h>
31 #include "c-client.h"
33 char *UW_copyright = "Copyright 1988-2007 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";
35 /* c-client global data */
37 /* version of this library */
38 static char *mailcclientversion = CCLIENTVERSION;
39 /* list of mail drivers */
40 static DRIVER *maildrivers = NIL;
41 /* list of authenticators */
42 static AUTHENTICATOR *mailauthenticators = NIL;
43 /* SSL driver pointer */
44 static NETDRIVER *mailssldriver = NIL;
45 /* pointer to alternate gets function */
46 static mailgets_t mailgets = NIL;
47 /* pointer to read progress function */
48 static readprogress_t mailreadprogress = NIL;
49 /* mail cache manipulation function */
50 static mailcache_t mailcache = mm_cache;
51 /* RFC-822 output generator */
52 static rfc822out_t mail822out = NIL;
53 /* RFC-822 output generator (new style) */
54 static rfc822outfull_t mail822outfull = NIL;
55 /* SMTP verbose callback */
56 static smtpverbose_t mailsmtpverbose = mm_dlog;
57 /* proxy copy routine */
58 static mailproxycopy_t mailproxycopy = NIL;
59 /* RFC-822 external line parse */
60 static parseline_t mailparseline = NIL;
61 /* RFC-822 external phrase parser */
62 static parsephrase_t mailparsephrase = NIL;
63 static kinit_t mailkinit = NIL; /* application kinit callback */
64 /* note network sent command */
65 static sendcommand_t mailsendcommand = NIL;
66 /* newsrc file name decision function */
67 static newsrcquery_t mailnewsrcquery = NIL;
68 /* ACL results callback */
69 static getacl_t mailaclresults = NIL;
70 /* list rights results callback */
71 static listrights_t maillistrightsresults = NIL;
72 /* my rights results callback */
73 static myrights_t mailmyrightsresults = NIL;
74 /* quota results callback */
75 static quota_t mailquotaresults = NIL;
76 /* quota root results callback */
77 static quotaroot_t mailquotarootresults = NIL;
78 /* sorted results callback */
79 static sortresults_t mailsortresults = NIL;
80 /* threaded results callback */
81 static threadresults_t mailthreadresults = NIL;
82 /* COPY UID results */
83 static copyuid_t mailcopyuid = NIL;
84 /* APPEND UID results */
85 static appenduid_t mailappenduid = NIL;
86 /* free elt extra stuff callback */
87 static freeeltsparep_t mailfreeeltsparep = NIL;
88 /* free envelope extra stuff callback */
89 static freeenvelopesparep_t mailfreeenvelopesparep = NIL;
90 /* free body extra stuff callback */
91 static freebodysparep_t mailfreebodysparep = NIL;
92 /* free stream extra stuff callback */
93 static freestreamsparep_t mailfreestreamsparep = NIL;
94 /* SSL start routine */
95 static sslstart_t mailsslstart = NIL;
96 /* SSL certificate query */
97 static sslcertificatequery_t mailsslcertificatequery = NIL;
98 /* SSL client certificate */
99 static sslclientcert_t mailsslclientcert = NIL;
100 /* SSL client private key */
101 static sslclientkey_t mailsslclientkey = NIL;
102 /* SSL failure notify */
103 static sslfailure_t mailsslfailure = NIL;
104 /* snarf interval */
105 static long mailsnarfinterval = 60;
106 /* snarf preservation */
107 static long mailsnarfpreserve = NIL;
108 /* newsrc name uses canonical host */
109 static long mailnewsrccanon = LONGT;
111 /* supported threaders */
112 static THREADER mailthreadordsub = {
113 "ORDEREDSUBJECT",mail_thread_orderedsubject,NIL
115 static THREADER mailthreadlist = {
116 "REFERENCES",mail_thread_references,&mailthreadordsub
119 /* server name */
120 static char *servicename = "unknown";
121 /* server externally-set authentication ID */
122 static char *externalauthid = NIL;
123 static int expungeatping = T; /* mail_ping() may call mm_expunged() */
124 static int trysslfirst = NIL; /* always try SSL first */
125 static int notimezones = NIL; /* write timezones in "From " header */
126 static int trustdns = T; /* do DNS canonicalization */
127 static int saslusesptrname = T; /* SASL uses name from DNS PTR lookup */
128 /* trustdns also must be set */
129 static int debugsensitive = NIL;/* debug telemetry includes sensitive data */
131 /* Default mail cache handler
132 * Accepts: pointer to cache handle
133 * message number
134 * caching function
135 * Returns: cache data
138 void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op)
140 size_t n;
141 void *ret = NIL;
142 unsigned long i;
143 switch ((int) op) { /* what function? */
144 case CH_INIT: /* initialize cache */
145 if (stream->cache) { /* flush old cache contents */
146 while (stream->cachesize) {
147 mm_cache (stream,stream->cachesize,CH_FREE);
148 mm_cache (stream,stream->cachesize--,CH_FREESORTCACHE);
150 fs_give ((void **) &stream->cache);
151 fs_give ((void **) &stream->sc);
152 stream->nmsgs = 0; /* can't have any messages now */
154 break;
155 case CH_SIZE: /* (re-)size the cache */
156 if (!stream->cache) { /* have a cache already? */
157 /* no, create new cache */
158 n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
159 stream->cache = (MESSAGECACHE **) memset (fs_get (n),0,n);
160 stream->sc = (SORTCACHE **) memset (fs_get (n),0,n);
162 /* is existing cache size large neough */
163 else if (msgno > stream->cachesize) {
164 i = stream->cachesize; /* remember old size */
165 n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
166 fs_resize ((void **) &stream->cache,n);
167 fs_resize ((void **) &stream->sc,n);
168 while (i < stream->cachesize) {
169 stream->cache[i] = NIL;
170 stream->sc[i++] = NIL;
173 break;
175 case CH_MAKEELT: /* return elt, make if necessary */
176 if (!stream->cache[msgno - 1])
177 stream->cache[msgno - 1] = mail_new_cache_elt (msgno);
178 /* falls through */
179 case CH_ELT: /* return elt */
180 ret = (void *) stream->cache[msgno - 1];
181 break;
182 case CH_SORTCACHE: /* return sortcache entry, make if needed */
183 if (!stream->sc[msgno - 1]) stream->sc[msgno - 1] =
184 (SORTCACHE *) memset (fs_get (sizeof (SORTCACHE)),0,sizeof (SORTCACHE));
185 ret = (void *) stream->sc[msgno - 1];
186 break;
187 case CH_FREE: /* free elt */
188 mail_free_elt (&stream->cache[msgno - 1]);
189 break;
190 case CH_FREESORTCACHE:
191 if (stream->sc[msgno - 1]) {
192 if (stream->sc[msgno - 1]->from)
193 fs_give ((void **) &stream->sc[msgno - 1]->from);
194 if (stream->sc[msgno - 1]->to)
195 fs_give ((void **) &stream->sc[msgno - 1]->to);
196 if (stream->sc[msgno - 1]->cc)
197 fs_give ((void **) &stream->sc[msgno - 1]->cc);
198 if (stream->sc[msgno - 1]->subject)
199 fs_give ((void **) &stream->sc[msgno - 1]->subject);
200 if (stream->sc[msgno - 1]->unique &&
201 (stream->sc[msgno - 1]->unique != stream->sc[msgno - 1]->message_id))
202 fs_give ((void **) &stream->sc[msgno - 1]->unique);
203 if (stream->sc[msgno - 1]->message_id)
204 fs_give ((void **) &stream->sc[msgno - 1]->message_id);
205 if (stream->sc[msgno - 1]->references)
206 mail_free_stringlist (&stream->sc[msgno - 1]->references);
207 fs_give ((void **) &stream->sc[msgno - 1]);
209 break;
210 case CH_EXPUNGE: /* expunge cache slot */
211 for (i = msgno - 1; msgno < stream->nmsgs; i++,msgno++) {
212 if (stream->cache[i] = stream->cache[msgno])
213 stream->cache[i]->msgno = msgno;
214 stream->sc[i] = stream->sc[msgno];
216 stream->cache[i] = NIL; /* top of cache goes away */
217 stream->sc[i] = NIL;
218 break;
219 default:
220 fatal ("Bad mm_cache op");
221 break;
223 return ret;
226 /* Dummy string driver for complete in-memory strings */
228 static void mail_string_init (STRING *s,void *data,unsigned long size);
229 static char mail_string_next (STRING *s);
230 static void mail_string_setpos (STRING *s,unsigned long i);
232 STRINGDRIVER mail_string = {
233 mail_string_init, /* initialize string structure */
234 mail_string_next, /* get next byte in string structure */
235 mail_string_setpos /* set position in string structure */
239 /* Initialize mail string structure for in-memory string
240 * Accepts: string structure
241 * pointer to string
242 * size of string
245 static void mail_string_init (STRING *s,void *data,unsigned long size)
247 /* set initial string pointers */
248 s->chunk = s->curpos = (char *) (s->data = data);
249 /* and sizes */
250 s->size = s->chunksize = s->cursize = size;
251 s->data1 = s->offset = 0; /* never any offset */
255 /* Get next character from string
256 * Accepts: string structure
257 * Returns: character, string structure chunk refreshed
260 static char mail_string_next (STRING *s)
262 return *s->curpos++; /* return the last byte */
266 /* Set string pointer position
267 * Accepts: string structure
268 * new position
271 static void mail_string_setpos (STRING *s,unsigned long i)
273 s->curpos = s->chunk + i; /* set new position */
274 s->cursize = s->chunksize - i;/* and new size */
277 /* Mail routines
279 * mail_xxx routines are the interface between this module and the outside
280 * world. Only these routines should be referenced by external callers.
282 * Note that there is an important difference between a "sequence" and a
283 * "message #" (msgno). A sequence is a string representing a sequence in
284 * {"n", "n:m", or combination separated by commas} format, whereas a msgno
285 * is a single integer.
289 /* Mail version check
290 * Accepts: version
293 void mail_versioncheck (char *version)
295 /* attempt to protect again wrong .h */
296 if (strcmp (version,mailcclientversion)) {
297 char tmp[MAILTMPLEN];
298 sprintf (tmp,"c-client library version skew, app=%.100s library=%.100s",
299 version,mailcclientversion);
300 fatal (tmp);
305 /* Mail link driver
306 * Accepts: driver to add to list
309 void mail_link (DRIVER *driver)
311 DRIVER **d = &maildrivers;
312 while (*d) d = &(*d)->next; /* find end of list of drivers */
313 *d = driver; /* put driver at the end */
314 driver->next = NIL; /* this driver is the end of the list */
317 /* Mail manipulate driver parameters
318 * Accepts: mail stream
319 * function code
320 * function-dependent value
321 * Returns: function-dependent return value
324 void *mail_parameters (MAILSTREAM *stream,long function,void *value)
326 void *r,*ret = NIL;
327 DRIVER *d;
328 AUTHENTICATOR *a;
329 switch ((int) function) {
330 case SET_INBOXPATH:
331 fatal ("SET_INBOXPATH not permitted");
332 case GET_INBOXPATH:
333 if ((stream || (stream = mail_open (NIL,"INBOX",OP_PROTOTYPE))) &&
334 stream->dtb) ret = (*stream->dtb->parameters) (function,value);
335 break;
336 case SET_THREADERS:
337 fatal ("SET_THREADERS not permitted");
338 case GET_THREADERS: /* use stream dtb instead of global */
339 ret = (stream && stream->dtb) ?
340 /* KLUDGE ALERT: note stream passed as value */
341 (*stream->dtb->parameters) (function,stream) : (void *) &mailthreadlist;
342 break;
343 case SET_NAMESPACE:
344 fatal ("SET_NAMESPACE not permitted");
345 break;
346 case SET_NEWSRC: /* too late on open stream */
347 if (stream && stream->dtb && (stream != ((*stream->dtb->open) (NIL))))
348 fatal ("SET_NEWSRC not permitted");
349 else ret = env_parameters (function,value);
350 break;
351 case GET_NAMESPACE:
352 case GET_NEWSRC: /* use stream dtb instead of environment */
353 ret = (stream && stream->dtb) ?
354 /* KLUDGE ALERT: note stream passed as value */
355 (*stream->dtb->parameters) (function,stream) :
356 env_parameters (function,value);
357 break;
358 case ENABLE_DEBUG:
359 fatal ("ENABLE_DEBUG not permitted");
360 case DISABLE_DEBUG:
361 fatal ("DISABLE_DEBUG not permitted");
362 case SET_DIRFMTTEST:
363 fatal ("SET_DIRFMTTEST not permitted");
364 case GET_DIRFMTTEST:
365 if (!(stream && stream->dtb &&
366 (ret = (*stream->dtb->parameters) (function,NIL))))
367 fatal ("GET_DIRFMTTEST not permitted");
368 break;
370 case SET_DRIVERS:
371 fatal ("SET_DRIVERS not permitted");
372 case GET_DRIVERS: /* always return global */
373 ret = (void *) maildrivers;
374 break;
375 case SET_DRIVER:
376 fatal ("SET_DRIVER not permitted");
377 case GET_DRIVER:
378 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
379 d = d->next);
380 ret = (void *) d;
381 break;
382 case ENABLE_DRIVER:
383 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
384 d = d->next);
385 if (ret = (void *) d) d->flags &= ~DR_DISABLE;
386 break;
387 case DISABLE_DRIVER:
388 for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
389 d = d->next);
390 if (ret = (void *) d) d->flags |= DR_DISABLE;
391 break;
392 case ENABLE_AUTHENTICATOR:
393 for (a = mailauthenticators;/* scan authenticators */
394 a && compare_cstring (a->name,(char *) value); a = a->next);
395 if (ret = (void *) a) a->flags &= ~AU_DISABLE;
396 break;
397 case DISABLE_AUTHENTICATOR:
398 for (a = mailauthenticators;/* scan authenticators */
399 a && compare_cstring (a->name,(char *) value); a = a->next);
400 if (ret = (void *) a) a->flags |= AU_DISABLE;
401 break;
402 case UNHIDE_AUTHENTICATOR:
403 for (a = mailauthenticators;/* scan authenticators */
404 a && compare_cstring (a->name,(char *) value); a = a->next);
405 if (ret = (void *) a) a->flags &= ~AU_HIDE;
406 break;
407 case HIDE_AUTHENTICATOR:
408 for (a = mailauthenticators;/* scan authenticators */
409 a && compare_cstring (a->name,(char *) value); a = a->next);
410 if (ret = (void *) a) a->flags |= AU_HIDE;
411 break;
412 case SET_EXTERNALAUTHID:
413 if (value) { /* setting external authentication ID */
414 externalauthid = cpystr ((char *) value);
415 mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"EXTERNAL");
417 else { /* clearing external authentication ID */
418 if (externalauthid) fs_give ((void **) &externalauthid);
419 mail_parameters (NIL,HIDE_AUTHENTICATOR,"EXTERNAL");
421 case GET_EXTERNALAUTHID:
422 ret = (void *) externalauthid;
423 break;
425 case SET_GETS:
426 mailgets = (mailgets_t) value;
427 case GET_GETS:
428 ret = (void *) mailgets;
429 break;
430 case SET_READPROGRESS:
431 mailreadprogress = (readprogress_t) value;
432 case GET_READPROGRESS:
433 ret = (void *) mailreadprogress;
434 break;
435 case SET_CACHE:
436 mailcache = (mailcache_t) value;
437 case GET_CACHE:
438 ret = (void *) mailcache;
439 break;
440 case SET_RFC822OUTPUT:
441 mail822out = (rfc822out_t) value;
442 case GET_RFC822OUTPUT:
443 ret = (void *) mail822out;
444 break;
445 case SET_RFC822OUTPUTFULL:
446 mail822outfull = (rfc822outfull_t) value;
447 case GET_RFC822OUTPUTFULL:
448 ret = (void *) mail822outfull;
449 break;
450 case SET_SMTPVERBOSE:
451 mailsmtpverbose = (smtpverbose_t) value;
452 case GET_SMTPVERBOSE:
453 ret = (void *) mailsmtpverbose;
454 break;
455 case SET_MAILPROXYCOPY:
456 mailproxycopy = (mailproxycopy_t) value;
457 case GET_MAILPROXYCOPY:
458 ret = (void *) mailproxycopy;
459 break;
460 case SET_PARSELINE:
461 mailparseline = (parseline_t) value;
462 case GET_PARSELINE:
463 ret = (void *) mailparseline;
464 break;
465 case SET_PARSEPHRASE:
466 mailparsephrase = (parsephrase_t) value;
467 case GET_PARSEPHRASE:
468 ret = (void *) mailparsephrase;
469 break;
470 case SET_NEWSRCQUERY:
471 mailnewsrcquery = (newsrcquery_t) value;
472 case GET_NEWSRCQUERY:
473 ret = (void *) mailnewsrcquery;
474 break;
475 case SET_NEWSRCCANONHOST:
476 mailnewsrccanon = (long) value;
477 case GET_NEWSRCCANONHOST:
478 ret = (void *) mailnewsrccanon;
479 break;
481 case SET_COPYUID:
482 mailcopyuid = (copyuid_t) value;
483 case GET_COPYUID:
484 ret = (void *) mailcopyuid;
485 break;
486 case SET_APPENDUID:
487 mailappenduid = (appenduid_t) value;
488 case GET_APPENDUID:
489 ret = (void *) mailappenduid;
490 break;
491 case SET_FREEENVELOPESPAREP:
492 mailfreeenvelopesparep = (freeenvelopesparep_t) value;
493 case GET_FREEENVELOPESPAREP:
494 ret = (void *) mailfreeenvelopesparep;
495 break;
496 case SET_FREEELTSPAREP:
497 mailfreeeltsparep = (freeeltsparep_t) value;
498 case GET_FREEELTSPAREP:
499 ret = (void *) mailfreeeltsparep;
500 break;
501 case SET_FREESTREAMSPAREP:
502 mailfreestreamsparep = (freestreamsparep_t) value;
503 case GET_FREESTREAMSPAREP:
504 ret = (void *) mailfreestreamsparep;
505 break;
506 case SET_FREEBODYSPAREP:
507 mailfreebodysparep = (freebodysparep_t) value;
508 case GET_FREEBODYSPAREP:
509 ret = (void *) mailfreebodysparep;
510 break;
512 case SET_SSLSTART:
513 mailsslstart = (sslstart_t) value;
514 case GET_SSLSTART:
515 ret = (void *) mailsslstart;
516 break;
517 case SET_SSLCERTIFICATEQUERY:
518 mailsslcertificatequery = (sslcertificatequery_t) value;
519 case GET_SSLCERTIFICATEQUERY:
520 ret = (void *) mailsslcertificatequery;
521 break;
522 case SET_SSLCLIENTCERT:
523 mailsslclientcert = (sslclientcert_t) value;
524 case GET_SSLCLIENTCERT:
525 ret = (void *) mailsslclientcert;
526 break;
527 case SET_SSLCLIENTKEY:
528 mailsslclientkey = (sslclientkey_t) value;
529 case GET_SSLCLIENTKEY:
530 ret = (void *) mailsslclientkey;
531 break;
532 case SET_SSLFAILURE:
533 mailsslfailure = (sslfailure_t) value;
534 case GET_SSLFAILURE:
535 ret = (void *) mailsslfailure;
536 break;
537 case SET_KINIT:
538 mailkinit = (kinit_t) value;
539 case GET_KINIT:
540 ret = (void *) mailkinit;
541 break;
542 case SET_SENDCOMMAND:
543 mailsendcommand = (sendcommand_t) value;
544 case GET_SENDCOMMAND:
545 ret = (void *) mailsendcommand;
546 break;
548 case SET_SERVICENAME:
549 servicename = (char *) value;
550 case GET_SERVICENAME:
551 ret = (void *) servicename;
552 break;
553 case SET_EXPUNGEATPING:
554 expungeatping = (value ? T : NIL);
555 case GET_EXPUNGEATPING:
556 ret = (void *) (expungeatping ? VOIDT : NIL);
557 break;
558 case SET_SORTRESULTS:
559 mailsortresults = (sortresults_t) value;
560 case GET_SORTRESULTS:
561 ret = (void *) mailsortresults;
562 break;
563 case SET_THREADRESULTS:
564 mailthreadresults = (threadresults_t) value;
565 case GET_THREADRESULTS:
566 ret = (void *) mailthreadresults;
567 break;
568 case SET_SSLDRIVER:
569 mailssldriver = (NETDRIVER *) value;
570 case GET_SSLDRIVER:
571 ret = (void *) mailssldriver;
572 break;
573 case SET_TRYSSLFIRST:
574 trysslfirst = (value ? T : NIL);
575 case GET_TRYSSLFIRST:
576 ret = (void *) (trysslfirst ? VOIDT : NIL);
577 break;
578 case SET_NOTIMEZONES:
579 notimezones = (value ? T : NIL);
580 case GET_NOTIMEZONES:
581 ret = (void *) (notimezones ? VOIDT : NIL);
582 break;
583 case SET_TRUSTDNS:
584 trustdns = (value ? T : NIL);
585 case GET_TRUSTDNS:
586 ret = (void *) (trustdns ? VOIDT : NIL);
587 break;
588 case SET_SASLUSESPTRNAME:
589 saslusesptrname = (value ? T : NIL);
590 case GET_SASLUSESPTRNAME:
591 ret = (void *) (saslusesptrname ? VOIDT : NIL);
592 break;
593 case SET_DEBUGSENSITIVE:
594 debugsensitive = (value ? T : NIL);
595 case GET_DEBUGSENSITIVE:
596 ret = (void *) (debugsensitive ? VOIDT : NIL);
597 break;
599 case SET_ACL:
600 mailaclresults = (getacl_t) value;
601 case GET_ACL:
602 ret = (void *) mailaclresults;
603 break;
604 case SET_LISTRIGHTS:
605 maillistrightsresults = (listrights_t) value;
606 case GET_LISTRIGHTS:
607 ret = (void *) maillistrightsresults;
608 break;
609 case SET_MYRIGHTS:
610 mailmyrightsresults = (myrights_t) value;
611 case GET_MYRIGHTS:
612 ret = (void *) mailmyrightsresults;
613 break;
614 case SET_QUOTA:
615 mailquotaresults = (quota_t) value;
616 case GET_QUOTA:
617 ret = (void *) mailquotaresults;
618 break;
619 case SET_QUOTAROOT:
620 mailquotarootresults = (quotaroot_t) value;
621 case GET_QUOTAROOT:
622 ret = (void *) mailquotarootresults;
623 break;
624 case SET_SNARFINTERVAL:
625 mailsnarfinterval = (long) value;
626 case GET_SNARFINTERVAL:
627 ret = (void *) mailsnarfinterval;
628 break;
629 case SET_SNARFPRESERVE:
630 mailsnarfpreserve = (long) value;
631 case GET_SNARFPRESERVE:
632 ret = (void *) mailsnarfpreserve;
633 break;
634 case SET_SNARFMAILBOXNAME:
635 if (stream) { /* have a stream? */
636 if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
637 stream->snarf.name = cpystr ((char *) value);
639 else fatal ("SET_SNARFMAILBOXNAME with no stream");
640 case GET_SNARFMAILBOXNAME:
641 if (stream) ret = (void *) stream->snarf.name;
642 break;
643 default:
644 if (r = smtp_parameters (function,value)) ret = r;
645 if (r = env_parameters (function,value)) ret = r;
646 if (r = tcp_parameters (function,value)) ret = r;
647 if (stream && stream->dtb) {/* if have stream, do for its driver only */
648 if (r = (*stream->dtb->parameters) (function,value)) ret = r;
650 /* else do all drivers */
651 else for (d = maildrivers; d; d = d->next)
652 if (r = (d->parameters) (function,value)) ret = r;
653 break;
655 return ret;
658 /* Mail validate mailbox name
659 * Accepts: MAIL stream
660 * mailbox name
661 * purpose string for error message
662 * Return: driver factory on success, NIL on failure
665 DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose)
667 char tmp[MAILTMPLEN];
668 DRIVER *factory = NIL;
669 /* never allow names with newlines */
670 if (strpbrk (mailbox,"\015\012")) {
671 if (purpose) { /* if want an error message */
672 sprintf (tmp,"Can't %s with such a name",purpose);
673 MM_LOG (tmp,ERROR);
675 return NIL;
677 /* validate name, find driver factory */
678 if (strlen (mailbox) < (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50))
679 for (factory = maildrivers; factory &&
680 ((factory->flags & DR_DISABLE) ||
681 ((factory->flags & DR_LOCAL) && (*mailbox == '{')) ||
682 !(*factory->valid) (mailbox));
683 factory = factory->next);
684 /* validate factory against non-dummy stream */
685 if (factory && stream && stream->dtb && (stream->dtb != factory) &&
686 strcmp (stream->dtb->name,"dummy"))
687 /* factory invalid; if dummy, use stream */
688 factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb;
689 if (!factory && purpose) { /* if want an error message */
690 sprintf (tmp,"Can't %s %.80s: %s",purpose,mailbox,(*mailbox == '{') ?
691 "invalid remote specification" : "no such mailbox");
692 MM_LOG (tmp,ERROR);
694 return factory; /* return driver factory */
697 /* Mail validate network mailbox name
698 * Accepts: mailbox name
699 * mailbox driver to validate against
700 * pointer to where to return host name if non-NIL
701 * pointer to where to return mailbox name if non-NIL
702 * Returns: driver on success, NIL on failure
705 DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox)
707 NETMBX mb;
708 if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,drv->name))
709 return NIL;
710 if (host) strcpy (host,mb.host);
711 if (mailbox) strcpy (mailbox,mb.mailbox);
712 return drv;
716 /* Mail validate network mailbox name
717 * Accepts: mailbox name
718 * NETMBX structure to return values
719 * Returns: T on success, NIL on failure
722 long mail_valid_net_parse (char *name,NETMBX *mb)
724 return mail_valid_net_parse_work (name,mb,"imap");
727 /* Mail validate network mailbox name worker routine
728 * Accepts: mailbox name
729 * NETMBX structure to return values
730 * default service
731 * Returns: T on success, NIL on failure
734 long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service)
736 int i,j;
737 char c,*s,*t,*v,tmp[MAILTMPLEN],arg[MAILTMPLEN];
738 /* initialize structure */
739 memset (mb,'\0',sizeof (NETMBX));
740 /* must have host specification */
741 if (*name++ != '{') return NIL;
742 if (*name == '[') { /* if domain literal, find its ending */
743 if (!((v = strpbrk (name,"]}")) && (*v++ == ']'))) return NIL;
745 /* find end of host name */
746 else if (!(v = strpbrk (name,"/:}"))) return NIL;
747 /* validate length, find mailbox part */
748 if (!((i = v - name) && (i < NETMAXHOST) && (t = strchr (v,'}')) &&
749 ((j = t - v) < MAILTMPLEN) && (strlen (t+1) < (size_t) NETMAXMBX)))
750 return NIL; /* invalid mailbox */
751 strncpy (mb->host,name,i); /* set host name */
752 strncpy (mb->orighost,name,i);
753 mb->host[i] = mb->orighost[i] = '\0';
754 strcpy (mb->mailbox,t+1); /* set mailbox name */
755 if (t - v) { /* any switches or port specification? */
756 strncpy (t = tmp,v,j); /* copy it */
757 tmp[j] = '\0'; /* tie it off */
758 c = *t++; /* get first delimiter */
759 do switch (c) { /* act based upon the character */
760 case ':': /* port specification */
761 if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL;
762 c = t ? *t++ : '\0'; /* get delimiter, advance pointer */
763 break;
764 case '/': /* switch */
765 /* find delimiter */
766 if (t = strpbrk (s = t,"/:=")) {
767 c = *t; /* remember delimiter for later */
768 *t++ = '\0'; /* tie off switch name */
770 else c = '\0'; /* no delimiter */
771 if (c == '=') { /* parse switches which take arguments */
772 if (*t == '"') { /* quoted string? */
773 for (v = arg,i = 0,++t; (c = *t++) != '"';) {
774 if (!c) return NIL; /* unterminated string */
775 /* quote next character */
776 if (c == '\\') c = *t++;
777 if (!c) return NIL; /* can't quote NUL either */
778 arg[i++] = c;
780 c = *t++; /* remember delimiter for later */
781 arg[i] = '\0'; /* tie off argument */
783 else { /* non-quoted argument */
784 if (t = strpbrk (v = t,"/:")) {
785 c = *t; /* remember delimiter for later */
786 *t++ = '\0'; /* tie off switch name */
788 else c = '\0'; /* no delimiter */
789 i = strlen (v); /* length of argument */
791 if (!compare_cstring (s,"service") && (i < NETMAXSRV) && !*mb->service)
792 lcase (strcpy (mb->service,v));
793 else if (!compare_cstring (s,"user") && (i < NETMAXUSER) && !*mb->user)
794 strcpy (mb->user,v);
795 else if (!compare_cstring (s,"authuser") && (i < NETMAXUSER) &&
796 !*mb->authuser) strcpy (mb->authuser,v);
797 else return NIL;
800 else { /* non-argument switch */
801 if (!compare_cstring (s,"anonymous")) mb->anoflag = T;
802 else if (!compare_cstring (s,"debug")) mb->dbgflag = T;
803 else if (!compare_cstring (s,"readonly")) mb->readonlyflag = T;
804 else if (!compare_cstring (s,"secure")) mb->secflag = T;
805 else if (!compare_cstring (s,"norsh")) mb->norsh = T;
806 else if (!compare_cstring (s,"loser")) mb->loser = T;
807 else if (!compare_cstring (s,"tls") && !mb->notlsflag)
808 mb->tlsflag = T;
809 else if (!compare_cstring (s,"tls-sslv23") && !mb->notlsflag)
810 mb->tlssslv23 = mb->tlsflag = T;
811 else if (!compare_cstring (s,"notls") && !mb->tlsflag)
812 mb->notlsflag = T;
813 else if (!compare_cstring (s,"tryssl"))
814 mb->trysslflag = mailssldriver? T : NIL;
815 else if (mailssldriver && !compare_cstring (s,"ssl") && !mb->tlsflag)
816 mb->sslflag = mb->notlsflag = T;
817 else if (!compare_cstring(s, "tls1")
818 && !mb->tls1_1flag && !mb->tls1_2flag && !mb->dtls1flag)
819 mb->sslflag = mb->notlsflag = mb->tls1_1flag = T;
820 else if (!compare_cstring(s, "tls1_1")
821 && !mb->tls1flag && !mb->tls1_2flag && !mb->dtls1flag)
822 mb->sslflag = mb->notlsflag = mb->tls1_1flag = T;
823 else if (!compare_cstring(s, "tls1_2")
824 && !mb->tls1flag && !mb->tls1_1flag && !mb->dtls1flag)
825 mb->sslflag = mb->notlsflag = mb->tls1_2flag = T;
826 else if (!compare_cstring(s, "dtls1")
827 && !mb->tls1flag && !mb->tls1_1flag && !mb->tls1_2flag)
828 mb->sslflag = mb->notlsflag = mb->dtls1flag = T;
829 else if (mailssldriver && !compare_cstring (s,"novalidate-cert"))
830 mb->novalidate = T;
831 /* hack for compatibility with the past */
832 else if (mailssldriver && !compare_cstring (s,"validate-cert"));
833 /* service switches below here */
834 else if (*mb->service) return NIL;
835 else if (!compare_cstring (s,"imap") ||
836 !compare_cstring (s,"nntp") ||
837 !compare_cstring (s,"pop3") ||
838 !compare_cstring (s,"smtp") ||
839 !compare_cstring (s,"submit"))
840 lcase (strcpy (mb->service,s));
841 else if (!compare_cstring (s,"imap2") ||
842 !compare_cstring (s,"imap2bis") ||
843 !compare_cstring (s,"imap4") ||
844 !compare_cstring (s,"imap4rev1"))
845 strcpy (mb->service,"imap");
846 else if (!compare_cstring (s,"pop"))
847 strcpy (mb->service,"pop3");
848 else return NIL; /* invalid non-argument switch */
850 break;
851 default: /* anything else is bogus */
852 return NIL;
853 } while (c); /* see if anything more to parse */
855 /* default mailbox name */
856 if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX");
857 /* default service name */
858 if (!*mb->service) strcpy (mb->service,service);
859 /* /norsh only valid if imap */
860 if (mb->norsh && strcmp (mb->service,"imap")) return NIL;
861 return T;
864 /* Mail scan mailboxes for string
865 * Accepts: mail stream
866 * reference
867 * pattern to search
868 * contents to search
871 void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
873 int remote = ((*pat == '{') || (ref && *ref == '{'));
874 DRIVER *d;
875 if (ref && (strlen (ref) > NETMAXMBX)) {
876 char tmp[MAILTMPLEN];
877 sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
878 MM_LOG (tmp,ERROR);
879 return;
881 if (strlen (pat) > NETMAXMBX) {
882 char tmp[MAILTMPLEN];
883 sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
884 MM_LOG (tmp,ERROR);
885 return;
887 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
888 if (stream) { /* if have a stream, do it for that stream */
889 if ((d = stream->dtb) && d->scan &&
890 !(((d->flags & DR_LOCAL) && remote)))
891 (*d->scan) (stream,ref,pat,contents);
893 /* otherwise do for all DTB's */
894 else for (d = maildrivers; d; d = d->next)
895 if (d->scan && !((d->flags & DR_DISABLE) ||
896 ((d->flags & DR_LOCAL) && remote)))
897 (d->scan) (NIL,ref,pat,contents);
900 /* Mail list mailboxes
901 * Accepts: mail stream
902 * reference
903 * pattern to search
906 void mail_list (MAILSTREAM *stream,char *ref,char *pat)
908 int remote = ((*pat == '{') || (ref && *ref == '{'));
909 DRIVER *d = maildrivers;
910 if (ref && (strlen (ref) > NETMAXMBX)) {
911 char tmp[MAILTMPLEN];
912 sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
913 MM_LOG (tmp,ERROR);
914 return;
916 if (strlen (pat) > NETMAXMBX) {
917 char tmp[MAILTMPLEN];
918 sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
919 MM_LOG (tmp,ERROR);
920 return;
922 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
923 if (stream && stream->dtb) { /* if have a stream, do it for that stream */
924 if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
925 (*d->list) (stream,ref,pat);
927 /* otherwise do for all DTB's */
928 else do if (!((d->flags & DR_DISABLE) ||
929 ((d->flags & DR_LOCAL) && remote)))
930 (d->list) (NIL,ref,pat);
931 while (d = d->next); /* until at the end */
934 /* Mail list subscribed mailboxes
935 * Accepts: mail stream
936 * pattern to search
939 void mail_lsub (MAILSTREAM *stream,char *ref,char *pat)
941 int remote = ((*pat == '{') || (ref && *ref == '{'));
942 DRIVER *d = maildrivers;
943 if (ref && (strlen (ref) > NETMAXMBX)) {
944 char tmp[MAILTMPLEN];
945 sprintf (tmp,"Invalid LSUB reference specification: %.80s",ref);
946 MM_LOG (tmp,ERROR);
947 return;
949 if (strlen (pat) > NETMAXMBX) {
950 char tmp[MAILTMPLEN];
951 sprintf (tmp,"Invalid LSUB pattern specification: %.80s",pat);
952 MM_LOG (tmp,ERROR);
953 return;
955 if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
956 if (stream && stream->dtb) { /* if have a stream, do it for that stream */
957 if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
958 (*d->lsub) (stream,ref,pat);
960 /* otherwise do for all DTB's */
961 else do if (!((d->flags & DR_DISABLE) ||
962 ((d->flags & DR_LOCAL) && remote)))
963 (d->lsub) (NIL,ref,pat);
964 while (d = d->next); /* until at the end */
967 /* Mail subscribe to mailbox
968 * Accepts: mail stream
969 * mailbox to add to subscription list
970 * Returns: T on success, NIL on failure
973 long mail_subscribe (MAILSTREAM *stream,char *mailbox)
975 DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox");
976 return factory ?
977 (factory->subscribe ?
978 (*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL;
982 /* Mail unsubscribe to mailbox
983 * Accepts: mail stream
984 * mailbox to delete from subscription list
985 * Returns: T on success, NIL on failure
988 long mail_unsubscribe (MAILSTREAM *stream,char *mailbox)
990 DRIVER *factory = mail_valid (stream,mailbox,NIL);
991 return (factory && factory->unsubscribe) ?
992 (*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox);
995 /* Mail create mailbox
996 * Accepts: mail stream
997 * mailbox name to create
998 * Returns: T on success, NIL on failure
1001 long mail_create (MAILSTREAM *stream,char *mailbox)
1003 MAILSTREAM *ts;
1004 char *s,*t,tmp[MAILTMPLEN];
1005 size_t i;
1006 DRIVER *d;
1007 /* never allow names with newlines */
1008 if (s = strpbrk (mailbox,"\015\012")) {
1009 MM_LOG ("Can't create mailbox with such a name",ERROR);
1010 return NIL;
1012 if (strlen (mailbox) >= (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
1013 sprintf (tmp,"Can't create %.80s: %s",mailbox,(*mailbox == '{') ?
1014 "invalid remote specification" : "no such mailbox");
1015 MM_LOG (tmp,ERROR);
1016 return NIL;
1018 /* create of INBOX invalid */
1019 if (!compare_cstring (mailbox,"INBOX")) {
1020 MM_LOG ("Can't create INBOX",ERROR);
1021 return NIL;
1023 /* validate name */
1024 if (s = mail_utf7_valid (mailbox)) {
1025 sprintf (tmp,"Can't create %s: %.80s",s,mailbox);
1026 MM_LOG (tmp,ERROR);
1027 return NIL;
1030 /* see if special driver hack */
1031 if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) &&
1032 ((mailbox[2] == 'r') || (mailbox[2] == 'R')) &&
1033 ((mailbox[3] == 'i') || (mailbox[3] == 'I')) &&
1034 ((mailbox[4] == 'v') || (mailbox[4] == 'V')) &&
1035 ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
1036 ((mailbox[6] == 'r') || (mailbox[6] == 'R')) && (mailbox[7] == '.')) {
1037 /* copy driver until likely delimiter */
1038 if ((s = strpbrk (t = mailbox+8,"/\\:")) && (i = s - t)) {
1039 strncpy (tmp,t,i);
1040 tmp[i] = '\0';
1042 else {
1043 sprintf (tmp,"Can't create mailbox %.80s: bad driver syntax",mailbox);
1044 MM_LOG (tmp,ERROR);
1045 return NIL;
1047 for (d = maildrivers; d && strcmp (d->name,tmp); d = d->next);
1048 if (d) mailbox = ++s; /* skip past driver specification */
1049 else {
1050 sprintf (tmp,"Can't create mailbox %.80s: unknown driver",mailbox);
1051 MM_LOG (tmp,ERROR);
1052 return NIL;
1055 /* use stream if one given or deterministic */
1056 else if ((stream && stream->dtb) ||
1057 (((*mailbox == '{') || (*mailbox == '#')) &&
1058 (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT))))
1059 d = stream->dtb;
1060 else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb;
1061 else { /* failed utterly */
1062 sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox);
1063 MM_LOG (tmp,ERROR);
1064 return NIL;
1066 return (*d->create) (stream,mailbox);
1069 /* Mail delete mailbox
1070 * Accepts: mail stream
1071 * mailbox name to delete
1072 * Returns: T on success, NIL on failure
1075 long mail_delete (MAILSTREAM *stream,char *mailbox)
1077 DRIVER *dtb = mail_valid (stream,mailbox,"delete mailbox");
1078 if (!dtb) return NIL;
1079 if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
1080 ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
1081 ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
1082 ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
1083 ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5]) {
1084 MM_LOG ("Can't delete INBOX",ERROR);
1085 return NIL;
1087 return SAFE_DELETE (dtb,stream,mailbox);
1091 /* Mail rename mailbox
1092 * Accepts: mail stream
1093 * old mailbox name
1094 * new mailbox name
1095 * Returns: T on success, NIL on failure
1098 long mail_rename (MAILSTREAM *stream,char *old,char *newname)
1100 char *s,tmp[MAILTMPLEN];
1101 DRIVER *dtb = mail_valid (stream,old,"rename mailbox");
1102 if (!dtb) return NIL;
1103 /* validate name */
1104 if (s = mail_utf7_valid (newname)) {
1105 sprintf (tmp,"Can't rename to %s: %.80s",s,newname);
1106 MM_LOG (tmp,ERROR);
1107 return NIL;
1109 if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) {
1110 sprintf (tmp,"Can't rename %.80s: mailbox %.80s already exists",
1111 old,newname);
1112 MM_LOG (tmp,ERROR);
1113 return NIL;
1115 return SAFE_RENAME (dtb,stream,old,newname);
1118 /* Validate mailbox as Modified UTF-7
1119 * Accepts: candidate mailbox name
1120 * Returns: error string if error, NIL if valid
1123 char *mail_utf7_valid (char *mailbox)
1125 char *s;
1126 for (s = mailbox; *s; s++) { /* make sure valid name */
1127 /* reserved for future use with UTF-8 */
1128 if (*s & 0x80) return "mailbox name with 8-bit octet";
1129 /* validate modified UTF-7 */
1130 else if (*s == '&') while (*++s != '-') switch (*s) {
1131 case '\0':
1132 return "unterminated modified UTF-7 name";
1133 case '+': /* valid modified BASE64 */
1134 case ',':
1135 break; /* all OK so far */
1136 default: /* must be alphanumeric */
1137 if (!isalnum (*s)) return "invalid modified UTF-7 name";
1138 break;
1141 return NIL; /* all OK */
1144 /* Mail status of mailbox
1145 * Accepts: mail stream if open on this mailbox
1146 * mailbox name
1147 * status flags
1148 * Returns: T on success, NIL on failure
1151 long mail_status (MAILSTREAM *stream,char *mbx,long flags)
1153 DRIVER *dtb = mail_valid (stream,mbx,"get status of mailbox");
1154 if (!dtb) return NIL; /* only if valid */
1155 if (stream && ((dtb != stream->dtb) ||
1156 ((dtb->flags & DR_LOCAL) && strcmp (mbx,stream->mailbox) &&
1157 strcmp (mbx,stream->original_mailbox))))
1158 stream = NIL; /* stream not suitable */
1159 return SAFE_STATUS (dtb,stream,mbx,flags);
1163 /* Mail status of mailbox default handler
1164 * Accepts: mail stream
1165 * mailbox name
1166 * status flags
1167 * Returns: T on success, NIL on failure
1170 long mail_status_default (MAILSTREAM *stream,char *mbx,long flags)
1172 MAILSTATUS status;
1173 unsigned long i;
1174 MAILSTREAM *tstream = NIL;
1175 /* make temporary stream (unless this mbx) */
1176 if (!stream && !(stream = tstream =
1177 mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
1178 status.flags = flags; /* return status values */
1179 status.messages = stream->nmsgs;
1180 status.recent = stream->recent;
1181 if (flags & SA_UNSEEN) /* must search to get unseen messages */
1182 for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
1183 if (!mail_elt (stream,i)->seen) status.unseen++;
1184 status.uidnext = stream->uid_last + 1;
1185 status.uidvalidity = stream->uid_validity;
1186 MM_STATUS(stream,mbx,&status);/* pass status to main program */
1187 if (tstream) mail_close (tstream);
1188 return T; /* success */
1191 /* Mail open
1192 * Accepts: candidate stream for recycling
1193 * mailbox name
1194 * open options
1195 * Returns: stream to use on success, NIL on failure
1198 MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options)
1200 int i;
1201 char c,*s,tmp[MAILTMPLEN];
1202 NETMBX mb;
1203 DRIVER *d;
1204 switch (name[0]) { /* see if special handling */
1205 case '#': /* possible special hacks */
1206 if (((name[1] == 'M') || (name[1] == 'm')) &&
1207 ((name[2] == 'O') || (name[2] == 'o')) &&
1208 ((name[3] == 'V') || (name[3] == 'v')) &&
1209 ((name[4] == 'E') || (name[4] == 'e')) && (c = name[5]) &&
1210 (s = strchr (name+6,c)) && (i = s - (name + 6)) && (i < MAILTMPLEN)) {
1211 if (stream = mail_open (stream,s+1,options)) {
1212 strncpy (tmp,name+6,i); /* copy snarf mailbox name */
1213 tmp[i] = '\0'; /* tie off name */
1214 mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
1215 stream->snarf.options = options;
1216 mail_ping (stream); /* do initial snarf */
1217 /* punt if can't do initial snarf */
1218 if (!stream->snarf.time) stream = mail_close (stream);
1220 return stream;
1222 /* special POP hack */
1223 else if (((name[1] == 'P') || (name[1] == 'p')) &&
1224 ((name[2] == 'O') || (name[2] == 'o')) &&
1225 ((name[3] == 'P') || (name[3] == 'p')) &&
1226 mail_valid_net_parse_work (name+4,&mb,"pop3") &&
1227 !strcmp (mb.service,"pop3") && !mb.anoflag && !mb.readonlyflag) {
1228 if (stream = mail_open (stream,mb.mailbox,options)) {
1229 sprintf (tmp,"{%.255s",mb.host);
1230 if (mb.port) sprintf (tmp + strlen (tmp),":%lu",mb.port);
1231 if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=%.64s",mb.user);
1232 if (mb.dbgflag) strcat (tmp,"/debug");
1233 if (mb.secflag) strcat (tmp,"/secure");
1234 if (mb.tlsflag) strcat (tmp,"/tls");
1235 if (mb.notlsflag) strcat (tmp,"/notls");
1236 if (mb.sslflag) strcat (tmp,"/ssl");
1237 if (mb.tls1_1flag) strcat (tmp,"/tls1_1");
1238 if (mb.tls1_2flag) strcat (tmp,"/tls1_2");
1239 if (mb.dtls1flag) strcat (tmp,"/dtls1");
1240 if (mb.trysslflag) strcat (tmp,"/tryssl");
1241 if (mb.novalidate) strcat (tmp,"/novalidate-cert");
1242 strcat (tmp,"/pop3/loser}");
1243 mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
1244 mail_ping (stream); /* do initial snarf */
1246 return stream; /* return local mailbox stream */
1249 else if ((options & OP_PROTOTYPE) &&
1250 ((name[1] == 'D') || (name[1] == 'd')) &&
1251 ((name[2] == 'R') || (name[2] == 'r')) &&
1252 ((name[3] == 'I') || (name[3] == 'i')) &&
1253 ((name[4] == 'V') || (name[4] == 'v')) &&
1254 ((name[5] == 'E') || (name[5] == 'e')) &&
1255 ((name[6] == 'R') || (name[6] == 'r')) && (name[7] == '.')) {
1256 sprintf (tmp,"%.80s",name+8);
1257 /* tie off name at likely delimiter */
1258 if (s = strpbrk (tmp,"/\\:")) *s++ = '\0';
1259 else {
1260 sprintf (tmp,"Can't resolve mailbox %.80s: bad driver syntax",name);
1261 MM_LOG (tmp,ERROR);
1262 return mail_close (stream);
1264 for (d = maildrivers; d && compare_cstring (d->name,tmp); d = d->next);
1265 if (d) return (*d->open) (NIL);
1266 sprintf (tmp,"Can't resolve mailbox %.80s: unknown driver",name);
1267 MM_LOG (tmp,ERROR);
1268 return mail_close (stream);
1270 /* fall through to default case */
1271 default: /* not special hack (but could be # name */
1272 d = mail_valid (NIL,name,(options & OP_SILENT) ?
1273 (char *) NIL : "open mailbox");
1275 return d ? mail_open_work (d,stream,name,options) : stream;
1278 /* Mail open worker routine
1279 * Accepts: factory
1280 * candidate stream for recycling
1281 * mailbox name
1282 * open options
1283 * Returns: stream to use on success, NIL on failure
1286 MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name,
1287 long options)
1289 int i;
1290 char tmp[MAILTMPLEN];
1291 NETMBX mb;
1292 if (options & OP_PROTOTYPE) return (*d->open) (NIL);
1293 /* name is copied here in case the caller does a re-open using
1294 * stream->mailbox or stream->original_mailbox as the argument.
1296 name = cpystr (name); /* make copy of name */
1297 if (stream) { /* recycling requested? */
1298 if ((stream->dtb == d) && (d->flags & DR_RECYCLE) &&
1299 ((d->flags & DR_HALFOPEN) || !(options & OP_HALFOPEN)) &&
1300 mail_usable_network_stream (stream,name)) {
1301 /* yes, checkpoint if needed */
1302 if (d->flags & DR_XPOINT) mail_check (stream);
1303 mail_free_cache (stream); /* clean up stream */
1304 if (stream->mailbox) fs_give ((void **) &stream->mailbox);
1305 if (stream->original_mailbox)
1306 fs_give ((void **) &stream->original_mailbox);
1307 /* flush user flags */
1308 for (i = 0; i < NUSERFLAGS; i++)
1309 if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
1311 else { /* stream not recycleable, babble if net */
1312 if (!stream->silent && stream->dtb && !(stream->dtb->flags&DR_LOCAL) &&
1313 mail_valid_net_parse (stream->mailbox,&mb)) {
1314 sprintf (tmp,"Closing connection to %.80s",mb.host);
1315 MM_LOG (tmp,(long) NIL);
1317 /* flush the old stream */
1318 stream = mail_close (stream);
1321 /* check if driver does not support halfopen */
1322 else if ((options & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)) {
1323 fs_give ((void **) &name);
1324 return NIL;
1327 /* instantiate new stream if not recycling */
1328 if (!stream) (*mailcache) (stream = (MAILSTREAM *)
1329 memset (fs_get (sizeof (MAILSTREAM)),0,
1330 sizeof (MAILSTREAM)),(long) 0,CH_INIT);
1331 stream->dtb = d; /* set dispatch */
1332 /* set mailbox name */
1333 stream->mailbox = cpystr (stream->original_mailbox = name);
1334 /* initialize stream flags */
1335 stream->inbox = stream->lock = NIL;
1336 stream->debug = (options & OP_DEBUG) ? T : NIL;
1337 stream->rdonly = (options & OP_READONLY) ? T : NIL;
1338 stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL;
1339 stream->scache = (options & OP_SHORTCACHE) ? T : NIL;
1340 stream->silent = (options & OP_SILENT) ? T : NIL;
1341 stream->halfopen = (options & OP_HALFOPEN) ? T : NIL;
1342 stream->secure = (options & OP_SECURE) ? T : NIL;
1343 stream->tryssl = (options & OP_TRYSSL) ? T : NIL;
1344 stream->mulnewsrc = (options & OP_MULNEWSRC) ? T : NIL;
1345 stream->nokod = (options & OP_NOKOD) ? T : NIL;
1346 stream->sniff = (options & OP_SNIFF) ? T : NIL;
1347 stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
1348 stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
1349 stream->uid_nosticky = (d->flags & DR_NOSTICKY) ? T : NIL;
1350 stream->uid_last = 0; /* default UID validity */
1351 stream->uid_validity = (unsigned long) time (0);
1352 /* have driver open, flush if failed */
1353 return ((*d->open) (stream)) ? stream : mail_close (stream);
1356 /* Mail close
1357 * Accepts: mail stream
1358 * close options
1359 * Returns: NIL, always
1362 MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options)
1364 int i;
1365 if (stream) { /* make sure argument given */
1366 /* do the driver's close action */
1367 if (stream->dtb) (*stream->dtb->close) (stream,options);
1368 stream->dtb = NIL; /* resign driver */
1369 if (stream->mailbox) fs_give ((void **) &stream->mailbox);
1370 if (stream->original_mailbox)
1371 fs_give ((void **) &stream->original_mailbox);
1372 if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
1373 stream->sequence++; /* invalidate sequence */
1374 /* flush user flags */
1375 for (i = 0; i < NUSERFLAGS; i++)
1376 if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
1377 mail_free_cache (stream); /* finally free the stream's storage */
1378 if (mailfreestreamsparep && stream->sparep)
1379 (*mailfreestreamsparep) (&stream->sparep);
1380 if (!stream->use) fs_give ((void **) &stream);
1382 return NIL;
1385 /* Mail make handle
1386 * Accepts: mail stream
1387 * Returns: handle
1389 * Handles provide a way to have multiple pointers to a stream yet allow the
1390 * stream's owner to nuke it or recycle it.
1393 MAILHANDLE *mail_makehandle (MAILSTREAM *stream)
1395 MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
1396 handle->stream = stream; /* copy stream */
1397 /* and its sequence */
1398 handle->sequence = stream->sequence;
1399 stream->use++; /* let stream know another handle exists */
1400 return handle;
1404 /* Mail release handle
1405 * Accepts: Mail handle
1408 void mail_free_handle (MAILHANDLE **handle)
1410 MAILSTREAM *s;
1411 if (*handle) { /* only free if exists */
1412 /* resign stream, flush unreferenced zombies */
1413 if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
1414 fs_give ((void **) handle); /* now flush the handle */
1419 /* Mail get stream handle
1420 * Accepts: Mail handle
1421 * Returns: mail stream or NIL if stream gone
1424 MAILSTREAM *mail_stream (MAILHANDLE *handle)
1426 MAILSTREAM *s = handle->stream;
1427 return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
1430 /* Mail fetch cache element
1431 * Accepts: mail stream
1432 * message # to fetch
1433 * Returns: cache element of this message
1434 * Can also be used to create cache elements for new messages.
1437 MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno)
1439 if (msgno < 1 || msgno > stream->nmsgs) {
1440 char tmp[MAILTMPLEN];
1441 sprintf (tmp,"Bad msgno %lu in mail_elt, nmsgs = %lu, mbx=%.80s",
1442 msgno,stream->nmsgs,stream->mailbox ? stream->mailbox : "???");
1443 fatal (tmp);
1445 return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT);
1449 /* Mail fetch fast information
1450 * Accepts: mail stream
1451 * sequence
1452 * option flags
1454 * Generally, mail_fetch_structure is preferred
1457 void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags)
1459 /* do the driver's action */
1460 if (stream->dtb && stream->dtb->fast)
1461 (*stream->dtb->fast) (stream,sequence,flags);
1465 /* Mail fetch flags
1466 * Accepts: mail stream
1467 * sequence
1468 * option flags
1471 void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags)
1473 /* do the driver's action */
1474 if (stream->dtb && stream->dtb->msgflags)
1475 (*stream->dtb->msgflags) (stream,sequence,flags);
1478 /* Mail fetch message overview
1479 * Accepts: mail stream
1480 * UID sequence to fetch
1481 * pointer to overview return function
1484 void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
1486 if (stream->dtb && mail_uid_sequence (stream,sequence) &&
1487 !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
1488 mail_ping (stream))
1489 mail_fetch_overview_default (stream,ofn);
1493 /* Mail fetch message overview using sequence numbers instead of UIDs
1494 * Accepts: mail stream
1495 * sequence to fetch
1496 * pointer to overview return function
1499 void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence,
1500 overview_t ofn)
1502 if (stream->dtb && mail_sequence (stream,sequence) &&
1503 !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
1504 mail_ping (stream))
1505 mail_fetch_overview_default (stream,ofn);
1509 /* Mail fetch message overview default handler
1510 * Accepts: mail stream with sequence bits lit
1511 * pointer to overview return function
1514 void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn)
1516 MESSAGECACHE *elt;
1517 ENVELOPE *env;
1518 OVERVIEW ov;
1519 unsigned long i;
1520 ov.optional.lines = 0;
1521 ov.optional.xref = NIL;
1522 for (i = 1; i <= stream->nmsgs; i++)
1523 if (((elt = mail_elt (stream,i))->sequence) &&
1524 (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
1525 ov.subject = env->subject;
1526 ov.from = env->from;
1527 ov.date = env->date;
1528 ov.message_id = env->message_id;
1529 ov.references = env->references;
1530 ov.optional.octets = elt->rfc822_size;
1531 (*ofn) (stream,mail_uid (stream,i),&ov,i);
1535 /* Mail fetch message structure
1536 * Accepts: mail stream
1537 * message # to fetch
1538 * pointer to return body
1539 * option flags
1540 * Returns: envelope of this message, body returned in body value
1542 * Fetches the "fast" information as well
1545 ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno,
1546 BODY **body,long flags)
1548 ENVELOPE **env;
1549 BODY **b;
1550 MESSAGECACHE *elt;
1551 char c,*s,*hdr;
1552 unsigned long hdrsize;
1553 STRING bs;
1554 /* do the driver's action if specified */
1555 if (stream->dtb && stream->dtb->structure)
1556 return (*stream->dtb->structure) (stream,msgno,body,flags);
1557 if (flags & FT_UID) { /* UID form of call */
1558 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1559 else return NIL; /* must get UID/msgno map first */
1561 elt = mail_elt (stream,msgno);/* get elt for real message number */
1562 if (stream->scache) { /* short caching */
1563 if (msgno != stream->msgno){/* garbage collect if not same message */
1564 mail_gc (stream,GC_ENV | GC_TEXTS);
1565 stream->msgno = msgno; /* this is the current message now */
1567 env = &stream->env; /* get pointers to envelope and body */
1568 b = &stream->body;
1570 else { /* get pointers to elt envelope and body */
1571 env = &elt->private.msg.env;
1572 b = &elt->private.msg.body;
1575 if (stream->dtb && ((body && !*b) || !*env || (*env)->incomplete)) {
1576 mail_free_envelope (env); /* flush old envelope and body */
1577 mail_free_body (b);
1578 /* see if need to fetch the whole thing */
1579 if (body || !elt->rfc822_size) {
1580 s = (*stream->dtb->header) (stream,msgno,&hdrsize,flags & ~FT_INTERNAL);
1581 /* make copy in case body fetch smashes it */
1582 hdr = (char *) memcpy (fs_get ((size_t) hdrsize+1),s,(size_t) hdrsize);
1583 hdr[hdrsize] = '\0'; /* tie off header */
1584 (*stream->dtb->text) (stream,msgno,&bs,(flags & ~FT_INTERNAL) | FT_PEEK);
1585 if (!elt->rfc822_size) elt->rfc822_size = hdrsize + SIZE (&bs);
1586 if (body) /* only parse body if requested */
1587 rfc822_parse_msg (env,b,hdr,hdrsize,&bs,BADHOST,stream->dtb->flags);
1588 else
1589 rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
1590 fs_give ((void **) &hdr); /* flush header */
1592 else { /* can save memory doing it this way */
1593 hdr = (*stream->dtb->header) (stream,msgno,&hdrsize,flags | FT_INTERNAL);
1594 if (hdrsize) { /* in case null header */
1595 c = hdr[hdrsize]; /* preserve what's there */
1596 hdr[hdrsize] = '\0'; /* tie off header */
1597 rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
1598 hdr[hdrsize] = c; /* restore in case cached data */
1600 else *env = mail_newenvelope ();
1603 /* if need date, have date in envelope? */
1604 if (!elt->day && *env && (*env)->date) mail_parse_date (elt,(*env)->date);
1605 /* sigh, fill in bogus default */
1606 if (!elt->day) elt->day = elt->month = 1;
1607 if (body) *body = *b; /* return the body */
1608 return *env; /* return the envelope */
1611 /* Mail mark single message (internal use only)
1612 * Accepts: mail stream
1613 * elt to mark
1614 * fetch flags
1617 static void markseen (MAILSTREAM *stream,MESSAGECACHE *elt,long flags)
1619 unsigned long i;
1620 char sequence[20];
1621 MESSAGECACHE *e;
1622 /* non-peeking and needs to set \Seen? */
1623 if (!(flags & FT_PEEK) && !elt->seen) {
1624 if (stream->dtb->flagmsg){ /* driver wants per-message call? */
1625 elt->valid = NIL; /* do pre-alteration driver call */
1626 (*stream->dtb->flagmsg) (stream,elt);
1627 /* set seen, do post-alteration driver call */
1628 elt->seen = elt->valid = T;
1629 (*stream->dtb->flagmsg) (stream,elt);
1631 if (stream->dtb->flag) { /* driver wants one-time call? */
1632 /* better safe than sorry, save seq bits */
1633 for (i = 1; i <= stream->nmsgs; i++) {
1634 e = mail_elt (stream,i);
1635 e->private.sequence = e->sequence;
1637 /* call driver to set the message */
1638 sprintf (sequence,"%lu",elt->msgno);
1639 (*stream->dtb->flag) (stream,sequence,"\\Seen",ST_SET);
1640 /* restore sequence bits */
1641 for (i = 1; i <= stream->nmsgs; i++) {
1642 e = mail_elt (stream,i);
1643 e->sequence = e->private.sequence;
1646 /* notify mail program of flag change */
1647 MM_FLAGS (stream,elt->msgno);
1651 /* Mail fetch message
1652 * Accepts: mail stream
1653 * message # to fetch
1654 * pointer to returned length
1655 * flags
1656 * Returns: message text
1659 char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno,
1660 unsigned long *len,long flags)
1662 GETS_DATA md;
1663 SIZEDTEXT *t;
1664 STRING bs;
1665 MESSAGECACHE *elt;
1666 char *s,*u;
1667 unsigned long i,j;
1668 if (len) *len = 0; /* default return size */
1669 if (flags & FT_UID) { /* UID form of call */
1670 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1671 else return ""; /* must get UID/msgno map first */
1673 /* initialize message data identifier */
1674 INIT_GETS (md,stream,msgno,"",0,0);
1675 /* is data already cached? */
1676 if ((t = &(elt = mail_elt (stream,msgno))->private.msg.full.text)->data) {
1677 markseen (stream,elt,flags);/* mark message seen */
1678 return mail_fetch_text_return (&md,t,len);
1680 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1681 if (stream->dtb->msgdata) return
1682 ((*stream->dtb->msgdata) (stream,msgno,"",0,0,NIL,flags) && t->data) ?
1683 mail_fetch_text_return (&md,t,len) : "";
1684 /* ugh, have to do this the crufty way */
1685 u = mail_fetch_header (stream,msgno,NIL,NIL,&i,flags);
1686 /* copy in case text method stomps on it */
1687 s = (char *) memcpy (fs_get ((size_t) i),u,(size_t) i);
1688 if ((*stream->dtb->text) (stream,msgno,&bs,flags)) {
1689 t = &stream->text; /* build combined copy */
1690 if (t->data) fs_give ((void **) &t->data);
1691 t->data = (unsigned char *) fs_get ((t->size = i + SIZE (&bs)) + 1);
1692 if (!elt->rfc822_size) elt->rfc822_size = t->size;
1693 else if (elt->rfc822_size != t->size) {
1694 char tmp[MAILTMPLEN];
1695 sprintf (tmp,"Calculated RFC822.SIZE (%lu) != reported size (%lu)",
1696 t->size,elt->rfc822_size);
1697 mm_log (tmp,WARN); /* bug trap */
1699 memcpy (t->data,s,(size_t) i);
1700 for (u = (char *) t->data + i, j = SIZE (&bs); j;) {
1701 memcpy (u,bs.curpos,bs.cursize);
1702 u += bs.cursize; /* update text */
1703 j -= bs.cursize;
1704 bs.curpos += (bs.cursize -1);
1705 bs.cursize = 0;
1706 (*bs.dtb->next) (&bs); /* advance to next buffer's worth */
1708 *u = '\0'; /* tie off data */
1709 u = mail_fetch_text_return (&md,t,len);
1711 else u = "";
1712 fs_give ((void **) &s); /* finished with copy of header */
1713 return u;
1716 /* Mail fetch message header
1717 * Accepts: mail stream
1718 * message # to fetch
1719 * MIME section specifier (#.#.#...#)
1720 * list of lines to fetch
1721 * pointer to returned length
1722 * flags
1723 * Returns: message header in RFC822 format
1725 * Note: never calls a mailgets routine
1728 char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section,
1729 STRINGLIST *lines,unsigned long *len,long flags)
1731 STRING bs;
1732 BODY *b = NIL;
1733 SIZEDTEXT *t = NIL,rt;
1734 MESSAGE *m = NIL;
1735 MESSAGECACHE *elt;
1736 char tmp[MAILTMPLEN];
1737 if (len) *len = 0; /* default return size */
1738 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1739 if (flags & FT_UID) { /* UID form of call */
1740 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1741 else return ""; /* must get UID/msgno map first */
1743 elt = mail_elt (stream,msgno);/* get cache data */
1744 if (section && *section) { /* nested body header wanted? */
1745 if (!((b = mail_body (stream,msgno,section)) &&
1746 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
1747 return ""; /* lose if no body or not MESSAGE/RFC822 */
1748 m = b->nested.msg; /* point to nested message */
1750 /* else top-level message header wanted */
1751 else m = &elt->private.msg;
1752 if (m->header.text.data && mail_match_lines (lines,m->lines,flags)) {
1753 if (lines) textcpy (t = &stream->text,&m->header.text);
1754 else t = &m->header.text; /* in cache, and cache is valid */
1755 markseen (stream,elt,flags);/* mark message seen */
1758 else if (stream->dtb) { /* not in cache, has live driver? */
1759 if (stream->dtb->msgdata) { /* has driver section fetch? */
1760 /* build driver section specifier */
1761 if (section && *section) sprintf (tmp,"%s.HEADER",section);
1762 else strcpy (tmp,"HEADER");
1763 if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,lines,flags)) {
1764 t = &m->header.text; /* fetch data */
1765 /* don't need to postprocess lines */
1766 if (m->lines) lines = NIL;
1767 else if (lines) textcpy (t = &stream->text,&m->header.text);
1770 else if (b) { /* nested body wanted? */
1771 if (stream->private.search.text) {
1772 rt.data = (unsigned char *) stream->private.search.text +
1773 b->nested.msg->header.offset;
1774 rt.size = b->nested.msg->header.text.size;
1775 t = &rt;
1777 else if ((*stream->dtb->text) (stream,msgno,&bs,flags & ~FT_INTERNAL)) {
1778 if ((bs.dtb->next == mail_string_next) && !lines) {
1779 rt.data = (unsigned char *) bs.curpos + b->nested.msg->header.offset;
1780 rt.size = b->nested.msg->header.text.size;
1781 if (stream->private.search.string)
1782 stream->private.search.text = bs.curpos;
1783 t = &rt; /* special hack to avoid extra copy */
1785 else textcpyoffstring (t = &stream->text,&bs,
1786 b->nested.msg->header.offset,
1787 b->nested.msg->header.text.size);
1790 else { /* top-level header fetch */
1791 /* mark message seen */
1792 markseen (stream,elt,flags);
1793 if (rt.data = (unsigned char *)
1794 (*stream->dtb->header) (stream,msgno,&rt.size,flags)) {
1795 /* make a safe copy if need to filter */
1796 if (lines) textcpy (t = &stream->text,&rt);
1797 else t = &rt; /* top level header */
1801 if (!t || !t->data) return "";/* error if no string */
1802 /* filter headers if requested */
1803 if (lines) t->size = mail_filter ((char *) t->data,t->size,lines,flags);
1804 if (len) *len = t->size; /* return size if requested */
1805 return (char *) t->data; /* and text */
1808 /* Mail fetch message text
1809 * Accepts: mail stream
1810 * message # to fetch
1811 * MIME section specifier (#.#.#...#)
1812 * pointer to returned length
1813 * flags
1814 * Returns: message text
1817 char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section,
1818 unsigned long *len,long flags)
1820 GETS_DATA md;
1821 PARTTEXT *p;
1822 STRING bs;
1823 MESSAGECACHE *elt;
1824 BODY *b = NIL;
1825 char tmp[MAILTMPLEN];
1826 unsigned long i;
1827 if (len) *len = 0; /* default return size */
1828 memset (&stream->private.string,NIL,sizeof (STRING));
1829 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1830 if (flags & FT_UID) { /* UID form of call */
1831 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1832 else return ""; /* must get UID/msgno map first */
1834 elt = mail_elt (stream,msgno);/* get cache data */
1835 if (section && *section) { /* nested body text wanted? */
1836 if (!((b = mail_body (stream,msgno,section)) &&
1837 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
1838 return ""; /* lose if no body or not MESSAGE/RFC822 */
1839 p = &b->nested.msg->text; /* point at nested message */
1840 /* build IMAP-format section specifier */
1841 sprintf (tmp,"%s.TEXT",section);
1842 flags &= ~FT_INTERNAL; /* can't win with this set */
1844 else { /* top-level message text wanted */
1845 p = &elt->private.msg.text;
1846 strcpy (tmp,"TEXT");
1848 /* initialize message data identifier */
1849 INIT_GETS (md,stream,msgno,section,0,0);
1850 if (p->text.data) { /* is data already cached? */
1851 markseen (stream,elt,flags);/* mark message seen */
1852 return mail_fetch_text_return (&md,&p->text,len);
1854 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1855 if (stream->dtb->msgdata) return
1856 ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && p->text.data)?
1857 mail_fetch_text_return (&md,&p->text,len) : "";
1858 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return "";
1859 if (section && *section) { /* nested is more complex */
1860 SETPOS (&bs,p->offset);
1861 i = p->text.size; /* just want this much */
1863 else i = SIZE (&bs); /* want entire text */
1864 return mail_fetch_string_return (&md,&bs,i,len,flags);
1867 /* Mail fetch message body part MIME headers
1868 * Accepts: mail stream
1869 * message # to fetch
1870 * MIME section specifier (#.#.#...#)
1871 * pointer to returned length
1872 * flags
1873 * Returns: message text
1876 char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section,
1877 unsigned long *len,long flags)
1879 PARTTEXT *p;
1880 STRING bs;
1881 BODY *b;
1882 char tmp[MAILTMPLEN];
1883 if (len) *len = 0; /* default return size */
1884 if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
1885 if (flags & FT_UID) { /* UID form of call */
1886 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1887 else return ""; /* must get UID/msgno map first */
1889 flags &= ~FT_INTERNAL; /* can't win with this set */
1890 if (!(section && *section && (b = mail_body (stream,msgno,section))))
1891 return ""; /* not valid section */
1892 /* in cache? */
1893 if ((p = &b->mime)->text.data) {
1894 /* mark message seen */
1895 markseen (stream,mail_elt (stream,msgno),flags);
1896 if (len) *len = p->text.size;
1897 return (char *) p->text.data;
1899 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1900 if (stream->dtb->msgdata) { /* has driver fetch? */
1901 /* build driver section specifier */
1902 sprintf (tmp,"%s.MIME",section);
1903 if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) &&
1904 p->text.data) {
1905 if (len) *len = p->text.size;
1906 return (char *) p->text.data;
1908 else return "";
1910 if (len) *len = b->mime.text.size;
1911 if (!b->mime.text.size) { /* empty MIME header -- mark seen anyway */
1912 markseen (stream,mail_elt (stream,msgno),flags);
1913 return "";
1915 /* have to get it from offset */
1916 if (stream->private.search.text)
1917 return stream->private.search.text + b->mime.offset;
1918 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
1919 if (len) *len = 0;
1920 return "";
1922 if (bs.dtb->next == mail_string_next) {
1923 if (stream->private.search.string) stream->private.search.text = bs.curpos;
1924 return bs.curpos + b->mime.offset;
1926 return textcpyoffstring (&stream->text,&bs,b->mime.offset,b->mime.text.size);
1929 /* Mail fetch message body part
1930 * Accepts: mail stream
1931 * message # to fetch
1932 * MIME section specifier (#.#.#...#)
1933 * pointer to returned length
1934 * flags
1935 * Returns: message body
1938 char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section,
1939 unsigned long *len,long flags)
1941 GETS_DATA md;
1942 PARTTEXT *p;
1943 STRING bs;
1944 BODY *b;
1945 SIZEDTEXT *t;
1946 char *s,tmp[MAILTMPLEN];
1947 memset (&stream->private.string,NIL,sizeof (STRING));
1948 if (!(section && *section)) /* top-level text wanted? */
1949 return mail_fetch_message (stream,msgno,len,flags);
1950 else if (strlen (section) > (MAILTMPLEN - 20)) return "";
1951 flags &= ~FT_INTERNAL; /* can't win with this set */
1952 /* initialize message data identifier */
1953 INIT_GETS (md,stream,msgno,section,0,0);
1954 /* kludge for old section 0 header */
1955 if (!strcmp (s = strcpy (tmp,section),"0") ||
1956 ((s = strstr (tmp,".0")) && !s[2])) {
1957 SIZEDTEXT ht;
1958 *s = '\0'; /* tie off section */
1959 /* this silly way so it does mailgets */
1960 ht.data = (unsigned char *) mail_fetch_header (stream,msgno,
1961 tmp[0] ? tmp : NIL,NIL,
1962 &ht.size,flags);
1963 /* may have UIDs here */
1964 md.flags = (flags & FT_UID) ? MG_UID : NIL;
1965 return mail_fetch_text_return (&md,&ht,len);
1967 if (len) *len = 0; /* default return size */
1968 if (flags & FT_UID) { /* UID form of call */
1969 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
1970 else return ""; /* must get UID/msgno map first */
1972 /* must have body */
1973 if (!(b = mail_body (stream,msgno,section))) return "";
1974 /* have cached text? */
1975 if ((t = &(p = &b->contents)->text)->data) {
1976 /* mark message seen */
1977 markseen (stream,mail_elt (stream,msgno),flags);
1978 return mail_fetch_text_return (&md,t,len);
1980 if (!stream->dtb) return ""; /* not in cache, must have live driver */
1981 if (stream->dtb->msgdata) return
1982 ((*stream->dtb->msgdata)(stream,msgno,section,0,0,NIL,flags) && t->data) ?
1983 mail_fetch_text_return (&md,t,len) : "";
1984 if (len) *len = t->size;
1985 if (!t->size) { /* empty body part -- mark seen anyway */
1986 markseen (stream,mail_elt (stream,msgno),flags);
1987 return "";
1989 /* copy body from stringstruct offset */
1990 if (stream->private.search.text)
1991 return stream->private.search.text + p->offset;
1992 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
1993 if (len) *len = 0;
1994 return "";
1996 if (bs.dtb->next == mail_string_next) {
1997 if (stream->private.search.string) stream->private.search.text = bs.curpos;
1998 return bs.curpos + p->offset;
2000 SETPOS (&bs,p->offset);
2001 return mail_fetch_string_return (&md,&bs,t->size,len,flags);
2004 /* Mail fetch partial message text
2005 * Accepts: mail stream
2006 * message # to fetch
2007 * MIME section specifier (#.#.#...#)
2008 * offset of first designed byte or 0 to start at beginning
2009 * maximum number of bytes or 0 for all bytes
2010 * flags
2011 * Returns: T if successful, else NIL
2014 long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section,
2015 unsigned long first,unsigned long last,long flags)
2017 GETS_DATA md;
2018 PARTTEXT *p = NIL;
2019 MESSAGECACHE *elt;
2020 STRING bs;
2021 BODY *b;
2022 char tmp[MAILTMPLEN];
2023 unsigned long i;
2024 if (!mailgets) fatal ("mail_partial_text() called without a mailgets!");
2025 if (section && (strlen (section) > (MAILTMPLEN - 20))) return NIL;
2026 if (flags & FT_UID) { /* UID form of call */
2027 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
2028 else return NIL; /* must get UID/msgno map first */
2030 elt = mail_elt (stream,msgno);/* get cache data */
2031 flags &= ~FT_INTERNAL; /* bogus if this is set */
2032 if (section && *section) { /* nested body text wanted? */
2033 if (!((b = mail_body (stream,msgno,section)) &&
2034 (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
2035 return NIL; /* lose if no body or not MESSAGE/RFC822 */
2036 p = &b->nested.msg->text; /* point at nested message */
2037 /* build IMAP-format section specifier */
2038 sprintf (tmp,"%s.TEXT",section);
2040 else { /* else top-level message text wanted */
2041 p = &elt->private.msg.text;
2042 strcpy (tmp,"TEXT");
2045 /* initialize message data identifier */
2046 INIT_GETS (md,stream,msgno,tmp,first,last);
2047 if (p->text.data) { /* is data already cached? */
2048 INIT (&bs,mail_string,p->text.data,i = p->text.size);
2049 markseen (stream,elt,flags);/* mark message seen */
2051 else { /* else get data from driver */
2052 if (!stream->dtb) return NIL;
2053 if (stream->dtb->msgdata) /* driver will handle this */
2054 return (*stream->dtb->msgdata) (stream,msgno,tmp,first,last,NIL,flags);
2055 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
2056 if (section && *section) { /* nexted if more complex */
2057 SETPOS (&bs,p->offset); /* offset stringstruct to data */
2058 i = p->text.size; /* maximum size of data */
2060 else i = SIZE (&bs); /* just want this much */
2062 if (i <= first) i = first = 0;/* first byte is beyond end of text */
2063 /* truncate as needed */
2064 else { /* offset and truncate */
2065 SETPOS (&bs,first + GETPOS (&bs));
2066 i -= first; /* reduced size */
2067 if (last && (i > last)) i = last;
2069 /* do the mailgets thing */
2070 (*mailgets) (mail_read,&bs,i,&md);
2071 return T; /* success */
2074 /* Mail fetch partial message body part
2075 * Accepts: mail stream
2076 * message # to fetch
2077 * MIME section specifier (#.#.#...#)
2078 * offset of first designed byte or 0 to start at beginning
2079 * maximum number of bytes or 0 for all bytes
2080 * flags
2081 * Returns: T if successful, else NIL
2084 long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section,
2085 unsigned long first,unsigned long last,long flags)
2087 GETS_DATA md;
2088 PARTTEXT *p;
2089 STRING bs;
2090 BODY *b;
2091 SIZEDTEXT *t;
2092 unsigned long i;
2093 if (!(section && *section)) /* top-level text wanted? */
2094 return mail_partial_text (stream,msgno,NIL,first,last,flags);
2095 if (!mailgets) fatal ("mail_partial_body() called without a mailgets!");
2096 if (flags & FT_UID) { /* UID form of call */
2097 if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
2098 else return NIL; /* must get UID/msgno map first */
2100 /* must have body */
2101 if (!(b = mail_body (stream,msgno,section))) return NIL;
2102 flags &= ~FT_INTERNAL; /* bogus if this is set */
2104 /* initialize message data identifier */
2105 INIT_GETS (md,stream,msgno,section,first,last);
2106 /* have cached text? */
2107 if ((t = &(p = &b->contents)->text)->data) {
2108 /* mark message seen */
2109 markseen (stream,mail_elt (stream,msgno),flags);
2110 INIT (&bs,mail_string,t->data,i = t->size);
2112 else { /* else get data from driver */
2113 if (!stream->dtb) return NIL;
2114 if (stream->dtb->msgdata) /* driver will handle this */
2115 return (*stream->dtb->msgdata) (stream,msgno,section,first,last,NIL,
2116 flags);
2117 if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
2118 if (section && *section) { /* nexted if more complex */
2119 SETPOS (&bs,p->offset); /* offset stringstruct to data */
2120 i = t->size; /* maximum size of data */
2122 else i = SIZE (&bs); /* just want this much */
2124 if (i <= first) i = first = 0;/* first byte is beyond end of text */
2125 else { /* offset and truncate */
2126 SETPOS (&bs,first + GETPOS (&bs));
2127 i -= first; /* reduced size */
2128 if (last && (i > last)) i = last;
2130 /* do the mailgets thing */
2131 (*mailgets) (mail_read,&bs,i,&md);
2132 return T; /* success */
2135 /* Mail return message text
2136 * Accepts: identifier data
2137 * sized text
2138 * pointer to returned length
2139 * Returns: text
2142 char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len)
2144 STRING bs;
2145 if (len) *len = t->size; /* return size */
2146 if (t->size && mailgets) { /* have to do the mailgets thing? */
2147 /* silly but do it anyway for consistency */
2148 INIT (&bs,mail_string,t->data,t->size);
2149 return (*mailgets) (mail_read,&bs,t->size,md);
2151 return t->size ? (char *) t->data : "";
2155 /* Mail return message string
2156 * Accepts: identifier data
2157 * stringstruct
2158 * text length
2159 * pointer to returned length
2160 * flags
2161 * Returns: text, or NIL if stringstruct returned
2164 char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i,
2165 unsigned long *len,long flags)
2167 char *ret = NIL;
2168 if (len) *len = i; /* return size */
2169 /* return stringstruct hack */
2170 if (flags & FT_RETURNSTRINGSTRUCT) {
2171 memcpy (&md->stream->private.string,bs,sizeof (STRING));
2172 SETPOS (&md->stream->private.string,GETPOS (&md->stream->private.string));
2174 /* have to do the mailgets thing? */
2175 else if (mailgets) ret = (*mailgets) (mail_read,bs,i,md);
2176 /* special hack to avoid extra copy */
2177 else if (bs->dtb->next == mail_string_next) ret = bs->curpos;
2178 /* make string copy in memory */
2179 else ret = textcpyoffstring (&md->stream->text,bs,GETPOS (bs),i);
2180 return ret;
2183 /* Read data from stringstruct
2184 * Accepts: stringstruct
2185 * size of data to read
2186 * buffer to read into
2187 * Returns: T, always, stringstruct updated
2190 long mail_read (void *stream,unsigned long size,char *buffer)
2192 unsigned long i;
2193 STRING *s = (STRING *) stream;
2194 while (size) { /* until satisfied */
2195 memcpy (buffer,s->curpos,i = min (s->cursize,size));
2196 buffer += i; /* update buffer */
2197 size -= i; /* note that we read this much */
2198 s->curpos += --i; /* advance that many spaces minus 1 */
2199 s->cursize -= i;
2200 SNX (s); /* now use SNX to advance the last byte */
2202 return T;
2205 /* Mail fetch UID
2206 * Accepts: mail stream
2207 * message number
2208 * Returns: UID or zero if dead stream
2211 unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno)
2213 unsigned long uid = mail_elt (stream,msgno)->private.uid;
2214 return uid ? uid :
2215 (stream->dtb && stream->dtb->uid) ? (*stream->dtb->uid) (stream,msgno) : 0;
2219 /* Mail fetch msgno from UID
2220 * Accepts: mail stream
2221 * UID
2222 * Returns: msgno or zero if failed
2225 unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid)
2227 unsigned long msgno,delta,first,firstuid,last,lastuid,middle,miduid;
2228 if (stream->dtb) { /* active stream? */
2229 if (stream->dtb->msgno) /* direct way */
2230 return (*stream->dtb->msgno) (stream,uid);
2231 else if (stream->dtb->uid) {/* indirect way */
2232 /* Placeholder for now, since currently there are no drivers which
2233 * have a uid method but not a msgno method
2235 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2236 if ((*stream->dtb->uid) (stream,msgno) == uid) return msgno;
2238 /* binary search since have full map */
2239 else for (first = 1,last = stream->nmsgs, delta = (first <= last) ? 1 : 0;
2240 delta &&
2241 (uid >= (firstuid = mail_elt (stream,first)->private.uid)) &&
2242 (uid <= (lastuid = mail_elt (stream,last)->private.uid));) {
2243 /* done if match at an endpoint */
2244 if (uid == firstuid) return first;
2245 if (uid == lastuid) return last;
2246 /* have anything between endpoints? */
2247 if (delta = ((last - first) / 2)) {
2248 if ((miduid = mail_elt (stream,middle = first + delta)->private.uid)
2249 == uid)
2250 return middle; /* found match in middle */
2251 else if (uid < miduid) last = middle - 1;
2252 else first = middle + 1;
2256 else { /* dead stream, do linear search for UID */
2257 for (msgno = 1; msgno <= stream->nmsgs; msgno++)
2258 if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
2260 return 0; /* didn't find the UID anywhere */
2263 /* Mail fetch From string for menu
2264 * Accepts: destination string
2265 * mail stream
2266 * message # to fetch
2267 * desired string length
2268 * Returns: string of requested length
2271 void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno,
2272 long length)
2274 char *t;
2275 char tmp[MAILTMPLEN];
2276 ENVELOPE *env = mail_fetchenvelope (stream,msgno);
2277 ADDRESS *adr = env ? env->from : NIL;
2278 memset (s,' ',(size_t)length);/* fill it with spaces */
2279 s[length] = '\0'; /* tie off with null */
2280 /* get first from address from envelope */
2281 while (adr && !adr->host) adr = adr->next;
2282 if (adr) { /* if a personal name exists use it */
2283 if (!(t = adr->personal))
2284 sprintf (t = tmp,"%.256s@%.256s",adr->mailbox,adr->host);
2285 memcpy (s,t,(size_t) min (length,(long) strlen (t)));
2290 /* Mail fetch Subject string for menu
2291 * Accepts: destination string
2292 * mail stream
2293 * message # to fetch
2294 * desired string length
2295 * Returns: string of no more than requested length
2298 void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno,
2299 long length)
2301 ENVELOPE *env = mail_fetchenvelope (stream,msgno);
2302 memset (s,'\0',(size_t) length+1);
2303 /* copy subject from envelope */
2304 if (env && env->subject) strncpy (s,env->subject,(size_t) length);
2305 else *s = ' '; /* if no subject then just a space */
2308 /* Mail modify flags
2309 * Accepts: mail stream
2310 * sequence
2311 * flag(s)
2312 * option flags
2315 void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
2317 MESSAGECACHE *elt;
2318 unsigned long i,uf;
2319 long f;
2320 short nf;
2321 if (!stream->dtb) return; /* no-op if no stream */
2322 if ((stream->dtb->flagmsg || !stream->dtb->flag) &&
2323 ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
2324 mail_sequence (stream,sequence)) &&
2325 ((f = mail_parse_flags (stream,flag,&uf)) || uf))
2326 for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
2327 if ((elt = mail_elt (stream,i))->sequence) {
2328 struct { /* old flags */
2329 unsigned int valid : 1;
2330 unsigned int seen : 1;
2331 unsigned int deleted : 1;
2332 unsigned int flagged : 1;
2333 unsigned int answered : 1;
2334 unsigned int draft : 1;
2335 unsigned long user_flags;
2336 } old;
2337 old.valid = elt->valid; old.seen = elt->seen;
2338 old.deleted = elt->deleted; old.flagged = elt->flagged;
2339 old.answered = elt->answered; old.draft = elt->draft;
2340 old.user_flags = elt->user_flags;
2341 elt->valid = NIL; /* prepare for flag alteration */
2342 if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
2343 if (f&fSEEN) elt->seen = nf;
2344 if (f&fDELETED) elt->deleted = nf;
2345 if (f&fFLAGGED) elt->flagged = nf;
2346 if (f&fANSWERED) elt->answered = nf;
2347 if (f&fDRAFT) elt->draft = nf;
2348 /* user flags */
2349 if (flags & ST_SET) elt->user_flags |= uf;
2350 else elt->user_flags &= ~uf;
2351 elt->valid = T; /* flags now altered */
2352 if ((old.valid != elt->valid) || (old.seen != elt->seen) ||
2353 (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
2354 (old.answered != elt->answered) || (old.draft != elt->draft) ||
2355 (old.user_flags != elt->user_flags))
2356 MM_FLAGS (stream,elt->msgno);
2357 if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
2359 /* call driver once */
2360 if (stream->dtb->flag) (*stream->dtb->flag) (stream,sequence,flag,flags);
2363 /* Mail search for messages
2364 * Accepts: mail stream
2365 * character set
2366 * search program
2367 * option flags
2368 * Returns: T if successful, NIL if dead stream, NIL searchpgm or bad charset
2371 long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
2372 long flags)
2374 unsigned long i;
2375 long ret = NIL;
2376 if (!(flags & SE_RETAIN)) /* clear search vector unless retaining */
2377 for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
2378 if (pgm && stream->dtb) /* must have a search program and driver */
2379 ret = (*(stream->dtb->search ? stream->dtb->search : mail_search_default))
2380 (stream,charset,pgm,flags);
2381 /* flush search program if requested */
2382 if (flags & SE_FREE) mail_free_searchpgm (&pgm);
2383 return ret;
2387 /* Mail search for messages default handler
2388 * Accepts: mail stream
2389 * character set
2390 * search program
2391 * option flags
2392 * Returns: T if successful, NIL if bad charset
2395 long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
2396 long flags)
2398 unsigned long i;
2399 char *msg;
2400 /* make sure that charset is good */
2401 if (msg = utf8_badcharset (charset)) {
2402 MM_LOG (msg,ERROR); /* output error */
2403 fs_give ((void **) &msg);
2404 return NIL;
2406 utf8_searchpgm (pgm,charset);
2407 for (i = 1; i <= stream->nmsgs; ++i)
2408 if (mail_search_msg (stream,i,NIL,pgm)) {
2409 if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
2410 else { /* mark as searched, notify mail program */
2411 mail_elt (stream,i)->searched = T;
2412 if (!stream->silent) mm_searched (stream,i);
2415 return LONGT; /* search completed */
2418 /* Mail ping mailbox
2419 * Accepts: mail stream
2420 * Returns: stream if still open else NIL
2423 long mail_ping (MAILSTREAM *stream)
2425 unsigned long i,n,uf,len;
2426 char *s,*f,tmp[MAILTMPLEN],flags[MAILTMPLEN];
2427 MAILSTREAM *snarf;
2428 MESSAGECACHE *elt;
2429 STRING bs;
2430 long ret;
2431 /* do driver action */
2432 if ((ret = ((stream && stream->dtb) ? (stream->dtb->ping) (stream) : NIL)) &&
2433 stream->snarf.name && /* time to snarf? */
2434 /* prohibit faster than once/min */
2435 (time (0) > (time_t) (stream->snarf.time + min(60,mailsnarfinterval))) &&
2436 (snarf = mail_open (NIL,stream->snarf.name,
2437 stream->snarf.options | OP_SILENT))) {
2438 if ((n = snarf->nmsgs) && /* yes, have messages to snarf? */
2439 mail_search_full (snarf,NIL,mail_criteria ("UNDELETED"),SE_FREE)) {
2440 for (i = 1; ret && (i <= n); i++) /* for each message */
2441 if ((elt = mail_elt (snarf,i))->searched &&
2442 (s = mail_fetch_message (snarf,i,&len,FT_PEEK)) && len) {
2443 INIT (&bs,mail_string,s,len);
2444 if (mailsnarfpreserve) {
2445 /* yes, make sure have fast data */
2446 if (!elt->valid || !elt->day) {
2447 sprintf (tmp,"%lu",n);
2448 mail_fetch_fast (snarf,tmp,NIL);
2450 /* initialize flag string */
2451 memset (flags,0,MAILTMPLEN);
2452 /* output system flags except \Deleted */
2453 if (elt->seen) strcat (flags," \\Seen");
2454 if (elt->flagged) strcat (flags," \\Flagged");
2455 if (elt->answered) strcat (flags," \\Answered");
2456 if (elt->draft) strcat (flags," \\Draft");
2457 /* any user flags? */
2458 for (uf = elt->user_flags,s = flags + strlen (flags);
2459 uf && (f = stream->user_flags[find_rightmost_bit (&uf)]) &&
2460 ((MAILTMPLEN - (s - tmp)) > (long) (2 + strlen (f)));
2461 s += strlen (s)) sprintf (s," %s",f);
2462 ret = mail_append_full (stream,stream->mailbox,flags + 1,
2463 mail_date (tmp,elt),&bs);
2465 else ret = mail_append (stream,stream->mailbox,&bs);
2467 if (ret) { /* did snarf succeed? */
2468 /* driver has per-message (or no) flag call */
2469 if (snarf->dtb->flagmsg || !snarf->dtb->flag) {
2470 elt->valid = NIL; /* prepare for flag alteration */
2471 if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
2472 /* flags now altered */
2473 elt->deleted = elt->seen = elt->valid = T;
2474 if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
2476 /* driver has one-time flag call */
2477 if (snarf->dtb->flag) {
2478 sprintf (tmp,"%lu",i);
2479 (*snarf->dtb->flag) (snarf,tmp,"\\Deleted \\Seen",ST_SET);
2482 else { /* copy failed */
2483 sprintf (tmp,"Unable to move message %lu from %s mailbox",
2484 i,snarf->dtb->name);
2485 mm_log (tmp,WARN);
2489 /* expunge the messages */
2490 mail_close_full (snarf,n ? CL_EXPUNGE : NIL);
2491 stream->snarf.time = (unsigned long) time (0);
2492 /* Even if the snarf failed, we don't want to return NIL if the stream
2493 * is still alive. Or at least that's what we currently think.
2495 /* redo the driver's action */
2496 ret = stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
2498 return ret;
2501 /* Mail check mailbox
2502 * Accepts: mail stream
2505 void mail_check (MAILSTREAM *stream)
2507 /* do the driver's action */
2508 if (stream->dtb) (*stream->dtb->check) (stream);
2512 /* Mail expunge mailbox
2513 * Accepts: mail stream
2514 * sequence to expunge if non-NIL
2515 * expunge options
2516 * Returns: T on success, NIL on failure
2519 long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options)
2521 /* do the driver's action */
2522 return stream->dtb ? (*stream->dtb->expunge) (stream,sequence,options) : NIL;
2526 /* Mail copy message(s)
2527 * Accepts: mail stream
2528 * sequence
2529 * destination mailbox
2530 * flags
2533 long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox,
2534 long options)
2536 return stream->dtb ?
2537 SAFE_COPY (stream->dtb,stream,sequence,mailbox,options) : NIL;
2540 /* Append data package to use for old single-message mail_append() interface */
2542 typedef struct mail_append_package {
2543 char *flags; /* initial flags */
2544 char *date; /* message internal date */
2545 STRING *message; /* stringstruct of message */
2546 } APPENDPACKAGE;
2549 /* Single append message string
2550 * Accepts: mail stream
2551 * package pointer (cast as a void *)
2552 * pointer to return initial flags
2553 * pointer to return message internal date
2554 * pointer to return stringstruct of message to append
2555 * Returns: T, always
2558 static long mail_append_single (MAILSTREAM *stream,void *data,char **flags,
2559 char **date,STRING **message)
2561 APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
2562 *flags = ap->flags; /* get desired data from the package */
2563 *date = ap->date;
2564 *message = ap->message;
2565 ap->message = NIL; /* so next callback puts a stop to it */
2566 return LONGT; /* always return success */
2570 /* Mail append message string
2571 * Accepts: mail stream
2572 * destination mailbox
2573 * initial flags
2574 * message internal date
2575 * stringstruct of message to append
2576 * Returns: T on success, NIL on failure
2579 long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
2580 STRING *message)
2582 APPENDPACKAGE ap;
2583 ap.flags = flags; /* load append package */
2584 ap.date = date;
2585 ap.message = message;
2586 return mail_append_multiple (stream,mailbox,mail_append_single,(void *) &ap);
2589 /* Mail append message(s)
2590 * Accepts: mail stream
2591 * destination mailbox
2592 * append data callback
2593 * arbitrary data for callback use
2594 * Returns: T on success, NIL on failure
2597 long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af,
2598 void *data)
2600 char *s,tmp[MAILTMPLEN];
2601 DRIVER *d = NIL;
2602 long ret = NIL;
2603 /* never allow names with newlines */
2604 if (strpbrk (mailbox,"\015\012"))
2605 MM_LOG ("Can't append to mailbox with such a name",ERROR);
2606 else if (strlen (mailbox) >=
2607 (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
2608 sprintf (tmp,"Can't append %.80s: %s",mailbox,(*mailbox == '{') ?
2609 "invalid remote specification" : "no such mailbox");
2610 MM_LOG (tmp,ERROR);
2612 /* special driver hack? */
2613 else if (!strncmp (lcase (strcpy (tmp,mailbox)),"#driver.",8)) {
2614 /* yes, tie off name at likely delimiter */
2615 if (!(s = strpbrk (tmp+8,"/\\:"))) {
2616 sprintf (tmp,"Can't append to mailbox %.80s: bad driver syntax",mailbox);
2617 MM_LOG (tmp,ERROR);
2618 return NIL;
2620 *s++ = '\0'; /* tie off at delimiter */
2621 if (!(d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,tmp+8))) {
2622 sprintf (tmp,"Can't append to mailbox %.80s: unknown driver",mailbox);
2623 MM_LOG (tmp,ERROR);
2625 else ret = SAFE_APPEND (d,stream,mailbox + (s - tmp),af,data);
2627 else if (d = mail_valid (stream,mailbox,NIL))
2628 ret = SAFE_APPEND (d,stream,mailbox,af,data);
2629 /* No driver, try for TRYCREATE if no stream. Note that we use the
2630 * createProto here, not the appendProto, since the dummy driver already
2631 * took care of the appendProto case. Otherwise, if appendProto is set to
2632 * NIL, we won't get a TRYCREATE.
2634 else if (!stream && (stream = default_proto (NIL)) && stream->dtb &&
2635 SAFE_APPEND (stream->dtb,stream,mailbox,af,data))
2636 /* timing race? */
2637 MM_NOTIFY (stream,"Append validity confusion",WARN);
2638 /* generate error message */
2639 else mail_valid (stream,mailbox,"append to mailbox");
2640 return ret;
2643 /* Mail garbage collect stream
2644 * Accepts: mail stream
2645 * garbage collection flags
2648 void mail_gc (MAILSTREAM *stream,long gcflags)
2650 MESSAGECACHE *elt;
2651 unsigned long i;
2652 /* do the driver's action first */
2653 if (stream->dtb && stream->dtb->gc) (*stream->dtb->gc) (stream,gcflags);
2654 stream->msgno = 0; /* nothing cached now */
2655 if (gcflags & GC_ENV) { /* garbage collect envelopes? */
2656 if (stream->env) mail_free_envelope (&stream->env);
2657 if (stream->body) mail_free_body (&stream->body);
2659 if (gcflags & GC_TEXTS) { /* free texts */
2660 if (stream->text.data) fs_give ((void **) &stream->text.data);
2661 stream->text.size = 0;
2663 /* garbage collect per-message stuff */
2664 for (i = 1; i <= stream->nmsgs; i++)
2665 if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT))
2666 mail_gc_msg (&elt->private.msg,gcflags);
2670 /* Mail garbage collect message
2671 * Accepts: message structure
2672 * garbage collection flags
2675 void mail_gc_msg (MESSAGE *msg,long gcflags)
2677 if (gcflags & GC_ENV) { /* garbage collect envelopes? */
2678 mail_free_envelope (&msg->env);
2679 mail_free_body (&msg->body);
2681 if (gcflags & GC_TEXTS) { /* garbage collect texts */
2682 if (msg->full.text.data) fs_give ((void **) &msg->full.text.data);
2683 if (msg->header.text.data) {
2684 mail_free_stringlist (&msg->lines);
2685 fs_give ((void **) &msg->header.text.data);
2687 if (msg->text.text.data) fs_give ((void **) &msg->text.text.data);
2688 /* now GC all body components */
2689 if (msg->body) mail_gc_body (msg->body);
2693 /* Mail garbage collect texts in BODY structure
2694 * Accepts: BODY structure
2697 void mail_gc_body (BODY *body)
2699 PART *part;
2700 switch (body->type) { /* free contents */
2701 case TYPEMULTIPART: /* multiple part */
2702 for (part = body->nested.part; part; part = part->next)
2703 mail_gc_body (&part->body);
2704 break;
2705 case TYPEMESSAGE: /* encapsulated message */
2706 if (body->subtype && !strcmp (body->subtype,"RFC822")) {
2707 mail_free_stringlist (&body->nested.msg->lines);
2708 mail_gc_msg (body->nested.msg,GC_TEXTS);
2710 break;
2711 default:
2712 break;
2714 if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
2715 if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
2718 /* Mail get body part
2719 * Accepts: mail stream
2720 * message number
2721 * section specifier
2722 * Returns: pointer to body
2725 BODY *mail_body (MAILSTREAM *stream,unsigned long msgno,unsigned char *section)
2727 BODY *b = NIL;
2728 PART *pt;
2729 unsigned long i;
2730 /* make sure have a body */
2731 if (section && *section && mail_fetchstructure (stream,msgno,&b) && b)
2732 while (*section) { /* find desired section */
2733 if (isdigit (*section)) { /* get section specifier */
2734 /* make sure what follows is valid */
2735 if (!(i = strtoul (section,(char **) &section,10)) ||
2736 (*section && ((*section++ != '.') || !*section))) return NIL;
2737 /* multipart content? */
2738 if (b->type == TYPEMULTIPART) {
2739 /* yes, find desired part */
2740 if (pt = b->nested.part) while (--i && (pt = pt->next));
2741 if (!pt) return NIL; /* bad specifier */
2742 b = &pt->body; /* note new body */
2744 /* otherwise must be section 1 */
2745 else if (i != 1) return NIL;
2746 /* need to go down further? */
2747 if (*section) switch (b->type) {
2748 case TYPEMULTIPART: /* multipart */
2749 break;
2750 case TYPEMESSAGE: /* embedded message */
2751 if (!strcmp (b->subtype,"RFC822")) {
2752 b = b->nested.msg->body;
2753 break;
2755 default: /* bogus subpart specification */
2756 return NIL;
2759 else return NIL; /* unknown section specifier */
2761 return b;
2764 /* Mail output date from elt fields
2765 * Accepts: character string to write into
2766 * elt to get data data from
2767 * Returns: the character string
2770 const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
2772 const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2773 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2775 char *mail_date (char *string,MESSAGECACHE *elt)
2777 sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d",
2778 elt->day ? elt->day : 1,
2779 months[elt->month ? (elt->month - 1) : 0],
2780 elt->year + BASEYEAR,elt->hours,elt->minutes,elt->seconds,
2781 elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes);
2782 return string;
2786 /* Mail output extended-ctime format date from elt fields
2787 * Accepts: character string to write into
2788 * elt to get data data from
2789 * Returns: the character string
2792 char *mail_cdate (char *string,MESSAGECACHE *elt)
2794 char *fmt = "%s %s %2d %02d:%02d:%02d %4d %s%02d%02d\n";
2795 int d = elt->day ? elt->day : 1;
2796 int m = elt->month ? (elt->month - 1) : 0;
2797 int y = elt->year + BASEYEAR;
2798 const char *s = months[m];
2799 if (m < 2) { /* if before March, */
2800 m += 10; /* January = month 10 of previous year */
2801 y--;
2803 else m -= 2; /* March is month 0 */
2804 sprintf (string,fmt,days[(int) (d + 2 + ((7 + 31 * m) / 12)
2805 #ifndef USEJULIANCALENDAR
2806 #ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
2807 + (y / 400)
2808 #ifdef Y4KBUGFIX
2809 - (y / 4000)
2810 #endif
2811 #else /* Orthodox calendar */
2812 + (2 * (y / 900)) + ((y % 900) >= 200)
2813 + ((y % 900) >= 600)
2814 #endif
2815 - (y / 100)
2816 #endif
2817 + y + (y / 4)) % 7],
2818 s,d,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR,
2819 elt->zoccident ? "-" : "+",elt->zhours,elt->zminutes);
2820 return string;
2823 /* Mail parse date into elt fields
2824 * Accepts: elt to write into
2825 * date string to parse
2826 * Returns: T if parse successful, else NIL
2827 * This routine parses dates as follows:
2828 * . leading three alphas followed by comma and space are ignored
2829 * . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy,
2830 * dd mmm yy, dd mmm yyyy, yyyy-mm-dd, yyyymmdd
2831 * . two and three digit years interpreted according to RFC 2822 rules
2832 * . mandatory end of string if yyyy-mm-dd or yyyymmdd; otherwise optional
2833 * space followed by time:
2834 * . time accepted in format hh:mm:ss or hh:mm
2835 * . end of string accepted
2836 * . timezone accepted: hyphen followed by symbolic timezone, or space
2837 * followed by signed numeric timezone or symbolic timezone
2838 * Examples of normal input:
2839 * . IMAP date-only (SEARCH):
2840 * dd-mmm-yyyy
2841 * . IMAP date-time (INTERNALDATE):
2842 * dd-mmm-yyyy hh:mm:ss +zzzz
2843 * . RFC-822:
2844 * www, dd mmm yy hh:mm:ss zzz
2845 * . RFC-2822:
2846 * www, dd mmm yyyy hh:mm:ss +zzzz
2849 long mail_parse_date (MESSAGECACHE *elt,unsigned char *s)
2851 unsigned long d,m,y;
2852 int mi,ms;
2853 struct tm *t;
2854 time_t tn;
2855 char tmp[MAILTMPLEN];
2856 static unsigned long maxyear = 0;
2857 if (!maxyear) { /* know the end of time yet? */
2858 MESSAGECACHE tmpelt;
2859 memset (&tmpelt,0xff,sizeof (MESSAGECACHE));
2860 maxyear = BASEYEAR + tmpelt.year;
2862 /* clear elt */
2863 elt->zoccident = elt->zhours = elt->zminutes =
2864 elt->hours = elt->minutes = elt->seconds =
2865 elt->day = elt->month = elt->year = 0;
2866 /* make a writeable uppercase copy */
2867 if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s));
2868 else return NIL;
2869 /* skip over possible day of week */
2870 if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') &&
2871 (s[4] == ' ')) s += 5;
2872 while (*s == ' ') s++; /* parse first number (probable month) */
2873 if (!(m = strtoul (s,(char **) &s,10))) return NIL;
2875 switch (*s) { /* different parse based on delimiter */
2876 case '/': /* mm/dd/yy format */
2877 if (isdigit (*++s) && (d = strtoul (s,(char **) &s,10)) &&
2878 (*s == '/') && isdigit (*++s)) {
2879 y = strtoul (s,(char **) &s,10);
2880 if (*s == '\0') break; /* must end here */
2882 return NIL; /* bogon */
2883 case ' ': /* dd mmm yy format */
2884 while (s[1] == ' ') s++; /* slurp extra whitespace */
2885 case '-':
2886 if (isdigit (s[1])) { /* possible ISO 8601 date format? */
2887 y = m; /* yes, first number is year */
2888 /* get month and day */
2889 if ((m = strtoul (s+1,(char **) &s,10)) && (*s++ == '-') &&
2890 (d = strtoul (s,(char **) &s,10)) && !*s) break;
2891 return NIL; /* syntax error or time present */
2893 d = m; /* dd-mmm-yy[yy], so first number is a day */
2894 /* make sure string long enough! */
2895 if (strlen (s) < (size_t) 5) return NIL;
2896 /* Some compilers don't allow `<<' and/or longs in case statements. */
2897 /* slurp up the month string */
2898 ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A');
2899 switch (ms) { /* determine the month */
2900 case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
2901 case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
2902 case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
2903 case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
2904 case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
2905 case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
2906 case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
2907 case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
2908 case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
2909 case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break;
2910 case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break;
2911 case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break;
2912 default: return NIL; /* unknown month */
2914 if (s[4] == *s) s += 5; /* advance to year */
2915 else { /* first three were OK, possibly full name */
2916 mi = *s; /* note delimiter, skip alphas */
2917 for (s += 4; isalpha (*s); s++);
2918 /* error if delimiter not here */
2919 if (mi != *s++) return NIL;
2921 while (*s == ' ') s++; /* parse year */
2922 if (isdigit (*s)) { /* must be a digit here */
2923 y = strtoul (s,(char **) &s,10);
2924 if (*s == '\0' || *s == ' ') break;
2926 case '\0': /* ISO 8601 compact date */
2927 if (m < (BASEYEAR * 10000)) return NIL;
2928 y = m / 10000; /* get year */
2929 d = (m %= 10000) % 100; /* get day */
2930 m /= 100; /* and month */
2931 break;
2932 default:
2933 return NIL; /* unknown date format */
2936 /* minimal validity check of date */
2937 if ((d > 31) || (m > 12)) return NIL;
2938 if (y < 49) y += 2000; /* RFC 2282 rules for two digit years 00-49 */
2939 else if (y < 999) y += 1900; /* 2-digit years 50-99 and 3-digit years */
2940 /* reject prehistoric and far future years */
2941 if ((y < BASEYEAR) || (y > maxyear)) return NIL;
2942 /* set values in elt */
2943 elt->day = d; elt->month = m; elt->year = y - BASEYEAR;
2944 ms = '\0'; /* initially no time zone string */
2945 if (*s) { /* time specification present? */
2946 /* parse time */
2947 d = strtoul (s+1,(char **) &s,10);
2948 if (*s != ':') return NIL;
2949 m = strtoul (++s,(char **) &s,10);
2950 y = (*s == ':') ? strtoul (++s,(char **) &s,10) : 0;
2951 /* validity check time */
2952 if ((d > 23) || (m > 59) || (y > 60)) return NIL;
2953 /* set values in elt */
2954 elt->hours = d; elt->minutes = m; elt->seconds = y;
2955 switch (*s) { /* time zone specifier? */
2956 case ' ': /* numeric time zone */
2957 while (s[1] == ' ') s++; /* slurp extra whitespace */
2958 if (!isalpha (s[1])) { /* treat as '-' case if alphabetic */
2959 /* test for sign character */
2960 if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++;
2961 /* validate proper timezone */
2962 if (isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && (s[2] < '6') &&
2963 isdigit(s[3])) {
2964 elt->zhours = (*s - '0') * 10 + (s[1] - '0');
2965 elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0');
2967 return T; /* all done! */
2969 /* falls through */
2970 case '-': /* symbolic time zone */
2971 if (!(ms = *++s)) ms = 'Z';
2972 else if (*++s) { /* multi-character? */
2973 ms -= 'A'; ms *= 1024; /* yes, make compressed three-byte form */
2974 ms += ((*s++ - 'A') * 32);
2975 if (*s) ms += *s++ - 'A';
2976 if (*s) ms = '\0'; /* more than three characters */
2978 default: /* ignore anything else */
2979 break;
2983 /* This is not intended to be a comprehensive list of all possible
2984 * timezone strings. Such a list would be impractical. Rather, this
2985 * listing is intended to incorporate all military, North American, and
2986 * a few special cases such as Japan and the major European zone names,
2987 * such as what might be expected to be found in a Tenex format mailbox
2988 * and spewed from an IMAP server. The trend is to migrate to numeric
2989 * timezones which lack the flavor but also the ambiguity of the names.
2991 * RFC-822 only recognizes UT, GMT, 1-letter military timezones, and the
2992 * 4 CONUS timezones and their summer time variants. [Sorry, Canadian
2993 * Atlantic Provinces, Alaska, and Hawaii.]
2995 switch (ms) { /* determine the timezone */
2996 /* Universal */
2997 case (('U'-'A')*1024)+(('T'-'A')*32):
2998 #ifndef STRICT_RFC822_TIMEZONES
2999 case (('U'-'A')*1024)+(('T'-'A')*32)+'C'-'A':
3000 #endif
3001 /* Greenwich */
3002 case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A':
3003 case 'Z': elt->zhours = 0; break;
3005 /* oriental (from Greenwich) timezones */
3006 #ifndef STRICT_RFC822_TIMEZONES
3007 /* Middle Europe */
3008 case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
3009 #endif
3010 #ifdef BRITISH_SUMMER_TIME
3011 /* British Summer */
3012 case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3013 #endif
3014 case 'A': elt->zhours = 1; break;
3015 #ifndef STRICT_RFC822_TIMEZONES
3016 /* Eastern Europe */
3017 case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
3018 #endif
3019 case 'B': elt->zhours = 2; break;
3020 case 'C': elt->zhours = 3; break;
3021 case 'D': elt->zhours = 4; break;
3022 case 'E': elt->zhours = 5; break;
3023 case 'F': elt->zhours = 6; break;
3024 case 'G': elt->zhours = 7; break;
3025 case 'H': elt->zhours = 8; break;
3026 #ifndef STRICT_RFC822_TIMEZONES
3027 /* Japan */
3028 case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3029 #endif
3030 case 'I': elt->zhours = 9; break;
3031 case 'K': elt->zhours = 10; break;
3032 case 'L': elt->zhours = 11; break;
3033 case 'M': elt->zhours = 12; break;
3035 /* occidental (from Greenwich) timezones */
3036 case 'N': elt->zoccident = 1; elt->zhours = 1; break;
3037 case 'O': elt->zoccident = 1; elt->zhours = 2; break;
3038 #ifndef STRICT_RFC822_TIMEZONES
3039 case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3040 #endif
3041 case 'P': elt->zoccident = 1; elt->zhours = 3; break;
3042 #ifdef NEWFOUNDLAND_STANDARD_TIME
3043 /* Newfoundland */
3044 case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3045 elt->zoccident = 1; elt->zhours = 3; elt->zminutes = 30; break;
3046 #endif
3047 #ifndef STRICT_RFC822_TIMEZONES
3048 /* Atlantic */
3049 case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3050 #endif
3051 /* CONUS */
3052 case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3053 case 'Q': elt->zoccident = 1; elt->zhours = 4; break;
3054 /* Eastern */
3055 case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3056 case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3057 case 'R': elt->zoccident = 1; elt->zhours = 5; break;
3058 /* Central */
3059 case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3060 case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3061 case 'S': elt->zoccident = 1; elt->zhours = 6; break;
3062 /* Mountain */
3063 case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3064 case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3065 case 'T': elt->zoccident = 1; elt->zhours = 7; break;
3066 /* Pacific */
3067 case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3068 #ifndef STRICT_RFC822_TIMEZONES
3069 case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
3070 #endif
3071 case 'U': elt->zoccident = 1; elt->zhours = 8; break;
3072 #ifndef STRICT_RFC822_TIMEZONES
3073 /* Yukon */
3074 case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3075 #endif
3076 case 'V': elt->zoccident = 1; elt->zhours = 9; break;
3077 #ifndef STRICT_RFC822_TIMEZONES
3078 /* Hawaii */
3079 case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3080 #endif
3081 case 'W': elt->zoccident = 1; elt->zhours = 10; break;
3082 /* Nome/Bering/Samoa */
3083 #ifdef NOME_STANDARD_TIME
3084 case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3085 #endif
3086 #ifdef BERING_STANDARD_TIME
3087 case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3088 #endif
3089 #ifdef SAMOA_STANDARD_TIME
3090 case (('S'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
3091 #endif
3092 case 'X': elt->zoccident = 1; elt->zhours = 11; break;
3093 case 'Y': elt->zoccident = 1; elt->zhours = 12; break;
3095 default: /* unknown time zones treated as local */
3096 tn = time (0); /* time now... */
3097 t = localtime (&tn); /* get local minutes since midnight */
3098 mi = t->tm_hour * 60 + t->tm_min;
3099 ms = t->tm_yday; /* note Julian day */
3100 if (t = gmtime (&tn)) { /* minus UTC minutes since midnight */
3101 mi -= t->tm_hour * 60 + t->tm_min;
3102 /* ms can be one of:
3103 * 36x local time is December 31, UTC is January 1, offset -24 hours
3104 * 1 local time is 1 day ahead of UTC, offset +24 hours
3105 * 0 local time is same day as UTC, no offset
3106 * -1 local time is 1 day behind UTC, offset -24 hours
3107 * -36x local time is January 1, UTC is December 31, offset +24 hours
3109 if (ms -= t->tm_yday) /* correct offset if different Julian day */
3110 mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60;
3111 if (mi < 0) { /* occidental? */
3112 mi = abs (mi); /* yup, make positive number */
3113 elt->zoccident = 1; /* and note west of UTC */
3115 elt->zhours = mi / 60; /* now break into hours and minutes */
3116 elt->zminutes = mi % 60;
3118 break;
3120 return T;
3123 /* Mail n messages exist
3124 * Accepts: mail stream
3125 * number of messages
3128 void mail_exists (MAILSTREAM *stream,unsigned long nmsgs)
3130 char tmp[MAILTMPLEN];
3131 if (nmsgs > MAXMESSAGES) {
3132 sprintf (tmp,"Mailbox has more messages (%lu) exist than maximum (%lu)",
3133 nmsgs,MAXMESSAGES);
3134 mm_log (tmp,ERROR);
3135 nmsgs = MAXMESSAGES; /* cap to maximum */
3136 /* probably will crash in mail_elt() soon enough... */
3138 /* make sure cache is large enough */
3139 (*mailcache) (stream,nmsgs,CH_SIZE);
3140 stream->nmsgs = nmsgs; /* update stream status */
3141 /* notify main program of change */
3142 if (!stream->silent) MM_EXISTS (stream,nmsgs);
3146 /* Mail n messages are recent
3147 * Accepts: mail stream
3148 * number of recent messages
3151 void mail_recent (MAILSTREAM *stream,unsigned long recent)
3153 char tmp[MAILTMPLEN];
3154 if (recent <= stream->nmsgs) stream->recent = recent;
3155 else {
3156 sprintf (tmp,"Non-existent recent message(s) %lu, nmsgs=%lu",
3157 recent,stream->nmsgs);
3158 mm_log (tmp,ERROR);
3163 /* Mail message n is expunged
3164 * Accepts: mail stream
3165 * message #
3168 void mail_expunged (MAILSTREAM *stream,unsigned long msgno)
3170 char tmp[MAILTMPLEN];
3171 MESSAGECACHE *elt;
3172 if (msgno > stream->nmsgs) {
3173 sprintf (tmp,"Expunge of non-existent message %lu, nmsgs=%lu",
3174 msgno,stream->nmsgs);
3175 mm_log (tmp,ERROR);
3177 else {
3178 elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
3179 /* notify main program of change */
3180 if (!stream->silent) MM_EXPUNGED (stream,msgno);
3181 if (elt) { /* if an element is there */
3182 elt->msgno = 0; /* invalidate its message number and free */
3183 (*mailcache) (stream,msgno,CH_FREE);
3184 (*mailcache) (stream,msgno,CH_FREESORTCACHE);
3186 /* expunge the slot */
3187 (*mailcache) (stream,msgno,CH_EXPUNGE);
3188 --stream->nmsgs; /* update stream status */
3189 if (stream->msgno) { /* have stream pointers? */
3190 /* make sure the short cache is nuked */
3191 if (stream->scache) mail_gc (stream,GC_ENV | GC_TEXTS);
3192 else stream->msgno = 0; /* make sure invalidated in any case */
3197 /* Mail stream status routines */
3200 /* Mail lock stream
3201 * Accepts: mail stream
3204 void mail_lock (MAILSTREAM *stream)
3206 if (stream->lock) {
3207 char tmp[MAILTMPLEN];
3208 sprintf (tmp,"Lock when already locked, mbx=%.80s",
3209 stream->mailbox ? stream->mailbox : "???");
3210 fatal (tmp);
3212 else stream->lock = T; /* lock stream */
3216 /* Mail unlock stream
3217 * Accepts: mail stream
3220 void mail_unlock (MAILSTREAM *stream)
3222 if (!stream->lock) fatal ("Unlock when not locked");
3223 else stream->lock = NIL; /* unlock stream */
3227 /* Mail turn on debugging telemetry
3228 * Accepts: mail stream
3231 void mail_debug (MAILSTREAM *stream)
3233 stream->debug = T; /* turn on debugging telemetry */
3234 if (stream->dtb) (*stream->dtb->parameters) (ENABLE_DEBUG,stream);
3238 /* Mail turn off debugging telemetry
3239 * Accepts: mail stream
3242 void mail_nodebug (MAILSTREAM *stream)
3244 stream->debug = NIL; /* turn off debugging telemetry */
3245 if (stream->dtb) (*stream->dtb->parameters) (DISABLE_DEBUG,stream);
3249 /* Mail log to debugging telemetry
3250 * Accepts: message
3251 * flag that data is "sensitive"
3254 void mail_dlog (char *string,long flag)
3256 mm_dlog ((debugsensitive || !flag) ? string : "<suppressed>");
3259 /* Mail parse UID sequence
3260 * Accepts: mail stream
3261 * sequence to parse
3262 * Returns: T if parse successful, else NIL
3265 long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence)
3267 unsigned long i,j,k,x,y;
3268 for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
3269 while (sequence && *sequence){/* while there is something to parse */
3270 if (*sequence == '*') { /* maximum message */
3271 i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
3272 sequence++; /* skip past * */
3274 /* parse and validate message number */
3275 /* parse and validate message number */
3276 else if (!isdigit (*sequence)) {
3277 MM_LOG ("Syntax error in sequence",ERROR);
3278 return NIL;
3280 else if (!(i = strtoul (sequence,(char **) &sequence,10))) {
3281 MM_LOG ("UID may not be zero",ERROR);
3282 return NIL;
3284 switch (*sequence) { /* see what the delimiter is */
3285 case ':': /* sequence range */
3286 if (*++sequence == '*') { /* maximum message */
3287 j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
3288 sequence++; /* skip past * */
3290 /* parse end of range */
3291 else if (!(j = strtoul (sequence,(char **) &sequence,10))) {
3292 MM_LOG ("UID sequence range invalid",ERROR);
3293 return NIL;
3295 if (*sequence && *sequence++ != ',') {
3296 MM_LOG ("UID sequence range syntax error",ERROR);
3297 return NIL;
3299 if (i > j) { /* swap the range if backwards */
3300 x = i; i = j; j = x;
3302 x = mail_msgno (stream,i);/* get msgnos */
3303 y = mail_msgno (stream,j);/* for both UIDS (don't && it) */
3304 /* easy if both UIDs valid */
3305 if (x && y) while (x <= y) mail_elt (stream,x++)->sequence = T;
3306 /* start UID valid, end is not */
3307 else if (x) while ((x <= stream->nmsgs) && (mail_uid (stream,x) <= j))
3308 mail_elt (stream,x++)->sequence = T;
3309 /* end UID valid, start is not */
3310 else if (y) for (x = 1; x <= y; x++) {
3311 if (mail_uid (stream,x) >= i) mail_elt (stream,x)->sequence = T;
3313 /* neither is valid, ugh */
3314 else for (x = 1; x <= stream->nmsgs; x++)
3315 if (((k = mail_uid (stream,x)) >= i) && (k <= j))
3316 mail_elt (stream,x)->sequence = T;
3317 break;
3318 case ',': /* single message */
3319 ++sequence; /* skip the delimiter, fall into end case */
3320 case '\0': /* end of sequence, mark this message */
3321 if (x = mail_msgno (stream,i)) mail_elt (stream,x)->sequence = T;
3322 break;
3323 default: /* anything else is a syntax error! */
3324 MM_LOG ("UID sequence syntax error",ERROR);
3325 return NIL;
3328 return T; /* successfully parsed sequence */
3331 /* Mail see if line list matches that in cache
3332 * Accepts: candidate line list
3333 * cached line list
3334 * matching flags
3335 * Returns: T if match, NIL if no match
3338 long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags)
3340 unsigned long i;
3341 unsigned char *s,*t;
3342 STRINGLIST *m;
3343 if (!msglines) return T; /* full header is in cache */
3344 /* need full header but filtered in cache */
3345 if ((flags & FT_NOT) || !lines) return NIL;
3346 do { /* make sure all present & accounted for */
3347 for (m = msglines; m; m = m->next) if (lines->text.size == m->text.size) {
3348 for (s = lines->text.data,t = m->text.data,i = lines->text.size;
3349 i && !compare_uchar (*s,*t); s++,t++,i--);
3350 if (!i) break; /* this line matches */
3352 if (!m) return NIL; /* didn't find in the list */
3354 while (lines = lines->next);
3355 return T; /* all lines found */
3358 /* Mail filter text by header lines
3359 * Accepts: text to filter, with trailing null
3360 * length of text
3361 * list of lines
3362 * fetch flags
3363 * Returns: new text size, text overwritten
3366 unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines,
3367 long flags)
3369 STRINGLIST *hdrs;
3370 int notfound;
3371 unsigned long i;
3372 char c,*s,*e,*t,tmp[MAILTMPLEN];
3373 char *src = text;
3374 char *dst = src;
3375 char *end = text + len;
3376 text[len] = '\012'; /* guard against running off buffer */
3377 while (src < end) { /* process header */
3378 /* slurp header line name */
3379 for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp;
3380 (s < e) && ((c = (*s ? *s : (*s = ' '))) != ':') &&
3381 ((c > ' ') ||
3382 ((c != ' ') && (c != '\t') && (c != '\015') && (c != '\012')));
3383 *t++ = *s++);
3384 *t = '\0'; /* tie off */
3385 notfound = T; /* not found yet */
3386 if (i = t - tmp) /* see if found in header */
3387 for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next)
3388 if ((hdrs->text.size == i) && !compare_csizedtext (tmp,&hdrs->text))
3389 notfound = NIL;
3390 /* skip header line if not wanted */
3391 if (i && ((flags & FT_NOT) ? !notfound : notfound))
3392 while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3393 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3394 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3395 (*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
3396 else if (src == dst) { /* copy to self */
3397 while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3398 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3399 (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
3400 (*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
3401 dst = src; /* update destination */
3403 else { /* copy line and any continuation line */
3404 while ((((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3405 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3406 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3407 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
3408 ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012'))||
3409 ((*src == ' ') || (*src == '\t')));
3410 /* in case hit the guard LF */
3411 if (src > end) dst -= (src - end);
3414 *dst = '\0'; /* tie off destination */
3415 return dst - text;
3418 /* Local mail search message
3419 * Accepts: MAIL stream
3420 * message number
3421 * optional section specification
3422 * search program
3423 * Returns: T if found, NIL otherwise
3426 long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section,
3427 SEARCHPGM *pgm)
3429 unsigned short d;
3430 char tmp[MAILTMPLEN];
3431 MESSAGECACHE *elt = mail_elt (stream,msgno);
3432 SEARCHHEADER *hdr;
3433 SEARCHOR *or;
3434 SEARCHPGMLIST *not;
3435 unsigned long now = (unsigned long) time (0);
3436 if (pgm->msgno || pgm->uid) { /* message set searches */
3437 SEARCHSET *set;
3438 /* message sequences */
3439 if (pgm->msgno) { /* inside this message sequence set */
3440 for (set = pgm->msgno; set; set = set->next)
3441 if (set->last ? ((set->first <= set->last) ?
3442 ((msgno >= set->first) && (msgno <= set->last)) :
3443 ((msgno >= set->last) && (msgno <= set->first))) :
3444 msgno == set->first) break;
3445 if (!set) return NIL; /* not found within sequence */
3447 if (pgm->uid) { /* inside this unique identifier set */
3448 unsigned long uid = mail_uid (stream,msgno);
3449 for (set = pgm->uid; set; set = set->next)
3450 if (set->last ? ((set->first <= set->last) ?
3451 ((uid >= set->first) && (uid <= set->last)) :
3452 ((uid >= set->last) && (uid <= set->first))) :
3453 uid == set->first) break;
3454 if (!set) return NIL; /* not found within sequence */
3458 /* Fast data searches */
3459 /* need to fetch fast data? */
3460 if ((!elt->rfc822_size && (pgm->larger || pgm->smaller)) ||
3461 (!elt->year && (pgm->before || pgm->on || pgm->since ||
3462 pgm->older || pgm->younger)) ||
3463 (!elt->valid && (pgm->answered || pgm->unanswered ||
3464 pgm->deleted || pgm->undeleted ||
3465 pgm->draft || pgm->undraft ||
3466 pgm->flagged || pgm->unflagged ||
3467 pgm->recent || pgm->old ||
3468 pgm->seen || pgm->unseen ||
3469 pgm->keyword || pgm->unkeyword))) {
3470 unsigned long i;
3471 MESSAGECACHE *ielt;
3472 for (i = elt->msgno; /* find last unloaded message in range */
3473 (i < stream->nmsgs) && (ielt = mail_elt (stream,i+1)) &&
3474 ((!ielt->rfc822_size && (pgm->larger || pgm->smaller)) ||
3475 (!ielt->year && (pgm->before || pgm->on || pgm->since ||
3476 pgm->older || pgm->younger)) ||
3477 (!ielt->valid && (pgm->answered || pgm->unanswered ||
3478 pgm->deleted || pgm->undeleted ||
3479 pgm->draft || pgm->undraft ||
3480 pgm->flagged || pgm->unflagged ||
3481 pgm->recent || pgm->old ||
3482 pgm->seen || pgm->unseen ||
3483 pgm->keyword || pgm->unkeyword))); ++i);
3484 if (i == elt->msgno) sprintf (tmp,"%lu",elt->msgno);
3485 else sprintf (tmp,"%lu:%lu",elt->msgno,i);
3486 mail_fetch_fast (stream,tmp,NIL);
3488 /* size ranges */
3489 if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
3490 (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;
3491 /* message flags */
3492 if ((pgm->answered && !elt->answered) ||
3493 (pgm->unanswered && elt->answered) ||
3494 (pgm->deleted && !elt->deleted) ||
3495 (pgm->undeleted && elt->deleted) ||
3496 (pgm->draft && !elt->draft) ||
3497 (pgm->undraft && elt->draft) ||
3498 (pgm->flagged && !elt->flagged) ||
3499 (pgm->unflagged && elt->flagged) ||
3500 (pgm->recent && !elt->recent) ||
3501 (pgm->old && elt->recent) ||
3502 (pgm->seen && !elt->seen) ||
3503 (pgm->unseen && elt->seen)) return NIL;
3504 /* keywords */
3505 if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
3506 (pgm->unkeyword && !mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
3507 return NIL;
3508 /* internal date ranges */
3509 if (pgm->before || pgm->on || pgm->since) {
3510 d = mail_shortdate (elt->year,elt->month,elt->day);
3511 if (pgm->before && (d >= pgm->before)) return NIL;
3512 if (pgm->on && (d != pgm->on)) return NIL;
3513 if (pgm->since && (d < pgm->since)) return NIL;
3515 if (pgm->older || pgm->younger) {
3516 unsigned long msgd = mail_longdate (elt);
3517 if (pgm->older && msgd > (now - pgm->older)) return NIL;
3518 if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
3521 /* envelope searches */
3522 if (pgm->sentbefore || pgm->senton || pgm->sentsince ||
3523 pgm->bcc || pgm->cc || pgm->from || pgm->to || pgm->subject ||
3524 pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to ||
3525 pgm->message_id || pgm->newsgroups || pgm->followup_to ||
3526 pgm->references) {
3527 ENVELOPE *env;
3528 MESSAGECACHE delt;
3529 if (section) { /* use body part envelope */
3530 BODY *body = mail_body (stream,msgno,section);
3531 env = (body && (body->type == TYPEMESSAGE) && body->subtype &&
3532 !strcmp (body->subtype,"RFC822")) ? body->nested.msg->env : NIL;
3534 else { /* use top level envelope if no section */
3535 if (pgm->header && !stream->scache && !(stream->dtb->flags & DR_LOCAL))
3536 mail_fetch_header(stream,msgno,NIL,NIL,NIL,FT_PEEK|FT_SEARCHLOOKAHEAD);
3537 env = mail_fetchenvelope (stream,msgno);
3539 if (!env) return NIL; /* no envelope obtained */
3540 /* sent date ranges */
3541 if ((pgm->sentbefore || pgm->senton || pgm->sentsince) &&
3542 (!mail_parse_date (&delt,env->date) ||
3543 !(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
3544 (pgm->sentbefore && (d >= pgm->sentbefore)) ||
3545 (pgm->senton && (d != pgm->senton)) ||
3546 (pgm->sentsince && (d < pgm->sentsince)))) return NIL;
3547 /* search headers */
3548 if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
3549 (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
3550 (pgm->from && !mail_search_addr (env->from,pgm->from)) ||
3551 (pgm->to && !mail_search_addr (env->to,pgm->to)) ||
3552 (pgm->subject && !mail_search_header_text (env->subject,pgm->subject)))
3553 return NIL;
3554 /* These criteria are not supported by IMAP and have to be emulated */
3555 if ((pgm->return_path &&
3556 !mail_search_addr (env->return_path,pgm->return_path)) ||
3557 (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
3558 (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
3559 (pgm->in_reply_to &&
3560 !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
3561 (pgm->message_id &&
3562 !mail_search_header_text (env->message_id,pgm->message_id)) ||
3563 (pgm->newsgroups &&
3564 !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
3565 (pgm->followup_to &&
3566 !mail_search_header_text (env->followup_to,pgm->followup_to)) ||
3567 (pgm->references &&
3568 !mail_search_header_text (env->references,pgm->references)))
3569 return NIL;
3572 /* search header lines */
3573 for (hdr = pgm->header; hdr; hdr = hdr->next) {
3574 char *t,*e,*v;
3575 SIZEDTEXT s;
3576 STRINGLIST sth,stc;
3577 sth.next = stc.next = NIL; /* only one at a time */
3578 sth.text.data = hdr->line.data;
3579 sth.text.size = hdr->line.size;
3580 /* get the header text */
3581 if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
3582 FT_INTERNAL | FT_PEEK |
3583 (section ? NIL : FT_SEARCHLOOKAHEAD))) &&
3584 strchr (t,':')) {
3585 if (hdr->text.size) { /* anything matches empty search string */
3586 /* non-empty, copy field data */
3587 s.data = (unsigned char *) fs_get (s.size + 1);
3588 /* for each line */
3589 for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
3590 default: /* non-continuation, skip leading field name */
3591 while ((t < e) && (*t++ != ':'));
3592 if ((t < e) && (*t == ':')) t++;
3593 case '\t': case ' ': /* copy field data */
3594 while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
3595 *v++ = '\n'; /* tie off line */
3596 while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
3598 /* calculate true size */
3599 s.size = v - (char *) s.data;
3600 *v = '\0'; /* tie off results */
3601 stc.text.data = hdr->text.data;
3602 stc.text.size = hdr->text.size;
3603 /* search header */
3604 if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
3605 else { /* search failed */
3606 fs_give ((void **) &s.data);
3607 return NIL;
3611 else return NIL; /* no matching header text */
3613 /* search strings */
3614 if ((pgm->text && !mail_search_text (stream,msgno,section,pgm->text,LONGT))||
3615 (pgm->body && !mail_search_text (stream,msgno,section,pgm->body,NIL)))
3616 return NIL;
3617 /* logical conditions */
3618 for (or = pgm->or; or; or = or->next)
3619 if (!(mail_search_msg (stream,msgno,section,or->first) ||
3620 mail_search_msg (stream,msgno,section,or->second))) return NIL;
3621 for (not = pgm->not; not; not = not->next)
3622 if (mail_search_msg (stream,msgno,section,not->pgm)) return NIL;
3623 return T;
3626 /* Mail search message header null-terminated text
3627 * Accepts: header text
3628 * strings to search
3629 * Returns: T if search found a match
3632 long mail_search_header_text (char *s,STRINGLIST *st)
3634 SIZEDTEXT h;
3635 /* have any text? */
3636 if (h.data = (unsigned char *) s) {
3637 h.size = strlen (s); /* yes, get its size */
3638 return mail_search_header (&h,st);
3640 return NIL;
3644 /* Mail search message header
3645 * Accepts: header as sized text
3646 * strings to search
3647 * Returns: T if search found a match
3650 long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st)
3652 SIZEDTEXT h;
3653 long ret = LONGT;
3654 /* make UTF-8 version of header */
3655 utf8_mime2text (hdr,&h,U8T_CANONICAL);
3656 while (h.size && ((h.data[h.size-1]=='\015') || (h.data[h.size-1]=='\012')))
3657 --h.size; /* slice off trailing newlines */
3658 do if (h.size ? /* search non-empty string */
3659 !ssearch (h.data,h.size,st->text.data,st->text.size) : st->text.size)
3660 ret = NIL;
3661 while (ret && (st = st->next));
3662 if (h.data != hdr->data) fs_give ((void **) &h.data);
3663 return ret;
3666 /* Mail search message body
3667 * Accepts: MAIL stream
3668 * message number
3669 * optional section specification
3670 * string list
3671 * flags
3672 * Returns: T if search found a match
3675 long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section,
3676 STRINGLIST *st,long flags)
3678 BODY *body;
3679 long ret = NIL;
3680 STRINGLIST *s = mail_newstringlist ();
3681 mailgets_t omg = mailgets;
3682 if (stream->dtb->flags & DR_LOWMEM) mailgets = mail_search_gets;
3683 /* strings to search */
3684 for (stream->private.search.string = s; st;) {
3685 s->text.data = st->text.data;
3686 s->text.size = st->text.size;
3687 if (st = st->next) s = s->next = mail_newstringlist ();
3689 stream->private.search.text = NIL;
3690 if (flags) { /* want header? */
3691 SIZEDTEXT s,t;
3692 s.data = (unsigned char *)
3693 mail_fetch_header (stream,msgno,section,NIL,&s.size,FT_INTERNAL|FT_PEEK);
3694 utf8_mime2text (&s,&t,U8T_CANONICAL);
3695 ret = mail_search_string_work (&t,&stream->private.search.string);
3696 if (t.data != s.data) fs_give ((void **) &t.data);
3698 if (!ret) { /* still looking for match? */
3699 /* no section, get top-level body */
3700 if (!section) mail_fetchstructure (stream,msgno,&body);
3701 /* get body of nested message */
3702 else if ((body = mail_body (stream,msgno,section)) &&
3703 (body->type == TYPEMULTIPART) && body->subtype &&
3704 !strcmp (body->subtype,"RFC822")) body = body->nested.msg->body;
3705 if (body) ret = mail_search_body (stream,msgno,body,NIL,1,flags);
3707 mailgets = omg; /* restore former gets routine */
3708 /* clear searching */
3709 for (s = stream->private.search.string; s; s = s->next) s->text.data = NIL;
3710 mail_free_stringlist (&stream->private.search.string);
3711 stream->private.search.text = NIL;
3712 return ret;
3715 /* Mail search message body text parts
3716 * Accepts: MAIL stream
3717 * message number
3718 * current body pointer
3719 * hierarchical level prefix
3720 * position at current hierarchical level
3721 * string list
3722 * flags
3723 * Returns: T if search found a match
3726 long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body,
3727 char *prefix,unsigned long section,long flags)
3729 long ret = NIL;
3730 unsigned long i;
3731 char *s,*t,sect[MAILTMPLEN];
3732 SIZEDTEXT st,h;
3733 PART *part;
3734 PARAMETER *param;
3735 if (prefix && (strlen (prefix) > (MAILTMPLEN - 20))) return NIL;
3736 sprintf (sect,"%s%lu",prefix ? prefix : "",section++);
3737 if (flags && prefix) { /* want to search MIME header too? */
3738 st.data = (unsigned char *) mail_fetch_mime (stream,msgno,sect,&st.size,
3739 FT_INTERNAL | FT_PEEK);
3740 if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
3741 else {
3742 /* make UTF-8 version of header */
3743 utf8_mime2text (&st,&h,U8T_CANONICAL);
3744 ret = mail_search_string_work (&h,&stream->private.search.string);
3745 if (h.data != st.data) fs_give ((void **) &h.data);
3748 if (!ret) switch (body->type) {
3749 case TYPEMULTIPART:
3750 /* extend prefix if not first time */
3751 s = prefix ? strcat (sect,".") : "";
3752 for (i = 1,part = body->nested.part; part && !ret; i++,part = part->next)
3753 ret = mail_search_body (stream,msgno,&part->body,s,i,flags);
3754 break;
3755 case TYPEMESSAGE:
3756 if (!strcmp (body->subtype,"RFC822")) {
3757 if (flags) { /* want to search nested message header? */
3758 st.data = (unsigned char *)
3759 mail_fetch_header (stream,msgno,sect,NIL,&st.size,
3760 FT_INTERNAL | FT_PEEK);
3761 if (stream->dtb->flags & DR_LOWMEM) ret =stream->private.search.result;
3762 else {
3763 /* make UTF-8 version of header */
3764 utf8_mime2text (&st,&h,U8T_CANONICAL);
3765 ret = mail_search_string_work (&h,&stream->private.search.string);
3766 if (h.data != st.data) fs_give ((void **) &h.data);
3769 if (body = body->nested.msg->body)
3770 ret = (body->type == TYPEMULTIPART) ?
3771 mail_search_body (stream,msgno,body,(prefix ? prefix : ""),
3772 section - 1,flags) :
3773 mail_search_body (stream,msgno,body,strcat (sect,"."),1,flags);
3774 break;
3776 /* non-MESSAGE/RFC822 falls into text case */
3778 case TYPETEXT:
3779 s = mail_fetch_body (stream,msgno,sect,&i,FT_INTERNAL | FT_PEEK);
3780 if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
3781 else {
3782 for (t = NIL,param = body->parameter; param && !t; param = param->next)
3783 if (!strcmp (param->attribute,"CHARSET")) t = param->value;
3784 switch (body->encoding) { /* what encoding? */
3785 case ENCBASE64:
3786 if (st.data = (unsigned char *)
3787 rfc822_base64 ((unsigned char *) s,i,&st.size)) {
3788 ret = mail_search_string (&st,t,&stream->private.search.string);
3789 fs_give ((void **) &st.data);
3791 break;
3792 case ENCQUOTEDPRINTABLE:
3793 if (st.data = rfc822_qprint ((unsigned char *) s,i,&st.size)) {
3794 ret = mail_search_string (&st,t,&stream->private.search.string);
3795 fs_give ((void **) &st.data);
3797 break;
3798 default:
3799 st.data = (unsigned char *) s;
3800 st.size = i;
3801 ret = mail_search_string (&st,t,&stream->private.search.string);
3802 break;
3805 break;
3807 return ret;
3810 /* Mail search text
3811 * Accepts: sized text to search
3812 * character set of sized text
3813 * string list of search keys
3814 * Returns: T if search found a match
3817 long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st)
3819 SIZEDTEXT u;
3820 long ret;
3821 STRINGLIST **sc = st;
3822 /* convert to UTF-8 as best we can */
3823 if (!utf8_text (s,charset,&u,U8T_CANONICAL))
3824 utf8_text (s,NIL,&u,U8T_CANONICAL);
3825 ret = mail_search_string_work (&u,st);
3826 if (u.data != s->data) fs_give ((void **) &u.data);
3827 return ret;
3831 /* Mail search text worker routine
3832 * Accepts: sized text to search
3833 * string list of search keys
3834 * Returns: T if search found a match
3837 long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st)
3839 void *t;
3840 STRINGLIST **sc = st;
3841 while (*sc) { /* run down criteria list */
3842 if (ssearch (s->data,s->size,(*sc)->text.data,(*sc)->text.size)) {
3843 t = (void *) (*sc); /* found one, need to flush this */
3844 *sc = (*sc)->next; /* remove it from the list */
3845 fs_give (&t); /* flush the buffer */
3847 else sc = &(*sc)->next; /* move to next in list */
3849 return *st ? NIL : LONGT;
3853 /* Mail search keyword
3854 * Accepts: MAIL stream
3855 * elt to get flags from
3856 * keyword list
3857 * T for keyword search, NIL for unkeyword search
3858 * Returns: T if search found a match
3861 long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st,
3862 long flag)
3864 int i,j;
3865 unsigned long f = 0;
3866 unsigned long tf;
3867 do {
3868 for (i = 0; (j = (i < NUSERFLAGS) && stream->user_flags[i]); ++i)
3869 if (!compare_csizedtext (stream->user_flags[i],&st->text)) {
3870 f |= (1 << i);
3871 break;
3873 if (flag && !j) return NIL;
3874 } while (st = st->next);
3875 tf = elt->user_flags & f; /* get set flags which match */
3876 return flag ? (f == tf) : !tf;
3879 /* Mail search an address list
3880 * Accepts: address list
3881 * string list
3882 * Returns: T if search found a match
3885 #define SEARCHBUFLEN (size_t) 2000
3886 #define SEARCHBUFSLOP (size_t) 5
3888 long mail_search_addr (ADDRESS *adr,STRINGLIST *st)
3890 ADDRESS *a,tadr;
3891 SIZEDTEXT txt;
3892 char tmp[SENDBUFLEN + 1];
3893 size_t i = SEARCHBUFLEN;
3894 size_t k;
3895 long ret = NIL;
3896 if (adr) {
3897 txt.data = (unsigned char *) fs_get (i + SEARCHBUFSLOP);
3898 /* never an error or next */
3899 tadr.error = NIL,tadr.next = NIL;
3900 /* write address list */
3901 for (txt.size = 0,a = adr; a; a = a->next) {
3902 k = (tadr.mailbox = a->mailbox) ? 4 + 2*strlen (a->mailbox) : 3;
3903 if (tadr.personal = a->personal) k += 3 + 2*strlen (a->personal);
3904 if (tadr.adl = a->adl) k += 3 + 2*strlen (a->adl);
3905 if (tadr.host = a->host) k += 3 + 2*strlen (a->host);
3906 if (tadr.personal || tadr.adl) k += 2;
3907 if (k < (SENDBUFLEN-10)) {/* ignore ridiculous addresses */
3908 tmp[0] = '\0';
3909 rfc822_write_address (tmp,&tadr);
3910 /* resize buffer if necessary */
3911 if (((k = strlen (tmp)) + txt.size) > i)
3912 fs_resize ((void **) &txt.data,SEARCHBUFSLOP + (i += SEARCHBUFLEN));
3913 /* add new address */
3914 memcpy (txt.data + txt.size,tmp,k);
3915 txt.size += k;
3916 /* another address follows */
3917 if (a->next) txt.data[txt.size++] = ',';
3920 txt.data[txt.size] = '\0'; /* tie off string */
3921 ret = mail_search_header (&txt,st);
3922 fs_give ((void **) &txt.data);
3924 return ret;
3927 /* Get string for low-memory searching
3928 * Accepts: readin function pointer
3929 * stream to use
3930 * number of bytes
3931 * gets data packet
3933 * mail stream
3934 * message number
3935 * descriptor string
3936 * option flags
3937 * Returns: NIL, always
3940 #define SEARCHSLOP 128
3942 char *mail_search_gets (readfn_t f,void *stream,unsigned long size,
3943 GETS_DATA *md)
3945 unsigned long i;
3946 char tmp[MAILTMPLEN+SEARCHSLOP+1];
3947 SIZEDTEXT st;
3948 /* better not be called unless searching */
3949 if (!md->stream->private.search.string) {
3950 sprintf (tmp,"Search botch, mbx = %.80s, %s = %lu[%.80s]",
3951 md->stream->mailbox,
3952 (md->flags & FT_UID) ? "UID" : "msg",md->msgno,md->what);
3953 fatal (tmp);
3955 /* initially no match for search */
3956 md->stream->private.search.result = NIL;
3957 /* make sure buffer clear */
3958 memset (st.data = (unsigned char *) tmp,'\0',
3959 (size_t) MAILTMPLEN+SEARCHSLOP+1);
3960 /* read first buffer */
3961 (*f) (stream,st.size = i = min (size,(long) MAILTMPLEN),tmp);
3962 /* search for text */
3963 if (mail_search_string (&st,NIL,&md->stream->private.search.string))
3964 md->stream->private.search.result = T;
3965 else if (size -= i) { /* more to do, blat slop down */
3966 memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP);
3967 do { /* read subsequent buffers one at a time */
3968 (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP);
3969 st.size = i + SEARCHSLOP;
3970 if (mail_search_string (&st,NIL,&md->stream->private.search.string))
3971 md->stream->private.search.result = T;
3972 else memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP);
3974 while ((size -= i) && !md->stream->private.search.result);
3976 if (size) { /* toss out everything after that */
3977 do (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp);
3978 while (size -= i);
3980 return NIL;
3983 /* Mail parse search criteria
3984 * Accepts: criteria
3985 * Returns: search program if parse successful, else NIL
3988 SEARCHPGM *mail_criteria (char *criteria)
3990 SEARCHPGM *pgm = NIL;
3991 char *criterion,*r,tmp[MAILTMPLEN];
3992 int f;
3993 if (criteria) { /* only if criteria defined */
3994 /* make writeable copy of criteria */
3995 criteria = cpystr (criteria);
3996 /* for each criterion */
3997 for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
3998 criterion; (criterion = strtok_r (NIL," ",&r))) {
3999 f = NIL; /* init then scan the criterion */
4000 switch (*ucase (criterion)) {
4001 case 'A': /* possible ALL, ANSWERED */
4002 if (!strcmp (criterion+1,"LL")) f = T;
4003 else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
4004 break;
4005 case 'B': /* possible BCC, BEFORE, BODY */
4006 if (!strcmp (criterion+1,"CC"))
4007 f = mail_criteria_string (&pgm->bcc,&r);
4008 else if (!strcmp (criterion+1,"EFORE"))
4009 f = mail_criteria_date (&pgm->before,&r);
4010 else if (!strcmp (criterion+1,"ODY"))
4011 f = mail_criteria_string (&pgm->body,&r);
4012 break;
4013 case 'C': /* possible CC */
4014 if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
4015 break;
4016 case 'D': /* possible DELETED */
4017 if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
4018 break;
4019 case 'F': /* possible FLAGGED, FROM */
4020 if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
4021 else if (!strcmp (criterion+1,"ROM"))
4022 f = mail_criteria_string (&pgm->from,&r);
4023 break;
4024 case 'K': /* possible KEYWORD */
4025 if (!strcmp (criterion+1,"EYWORD"))
4026 f = mail_criteria_string (&pgm->keyword,&r);
4027 break;
4029 case 'N': /* possible NEW */
4030 if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
4031 break;
4032 case 'O': /* possible OLD, ON */
4033 if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
4034 else if (!strcmp (criterion+1,"N"))
4035 f = mail_criteria_date (&pgm->on,&r);
4036 break;
4037 case 'R': /* possible RECENT */
4038 if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
4039 break;
4040 case 'S': /* possible SEEN, SINCE, SUBJECT */
4041 if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
4042 else if (!strcmp (criterion+1,"INCE"))
4043 f = mail_criteria_date (&pgm->since,&r);
4044 else if (!strcmp (criterion+1,"UBJECT"))
4045 f = mail_criteria_string (&pgm->subject,&r);
4046 break;
4047 case 'T': /* possible TEXT, TO */
4048 if (!strcmp (criterion+1,"EXT"))
4049 f = mail_criteria_string (&pgm->text,&r);
4050 else if (!strcmp (criterion+1,"O"))
4051 f = mail_criteria_string (&pgm->to,&r);
4052 break;
4053 case 'U': /* possible UN* */
4054 if (criterion[1] == 'N') {
4055 if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
4056 else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
4057 else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
4058 else if (!strcmp (criterion+2,"KEYWORD"))
4059 f = mail_criteria_string (&pgm->unkeyword,&r);
4060 else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
4062 break;
4063 default: /* we will barf below */
4064 break;
4066 if (!f) { /* if can't identify criterion */
4067 sprintf (tmp,"Unknown search criterion: %.30s",criterion);
4068 MM_LOG (tmp,ERROR);
4069 mail_free_searchpgm (&pgm);
4070 break;
4073 /* no longer need copy of criteria */
4074 fs_give ((void **) &criteria);
4076 return pgm;
4079 /* Parse a date
4080 * Accepts: pointer to date integer to return
4081 * pointer to strtok state
4082 * Returns: T if successful, else NIL
4085 int mail_criteria_date (unsigned short *date,char **r)
4087 STRINGLIST *s = NIL;
4088 MESSAGECACHE elt;
4089 /* parse the date and return fn if OK */
4090 int ret = (mail_criteria_string (&s,r) &&
4091 mail_parse_date (&elt,(char *) s->text.data) &&
4092 (*date = mail_shortdate (elt.year,elt.month,elt.day))) ?
4093 T : NIL;
4094 if (s) mail_free_stringlist (&s);
4095 return ret;
4098 /* Calculate shortdate from elt values
4099 * Accepts: year (0 = BASEYEAR)
4100 * month (1 = January)
4101 * day
4102 * Returns: shortdate
4105 unsigned short mail_shortdate (unsigned int year,unsigned int month,
4106 unsigned int day)
4108 return (year << 9) + (month << 5) + day;
4111 /* Parse a string
4112 * Accepts: pointer to stringlist
4113 * pointer to strtok state
4114 * Returns: T if successful, else NIL
4117 int mail_criteria_string (STRINGLIST **s,char **r)
4119 unsigned long n;
4120 char e,*d,*end = " ",*c = strtok_r (NIL,"",r);
4121 if (!c) return NIL; /* missing argument */
4122 switch (*c) { /* see what the argument is */
4123 case '{': /* literal string */
4124 n = strtoul (c+1,&d,10); /* get its length */
4125 if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') &&
4126 (!(*(c = d + n)) || (*c == ' '))) {
4127 e = *--c; /* store old delimiter */
4128 *c = '\377'; /* make sure not a space */
4129 strtok_r (c," ",r); /* reset the strtok mechanism */
4130 *c = e; /* put character back */
4131 break;
4133 case '\0': /* catch bogons */
4134 case ' ':
4135 return NIL;
4136 case '"': /* quoted string */
4137 if (strchr (c+1,'"')) end = "\"";
4138 else return NIL; /* falls through */
4139 default: /* atomic string */
4140 if (d = strtok_r (c,end,r)) n = strlen (d);
4141 else return NIL;
4142 break;
4144 while (*s) s = &(*s)->next; /* find tail of list */
4145 *s = mail_newstringlist (); /* make new entry */
4146 /* return the data */
4147 (*s)->text.data = (unsigned char *) cpystr (d);
4148 (*s)->text.size = n;
4149 return T;
4152 /* Mail parse set from string
4153 * Accepts: string to parse
4154 * pointer to updated string pointer for return
4155 * Returns: set with pointer updated, or NIL if error
4158 SEARCHSET *mail_parse_set (char *s,char **ret)
4160 SEARCHSET *cur;
4161 SEARCHSET *set = NIL;
4162 while (isdigit (*s)) {
4163 if (!set) cur = set = mail_newsearchset ();
4164 else cur = cur->next = mail_newsearchset ();
4165 /* parse value */
4166 if (!(cur->first = strtoul (s,&s,10)) ||
4167 ((*s == ':') && !(isdigit (*++s) && (cur->last = strtoul (s,&s,10)))))
4168 break; /* bad value or range */
4169 if (*s == ',') ++s; /* point to next value if more */
4170 else { /* end of set */
4171 *ret = s; /* set return pointer */
4172 return set; /* return set */
4175 mail_free_searchset (&set); /* failure, punt partial set */
4176 return NIL;
4180 /* Mail append to set
4181 * Accepts: head of search set or NIL to do nothing
4182 * message to add
4183 * Returns: tail of search set or NIL if did nothing
4186 SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno)
4188 if (set) { /* find tail */
4189 while (set->next) set = set->next;
4190 /* start of set if no first member */
4191 if (!set->first) set->first = msgno;
4192 else if (msgno == (set->last ? set->last : set->first) + 1)
4193 set->last = msgno; /* extend range if 1 past current */
4194 else (set = set->next = mail_newsearchset ())->first = msgno;
4196 return set;
4199 /* Mail sort messages
4200 * Accepts: mail stream
4201 * character set
4202 * search program
4203 * sort program
4204 * option flags
4205 * Returns: vector of sorted message sequences or NIL if error
4208 unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
4209 SORTPGM *pgm,long flags)
4211 unsigned long *ret = NIL;
4212 if (stream->dtb) /* do the driver's action */
4213 ret = (*(stream->dtb->sort ? stream->dtb->sort : mail_sort_msgs))
4214 (stream,charset,spg,pgm,flags);
4215 /* flush search/sort programs if requested */
4216 if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
4217 if (flags & SO_FREE) mail_free_sortpgm (&pgm);
4218 return ret;
4221 /* Mail sort messages work routine
4222 * Accepts: mail stream
4223 * character set
4224 * search program
4225 * sort program
4226 * option flags
4227 * Returns: vector of sorted message sequences or NIL if error
4230 unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
4231 SORTPGM *pgm,long flags)
4233 unsigned long i;
4234 SORTCACHE **sc;
4235 unsigned long *ret = NIL;
4236 if (spg) { /* only if a search needs to be done */
4237 int silent = stream->silent;
4238 stream->silent = T; /* don't pass up mm_searched() events */
4239 /* search for messages */
4240 mail_search_full (stream,charset,spg,NIL);
4241 stream->silent = silent; /* restore silence state */
4243 /* initialize progress counters */
4244 pgm->nmsgs = pgm->progress.cached = 0;
4245 /* pass 1: count messages to sort */
4246 for (i = 1; i <= stream->nmsgs; ++i)
4247 if (mail_elt (stream,i)->searched) pgm->nmsgs++;
4248 if (pgm->nmsgs) { /* pass 2: sort cache */
4249 sc = mail_sort_loadcache (stream,pgm);
4250 /* pass 3: sort messages */
4251 if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
4252 fs_give ((void **) &sc); /* don't need sort vector any more */
4254 /* empty sort results */
4255 else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
4256 sizeof (unsigned long));
4257 /* also return via callback if requested */
4258 if (mailsortresults) (*mailsortresults) (stream,ret,pgm->nmsgs);
4259 return ret; /* return sort results */
4262 /* Mail sort sortcache vector
4263 * Accepts: mail stream
4264 * sort program
4265 * sortcache vector
4266 * option flags
4267 * Returns: vector of sorted message sequences or NIL if error
4270 unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc,
4271 long flags)
4273 unsigned long i,*ret;
4274 /* pass 3: sort messages */
4275 qsort ((void *) sc,pgm->nmsgs,sizeof (SORTCACHE *),mail_sort_compare);
4276 /* optional post sorting */
4277 if (pgm->postsort) (*pgm->postsort) ((void *) sc);
4278 /* pass 4: return results */
4279 ret = (unsigned long *) fs_get ((pgm->nmsgs+1) * sizeof (unsigned long));
4280 if (flags & SE_UID) /* UID or msgno? */
4281 for (i = 0; i < pgm->nmsgs; i++) ret[i] = mail_uid (stream,sc[i]->num);
4282 else for (i = 0; i < pgm->nmsgs; i++) ret[i] = sc[i]->num;
4283 ret[pgm->nmsgs] = 0; /* tie off message list */
4284 return ret;
4287 /* Mail load sortcache
4288 * Accepts: mail stream, already searched
4289 * sort program
4290 * Returns: vector of sortcache pointers matching search
4293 static STRINGLIST maildateline = {{(unsigned char *) "date",4},NIL};
4294 static STRINGLIST mailrnfromline = {{(unsigned char *) ">from",5},NIL};
4295 static STRINGLIST mailfromline = {{(unsigned char *) "from",4},
4296 &mailrnfromline};
4297 static STRINGLIST mailtonline = {{(unsigned char *) "to",2},NIL};
4298 static STRINGLIST mailccline = {{(unsigned char *) "cc",2},NIL};
4299 static STRINGLIST mailsubline = {{(unsigned char *) "subject",7},NIL};
4301 SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm)
4303 char *t,*v,*x,tmp[MAILTMPLEN];
4304 SORTPGM *pg;
4305 SORTCACHE *s,**sc;
4306 MESSAGECACHE *elt,telt;
4307 ENVELOPE *env;
4308 ADDRESS *adr = NIL;
4309 unsigned long i = (pgm->nmsgs) * sizeof (SORTCACHE *);
4310 sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
4311 /* see what needs to be loaded */
4312 for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
4313 if ((elt = mail_elt (stream,i))->searched) {
4314 sc[pgm->progress.cached++] =
4315 s = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
4316 s->pgm = pgm; /* note sort program */
4317 s->num = i;
4318 /* get envelope if cached */
4319 if (stream->scache) env = (i == stream->msgno) ? stream->env : NIL;
4320 else env = elt->private.msg.env;
4321 for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
4322 case SORTARRIVAL: /* sort by arrival date */
4323 if (!s->arrival) {
4324 /* internal date unknown but can get? */
4325 if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
4326 sprintf (tmp,"%lu",i);
4327 mail_fetch_fast (stream,tmp,NIL);
4329 /* wrong thing before 3-Jan-1970 */
4330 s->arrival = elt->day ? mail_longdate (elt) : 1;
4331 s->dirty = T;
4333 break;
4334 case SORTSIZE: /* sort by message size */
4335 if (!s->size) {
4336 if (!elt->rfc822_size) {
4337 sprintf (tmp,"%lu",i);
4338 mail_fetch_fast (stream,tmp,NIL);
4340 s->size = elt->rfc822_size ? elt->rfc822_size : 1;
4341 s->dirty = T;
4343 break;
4345 case SORTDATE: /* sort by date */
4346 if (!s->date) {
4347 if (env) t = env->date;
4348 else if ((t = mail_fetch_header (stream,i,NIL,&maildateline,NIL,
4349 FT_INTERNAL | FT_PEEK)) &&
4350 (t = strchr (t,':')))
4351 for (x = ++t; x = strpbrk (x,"\012\015"); x++)
4352 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4353 case ' ': /* erase continuation newlines */
4354 case '\t':
4355 memmove (x,v,strlen (v));
4356 break;
4357 default: /* tie off extraneous text */
4358 *x = x[1] = '\0';
4360 /* skip leading whitespace */
4361 if (t) while ((*t == ' ') || (*t == '\t')) t++;
4362 /* parse date from Date: header */
4363 if (!(t && mail_parse_date (&telt,t) &&
4364 (s->date = mail_longdate (&telt)))) {
4365 /* failed, use internal date */
4366 if (!(s->date = s->arrival)) {
4367 /* internal date unknown but can get? */
4368 if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
4369 sprintf (tmp,"%lu",i);
4370 mail_fetch_fast (stream,tmp,NIL);
4372 /* wrong thing before 3-Jan-1970 */
4373 s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
4376 s->dirty = T;
4378 break;
4380 case SORTFROM: /* sort by first from */
4381 if (!s->from) {
4382 if (env) s->from = env->from && env->from->mailbox ?
4383 cpystr (env->from->mailbox) : NIL;
4384 else if ((t = mail_fetch_header (stream,i,NIL,&mailfromline,NIL,
4385 FT_INTERNAL | FT_PEEK)) &&
4386 (t = strchr (t,':'))) {
4387 for (x = ++t; x = strpbrk (x,"\012\015"); x++)
4388 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4389 case ' ': /* erase continuation newlines */
4390 case '\t':
4391 memmove (x,v,strlen (v));
4392 break;
4393 case 'f': /* continuation but with extra "From:" */
4394 case 'F':
4395 if (v = strchr (v,':')) {
4396 memmove (x,v+1,strlen (v+1));
4397 break;
4399 default: /* tie off extraneous text */
4400 *x = x[1] = '\0';
4402 if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
4403 s->from = adr->mailbox;
4404 adr->mailbox = NIL;
4405 mail_free_address (&adr);
4408 if (!s->from) s->from = cpystr ("");
4409 s->dirty = T;
4411 break;
4413 case SORTTO: /* sort by first to */
4414 if (!s->to) {
4415 if (env) s->to = env->to && env->to->mailbox ?
4416 cpystr (env->to->mailbox) : NIL;
4417 else if ((t = mail_fetch_header (stream,i,NIL,&mailtonline,NIL,
4418 FT_INTERNAL | FT_PEEK)) &&
4419 (t = strchr (t,':'))) {
4420 for (x = ++t; x = strpbrk (x,"\012\015"); 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 case 't': /* continuation but with extra "To:" */
4427 case 'T':
4428 if (v = strchr (v,':')) {
4429 memmove (x,v+1,strlen (v+1));
4430 break;
4432 default: /* tie off extraneous text */
4433 *x = x[1] = '\0';
4435 if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
4436 s->to = adr->mailbox;
4437 adr->mailbox = NIL;
4438 mail_free_address (&adr);
4441 if (!s->to) s->to = cpystr ("");
4442 s->dirty = T;
4444 break;
4446 case SORTCC: /* sort by first cc */
4447 if (!s->cc) {
4448 if (env) s->cc = env->cc && env->cc->mailbox ?
4449 cpystr (env->cc->mailbox) : NIL;
4450 else if ((t = mail_fetch_header (stream,i,NIL,&mailccline,NIL,
4451 FT_INTERNAL | FT_PEEK)) &&
4452 (t = strchr (t,':'))) {
4453 for (x = ++t; x = strpbrk (x,"\012\015"); x++)
4454 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4455 case ' ': /* erase continuation newlines */
4456 case '\t':
4457 memmove (x,v,strlen (v));
4458 break;
4459 case 't': /* continuation but with extra "To:" */
4460 case 'T':
4461 if (v = strchr (v,':')) {
4462 memmove (x,v+1,strlen (v+1));
4463 break;
4465 default: /* tie off extraneous text */
4466 *x = x[1] = '\0';
4468 if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
4469 s->cc = adr->mailbox;
4470 adr->mailbox = NIL;
4471 mail_free_address (&adr);
4474 if (!s->cc) s->cc = cpystr ("");
4475 s->dirty = T;
4477 break;
4479 case SORTSUBJECT: /* sort by subject */
4480 if (!s->subject) {
4481 /* get subject from envelope if have one */
4482 if (env) t = env->subject ? env->subject : "";
4483 /* otherwise snarf from header text */
4484 else if ((t = mail_fetch_header (stream,i,NIL,&mailsubline,
4485 NIL,FT_INTERNAL | FT_PEEK)) &&
4486 (t = strchr (t,':')))
4487 for (x = ++t; x = strpbrk (x,"\012\015"); x++)
4488 switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
4489 case ' ': /* erase continuation newlines */
4490 case '\t':
4491 memmove (x,v,strlen (v));
4492 break;
4493 default: /* tie off extraneous text */
4494 *x = x[1] = '\0';
4496 else t = ""; /* empty subject */
4497 /* strip and cache subject */
4498 s->refwd = mail_strip_subject (t,&s->subject);
4499 s->dirty = T;
4501 break;
4502 default:
4503 fatal ("Unknown sort function");
4506 return sc;
4509 /* Strip subjects of extra spaces and leading and trailing cruft for sorting
4510 * Accepts: unstripped subject
4511 * pointer to return stripped subject, in cpystr form
4512 * Returns: T if subject had a re/fwd, NIL otherwise
4515 unsigned int mail_strip_subject (char *t,char **ret)
4517 SIZEDTEXT src,dst;
4518 unsigned long i,slen;
4519 char c,*s,*x;
4520 unsigned int refwd = NIL;
4521 if (src.size = strlen (t)) { /* have non-empty subject? */
4522 src.data = (unsigned char *) t;
4523 /* Step 1 */
4524 /* make copy, convert MIME2 if needed */
4525 *ret = s = (utf8_mime2text (&src,&dst,U8T_CANONICAL) &&
4526 (src.data != dst.data)) ? (char *) dst.data : cpystr (t);
4527 /* convert spaces to tab, strip extra spaces */
4528 for (x = t = s, c = 'x'; *t; t++) {
4529 if (c != ' ') c = *x++ = ((*t == '\t') ? ' ' : *t);
4530 else if ((*t != '\t') && (*t != ' ')) c = *x++ = *t;
4532 *x = '\0'; /* tie off string */
4533 /* Step 2 */
4534 for (slen = dst.size; s; slen = strlen (s)) {
4535 for (t = s + slen; t > s; ) switch (t[-1]) {
4536 case ' ': case '\t': /* WSP */
4537 *--t = '\0'; /* just remove it */
4538 break;
4539 case ')': /* possible "(fwd)" */
4540 if ((t >= (s + 5)) && (t[-5] == '(') &&
4541 ((t[-4] == 'F') || (t[-4] == 'f')) &&
4542 ((t[-3] == 'W') || (t[-3] == 'w')) &&
4543 ((t[-2] == 'D') || (t[-2] == 'd'))) {
4544 *(t -= 5) = '\0'; /* remove "(fwd)" */
4545 refwd = T; /* note a re/fwd */
4546 break;
4548 default: /* not a subj-trailer */
4549 t = s;
4550 break;
4552 /* Steps 3-5 */
4553 for (t = s; t; ) switch (*s) {
4554 case ' ': case '\t': /* WSP */
4555 s = t = mail_strip_subject_wsp (s + 1);
4556 break;
4557 case 'r': case 'R': /* possible "re" */
4558 if (((s[1] == 'E') || (s[1] == 'e')) &&
4559 (t = mail_strip_subject_wsp (s + 2)) &&
4560 (t = mail_strip_subject_blob (t)) && (*t == ':')) {
4561 s = ++t; /* found "re" */
4562 refwd = T; /* definitely a re/fwd at this point */
4564 else t = NIL; /* found subj-middle */
4565 break;
4566 case 'f': case 'F': /* possible "fw" or "fwd" */
4567 if (((s[1] == 'w') || (s[1] == 'W')) &&
4568 (((s[2] == 'd') || (s[2] == 'D')) ?
4569 (t = mail_strip_subject_wsp (s + 3)) :
4570 (t = mail_strip_subject_wsp (s + 2))) &&
4571 (t = mail_strip_subject_blob (t)) && (*t == ':')) {
4572 s = ++t; /* found "fwd" */
4573 refwd = T; /* definitely a re/fwd at this point */
4575 else t = NIL; /* found subj-middle */
4576 break;
4577 case '[': /* possible subj-blob */
4578 if ((t = mail_strip_subject_blob (s)) && *t) s = t;
4579 else t = NIL; /* found subj-middle */
4580 break;
4581 default:
4582 t = NIL; /* found subj-middle */
4583 break;
4585 /* Step 6 */
4586 /* Netscape-style "[Fwd: ...]"? */
4587 if ((*s == '[') && ((s[1] == 'F') || (s[1] == 'f')) &&
4588 ((s[2] == 'W') || (s[2] == 'w')) &&
4589 ((s[3] == 'D') || (s[3] == 'd')) && (s[4] == ':') &&
4590 (s[i = strlen (s) - 1] == ']')) {
4591 s[i] = '\0'; /* flush closing "]" */
4592 s += 5; /* and leading "[Fwd:" */
4593 refwd = T; /* definitely a re/fwd at this point */
4595 else break; /* don't need to loop back to step 2 */
4597 if (s != (t = *ret)) { /* removed leading text? */
4598 s = *ret = cpystr (s); /* yes, make a fresh return copy */
4599 fs_give ((void **) &t); /* flush old copy */
4602 else *ret = cpystr (""); /* empty subject */
4603 return refwd; /* return re/fwd state */
4606 /* Strip subject wsp helper routine
4607 * Accepts: text
4608 * Returns: pointer to text after blob
4611 char *mail_strip_subject_wsp (char *s)
4613 while ((*s == ' ') || (*s == '\t')) s++;
4614 return s;
4618 /* Strip subject blob helper routine
4619 * Accepts: text
4620 * Returns: pointer to text after any blob, NIL if blob-like but not blob
4623 char *mail_strip_subject_blob (char *s)
4625 if (*s != '[') return s; /* not a blob, ignore */
4626 /* search for end of blob */
4627 while (*++s != ']') if ((*s == '[') || !*s) return NIL;
4628 return mail_strip_subject_wsp (s + 1);
4631 /* Sort compare messages
4632 * Accept: first message sort cache element
4633 * second message sort cache element
4634 * Returns: -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2
4637 int mail_sort_compare (const void *a1,const void *a2)
4639 int i = 0;
4640 SORTCACHE *s1 = *(SORTCACHE **) a1;
4641 SORTCACHE *s2 = *(SORTCACHE **) a2;
4642 SORTPGM *pgm = s1->pgm;
4643 if (!s1->sorted) { /* this one sorted yet? */
4644 s1->sorted = T;
4645 pgm->progress.sorted++; /* another sorted message */
4647 if (!s2->sorted) { /* this one sorted yet? */
4648 s2->sorted = T;
4649 pgm->progress.sorted++; /* another sorted message */
4651 do {
4652 switch (pgm->function) { /* execute search program */
4653 case SORTDATE: /* sort by date */
4654 i = compare_ulong (s1->date,s2->date);
4655 break;
4656 case SORTARRIVAL: /* sort by arrival date */
4657 i = compare_ulong (s1->arrival,s2->arrival);
4658 break;
4659 case SORTSIZE: /* sort by message size */
4660 i = compare_ulong (s1->size,s2->size);
4661 break;
4662 case SORTFROM: /* sort by first from */
4663 i = compare_cstring (s1->from,s2->from);
4664 break;
4665 case SORTTO: /* sort by first to */
4666 i = compare_cstring (s1->to,s2->to);
4667 break;
4668 case SORTCC: /* sort by first cc */
4669 i = compare_cstring (s1->cc,s2->cc);
4670 break;
4671 case SORTSUBJECT: /* sort by subject */
4672 i = compare_cstring (s1->subject,s2->subject);
4673 break;
4675 if (pgm->reverse) i = -i; /* flip results if necessary */
4677 while (pgm = i ? NIL : pgm->next);
4678 /* return result, avoid 0 if at all possible */
4679 return i ? i : compare_ulong (s1->num,s2->num);
4682 /* Return message date as an unsigned long seconds since time began
4683 * Accepts: message cache pointer
4684 * Returns: unsigned long of date
4686 * This routine, like most UNIX systems, is clueless about leap seconds.
4687 * Thus, it treats 23:59:60 as equivalent to 00:00:00 the next day.
4689 * This routine forces any early hours on 1-Jan-1970 in oriental timezones
4690 * to be 1-Jan-1970 00:00:00 UTC, so as to avoid negative longdates.
4693 unsigned long mail_longdate (MESSAGECACHE *elt)
4695 unsigned long m = elt->month ? elt->month : 1;
4696 unsigned long yr = elt->year + BASEYEAR;
4697 /* number of days since time began */
4698 unsigned long ret = (elt->day ? (elt->day - 1) : 0)
4699 + 30 * (m - 1) + ((m + (m > 8)) / 2)
4700 #ifndef USEJULIANCALENDAR
4701 #ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
4702 + ((yr / 400) - (BASEYEAR / 400)) - ((yr / 100) - (BASEYEAR / 100))
4703 #ifdef Y4KBUGFIX
4704 - ((yr / 4000) - (BASEYEAR / 4000))
4705 #endif
4706 - ((m < 3) ?
4707 !(yr % 4) && ((yr % 100) || (!(yr % 400)
4708 #ifdef Y4KBUGFIX
4709 && (yr % 4000)
4710 #endif
4711 )) : 2)
4712 #else /* Orthodox calendar */
4713 + ((2*(yr / 900)) - (2*(BASEYEAR / 900)))
4714 + (((yr % 900) >= 200) - ((BASEYEAR % 900) >= 200))
4715 + (((yr % 900) >= 600) - ((BASEYEAR % 900) >= 600))
4716 - ((yr / 100) - (BASEYEAR / 100))
4717 - ((m < 3) ?
4718 !(yr % 4) && ((yr % 100) || ((yr % 900) == 200) || ((yr % 900) == 600))
4719 : 2)
4720 #endif
4721 #endif
4722 + elt->year * 365 + (((unsigned long) (elt->year + (BASEYEAR % 4))) / 4);
4723 ret *= 24; ret += elt->hours; /* date value in hours */
4724 ret *= 60; ret +=elt->minutes;/* date value in minutes */
4725 yr = (elt->zhours * 60) + elt->zminutes;
4726 if (elt->zoccident) ret += yr;/* occidental timezone, make UTC */
4727 else if (ret < yr) return 0; /* still 31-Dec-1969 in UTC */
4728 else ret -= yr; /* oriental timezone, make UTC */
4729 ret *= 60; ret += elt->seconds;
4730 return ret;
4733 /* Mail thread messages
4734 * Accepts: mail stream
4735 * thread type
4736 * character set
4737 * search program
4738 * option flags
4739 * Returns: thread node tree or NIL if error
4742 THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset,
4743 SEARCHPGM *spg,long flags)
4745 THREADNODE *ret = NIL;
4746 if (stream->dtb) /* must have a live driver */
4747 ret = stream->dtb->thread ? /* do driver's action if available */
4748 (*stream->dtb->thread) (stream,type,charset,spg,flags) :
4749 mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
4750 /* flush search/sort programs if requested */
4751 if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
4752 return ret;
4756 /* Mail thread messages
4757 * Accepts: mail stream
4758 * thread type
4759 * character set
4760 * search program
4761 * option flags
4762 * sorter routine
4763 * Returns: thread node tree or NIL if error
4766 THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset,
4767 SEARCHPGM *spg,long flags,sorter_t sorter)
4769 THREADER *t;
4770 for (t = &mailthreadlist; t; t = t->next)
4771 if (!compare_cstring (type,t->name)) {
4772 THREADNODE *ret = (*t->dispatch) (stream,charset,spg,flags,sorter);
4773 if (mailthreadresults) (*mailthreadresults) (stream,ret);
4774 return ret;
4776 MM_LOG ("No such thread type",ERROR);
4777 return NIL;
4780 /* Mail thread ordered subject
4781 * Accepts: mail stream
4782 * character set
4783 * search program
4784 * option flags
4785 * sorter routine
4786 * Returns: thread node tree
4789 THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset,
4790 SEARCHPGM *spg,long flags,
4791 sorter_t sorter)
4793 THREADNODE *thr = NIL;
4794 THREADNODE *cur,*top,**tc;
4795 SORTPGM pgm,pgm2;
4796 SORTCACHE *s;
4797 unsigned long i,j,*lst,*ls;
4798 /* sort by subject+date */
4799 memset (&pgm,0,sizeof (SORTPGM));
4800 memset (&pgm2,0,sizeof (SORTPGM));
4801 pgm.function = SORTSUBJECT;
4802 pgm.next = &pgm2;
4803 pgm2.function = SORTDATE;
4804 if (lst = (*sorter) (stream,charset,spg,&pgm,flags & ~(SE_FREE | SE_UID))){
4805 if (*(ls = lst)) { /* create thread */
4806 /* note first subject */
4807 cur = top = thr = mail_newthreadnode
4808 ((SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE));
4809 /* note its number */
4810 cur->num = (flags & SE_UID) ? mail_uid (stream,*lst) : *lst;
4811 i = 1; /* number of threads */
4812 while (*ls) { /* build tree */
4813 /* subjects match? */
4814 s = (SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE);
4815 if (compare_cstring (top->sc->subject,s->subject)) {
4816 i++; /* have a new thread */
4817 top = top->branch = cur = mail_newthreadnode (s);
4819 /* start a child of the top */
4820 else if (cur == top) cur = cur->next = mail_newthreadnode (s);
4821 /* sibling of child */
4822 else cur = cur->branch = mail_newthreadnode (s);
4823 /* set to msgno or UID as needed */
4824 cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
4826 /* make threadnode cache */
4827 tc = (THREADNODE **) fs_get (i * sizeof (THREADNODE *));
4828 /* load threadnode cache */
4829 for (j = 0, cur = thr; cur; cur = cur->branch) tc[j++] = cur;
4830 if (i != j) fatal ("Threadnode cache confusion");
4831 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
4832 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
4833 tc[j]->branch = NIL; /* end of root */
4834 thr = tc[0]; /* head of data */
4835 fs_give ((void **) &tc);
4837 fs_give ((void **) &lst);
4839 return thr;
4842 /* Mail thread references
4843 * Accepts: mail stream
4844 * character set
4845 * search program
4846 * option flags
4847 * sorter routine
4848 * Returns: thread node tree
4851 #define REFHASHSIZE 1009 /* arbitrary prime for hash table size */
4853 /* Reference threading container, as described in Jamie Zawinski's web page
4854 * (http://www.jwz.org/doc/threading.html) for this algorithm. These are
4855 * stored as extended data in the hash table (called "id_table" in JWZ's
4856 * document) and are maintained by the hash table routines. The hash table
4857 * routines implement extended data as additional void* words at the end of
4858 * each bucket, hence these strange macros instead of a struct which would
4859 * have been more straightforward.
4862 #define THREADLINKS 3 /* number of thread links */
4864 #define CACHE(data) ((SORTCACHE *) (data)[0])
4865 #define PARENT(data) ((container_t) (data)[1])
4866 #define SETPARENT(data,value) ((container_t) (data[1] = value))
4867 #define SIBLING(data) ((container_t) (data)[2])
4868 #define SETSIBLING(data,value) ((container_t) (data[2] = value))
4869 #define CHILD(data) ((container_t) (data)[3])
4870 #define SETCHILD(data,value) ((container_t) (data[3] = value))
4872 THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset,
4873 SEARCHPGM *spg,long flags,sorter_t sorter)
4875 MESSAGECACHE *elt,telt;
4876 ENVELOPE *env;
4877 SORTCACHE *s;
4878 STRINGLIST *st;
4879 HASHENT *he;
4880 THREADNODE **tc,*cur,*lst,*nxt,*sis,*msg;
4881 container_t con,nxc,prc,sib;
4882 void **sub;
4883 char *t,tmp[MAILTMPLEN];
4884 unsigned long j,nmsgs;
4885 unsigned long i = stream->nmsgs * sizeof (SORTCACHE *);
4886 SORTCACHE **sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
4887 HASHTAB *ht = hash_create (REFHASHSIZE);
4888 THREADNODE *root = NIL;
4889 if (spg) { /* only if a search needs to be done */
4890 int silent = stream->silent;
4891 stream->silent = T; /* don't pass up mm_searched() events */
4892 /* search for messages */
4893 mail_search_full (stream,charset,spg,NIL);
4894 stream->silent = silent; /* restore silence state */
4897 /* create SORTCACHE vector of requested msgs */
4898 for (i = 1, nmsgs = 0; i <= stream->nmsgs; ++i)
4899 if (mail_elt (stream,i)->searched)
4900 (sc[nmsgs++] = (SORTCACHE *)(*mailcache)(stream,i,CH_SORTCACHE))->num =i;
4901 /* separate pass so can do overview fetch lookahead */
4902 for (i = 0; i < nmsgs; ++i) { /* for each requested message */
4903 /* is anything missing in its SORTCACHE? */
4904 if (!((s = sc[i])->date && s->subject && s->message_id && s->references)) {
4905 /* driver has an overview mechanism? */
4906 if (stream->dtb && stream->dtb->overview) {
4907 /* yes, find following unloaded entries */
4908 for (j = i + 1; (j < nmsgs) && !sc[j]->references; ++j);
4909 sprintf (tmp,"%lu",mail_uid (stream,s->num));
4910 if (i != --j) /* end of range different? */
4911 sprintf (tmp + strlen (tmp),":%lu",mail_uid (stream,sc[j]->num));
4912 /* load via overview mechanism */
4913 mail_fetch_overview (stream,tmp,mail_thread_loadcache);
4915 /* still missing data? */
4916 if (!s->date || !s->subject || !s->message_id || !s->references) {
4917 /* try to load data from envelope */
4918 if (env = mail_fetch_structure (stream,s->num,NIL,NIL)) {
4919 if (!s->date && env->date && mail_parse_date (&telt,env->date))
4920 s->date = mail_longdate (&telt);
4921 if (!s->subject && env->subject)
4922 s->refwd =
4923 mail_strip_subject (env->subject,&s->subject);
4924 if (!s->message_id && env->message_id && *env->message_id)
4925 s->message_id = mail_thread_parse_msgid (env->message_id,NIL);
4926 if (!s->references && /* use References: or In-Reply-To: */
4927 !(s->references =
4928 mail_thread_parse_references (env->references,T)))
4929 s->references = mail_thread_parse_references(env->in_reply_to,NIL);
4931 /* last resort */
4932 if (!s->date && !(s->date = s->arrival)) {
4933 /* internal date unknown but can get? */
4934 if (!(elt = mail_elt (stream,s->num))->day &&
4935 !(stream->dtb->flags & DR_NOINTDATE)) {
4936 sprintf (tmp,"%lu",s->num);
4937 mail_fetch_fast (stream,tmp,NIL);
4939 /* wrong thing before 3-Jan-1970 */
4940 s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
4942 if (!s->subject) s->subject = cpystr ("");
4943 if (!s->references) s->references = mail_newstringlist ();
4944 s->dirty = T;
4948 /* Step 1 (preliminary) */
4949 /* generate unique string */
4950 sprintf (tmp,"%s.%lx.%lx@%s",stream->mailbox,stream->uid_validity,
4951 mail_uid (stream,s->num),mylocalhost ());
4952 /* flush old unique string if not message-id */
4953 if (s->unique && (s->unique != s->message_id))
4954 fs_give ((void **) &s->unique);
4955 s->unique = s->message_id ? /* don't permit Message ID duplicates */
4956 (hash_lookup (ht,s->message_id) ? cpystr (tmp) : s->message_id) :
4957 (s->message_id = cpystr (tmp));
4958 /* add unique string to hash table */
4959 hash_add (ht,s->unique,s,THREADLINKS);
4961 /* Step 1 */
4962 for (i = 0; i < nmsgs; ++i) { /* for each message in sortcache */
4963 /* Step 1A */
4964 if ((st = (s = sc[i])->references) && st->text.data)
4965 for (con = hash_lookup_and_add (ht,(char *) st->text.data,NIL,
4966 THREADLINKS); st = st->next; con = nxc) {
4967 nxc = hash_lookup_and_add (ht,(char *) st->text.data,NIL,THREADLINKS);
4968 /* only if no parent & won't introduce loop */
4969 if (!PARENT (nxc) && !mail_thread_check_child (con,nxc)) {
4970 SETPARENT (nxc,con); /* establish parent/child link */
4971 /* other children become sibling of this one */
4972 SETSIBLING (nxc,CHILD (con));
4973 SETCHILD (con,nxc); /* set as child of parent */
4976 else con = NIL; /* else message has no ancestors */
4977 /* Step 1B */
4978 if ((prc = PARENT ((nxc = hash_lookup (ht,s->unique)))) &&
4979 (prc != con)) { /* break links if have a different parent */
4980 SETPARENT (nxc,NIL); /* easy if direct child */
4981 if (nxc == CHILD (prc)) SETCHILD (prc,SIBLING (nxc));
4982 else { /* otherwise hunt through sisters */
4983 for (sib = CHILD (prc); nxc != SIBLING (sib); sib = SIBLING (sib));
4984 SETSIBLING (sib,SIBLING (nxc));
4986 SETSIBLING (nxc,NIL); /* no more little sisters either */
4987 prc = NIL; /* no more parent set */
4989 /* need to set parent, and parent is good? */
4990 if (!prc && !mail_thread_check_child (con,nxc)) {
4991 SETPARENT (nxc,con); /* establish parent/child link */
4992 if (con) { /* if non-root parent, set parent's child */
4993 if (CHILD (con)) { /* have a child already */
4994 /* find youngest daughter */
4995 for (con = CHILD (con); SIBLING (con); con = SIBLING (con));
4996 SETSIBLING (con,nxc); /* add new baby sister */
4998 else SETCHILD (con,nxc);/* set as only child */
5002 fs_give ((void **) &sc); /* finished with sortcache vector */
5004 /* Step 2 */
5005 /* search hash table for parentless messages */
5006 for (i = 0, prc = con = NIL; i < ht->size; i++)
5007 for (he = ht->table[i]; he; he = he->next)
5008 if (!PARENT ((nxc = he->data))) {
5009 /* sibling of previous parentless message */
5010 if (con) con = SETSIBLING (con,nxc);
5011 else prc = con = nxc; /* first parentless message */
5013 /* Once the dummy containers are pruned, we no longer need the parent
5014 * information, so we can convert the containers to THREADNODEs. Since
5015 * we don't need the id_table any more either, we can reset the hash table
5016 * and reuse it as a subject_table. Resetting the hash table will also
5017 * destroy the containers.
5019 /* Step 3 */
5020 /* prune dummies, convert to threadnode */
5021 root = mail_thread_c2node (stream,mail_thread_prune_dummy (prc,NIL),flags);
5022 /* Step 4 */
5023 /* make buffer for sorting */
5024 tc = (THREADNODE **) fs_get (nmsgs * sizeof (THREADNODE *));
5025 /* load threadcache and count nodes to sort */
5026 for (i = 0, cur = root; cur ; cur = cur->branch) tc[i++] = cur;
5027 if (i > 1) { /* only if need to sort */
5028 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
5029 /* relink siblings */
5030 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
5031 tc[j]->branch = NIL; /* end of root */
5032 root = tc[0]; /* establish new root */
5034 /* Step 5A */
5035 hash_reset (ht); /* discard containers, reset ht */
5036 /* Step 5B */
5037 for (cur = root; cur; cur = cur->branch)
5038 if ((t = (nxt = (cur->sc ? cur : cur->next))->sc->subject) && *t) {
5039 /* add new subject to hash table */
5040 if (!(sub = hash_lookup (ht,t))) hash_add (ht,t,cur,0);
5041 /* if one in table not dummy and */
5042 else if ((s = (lst = (THREADNODE *) sub[0])->sc) &&
5043 /* current dummy, or not re/fwd and table is */
5044 (!cur->sc || (!nxt->sc->refwd && s->refwd)))
5045 sub[0] = (void *) cur; /* replace with this message */
5048 /* Step 5C */
5049 for (cur = root, sis = NIL; cur; cur = msg) {
5050 /* do nothing if current message or no sub */
5051 if (!(t = (cur->sc ? cur : cur->next)->sc->subject) || !*t ||
5052 ((lst = (THREADNODE *) (sub = hash_lookup (ht,t))[0]) == cur))
5053 msg = (sis = cur)->branch;
5054 else if (!lst->sc) { /* is message in the table a dummy? */
5055 /* find youngest daughter of msg in table */
5056 for (msg = lst->next; msg->branch; msg = msg->branch);
5057 if (!cur->sc) { /* current message a dummy? */
5058 msg->branch = cur->next;/* current's daughter now dummy's youngest */
5059 msg = cur->branch; /* continue scan at younger sister */
5060 /* now delete this node */
5061 cur->branch = cur->next = NIL;
5062 mail_free_threadnode (&cur);
5064 else { /* current message not a dummy */
5065 msg->branch = cur; /* append as youngest daughter */
5066 msg = cur->branch; /* continue scan at younger sister */
5067 cur->branch = NIL; /* lose our younger sisters */
5070 else { /* no dummies, is current re/fwd, table not? */
5071 if (cur->sc->refwd && !lst->sc->refwd) {
5072 if (lst->next) { /* find youngest daughter of msg in table */
5073 for (msg = lst->next; msg->branch; msg = msg->branch);
5074 msg->branch = cur; /* append as youngest daughter */
5076 else lst->next = cur; /* no children, so make the eldest daughter */
5079 else { /* no re/fwd, create a new dummy */
5080 msg = mail_newthreadnode (NIL);
5081 if (lst == root) { /* msg in table is root? */
5082 root = lst->branch; /* younger sister becomes new root */
5083 /* no longer older sister either */
5084 if (lst == sis) sis = NIL;
5086 else { /* find older sister of msg in table */
5087 for (nxt = root; lst != nxt->branch; nxt = nxt->branch);
5088 /* remove from older sister */
5089 nxt->branch = lst->branch;
5091 msg->next = lst; /* msg in table becomes child */
5092 lst->branch = cur; /* current now little sister of msg in table */
5093 if (sis) { /* have an elder sister? */
5094 if (sis == lst) /* rescan if lost her */
5095 for (sis = root; cur != sis->branch; sis = sis->branch);
5096 sis->branch = msg; /* make dummy younger sister of big sister */
5098 else root = msg; /* otherwise this is the new root */
5099 sub[0] = sis = msg; /* set new msg in table and new big sister */
5101 msg = cur->branch; /* continue scan at younger sister */
5102 cur->branch = NIL; /* lose our younger sisters */
5104 if (sis) sis->branch = msg; /* older sister gets this as younger sister */
5105 else root = msg; /* otherwise this is the new root */
5107 hash_destroy (&ht); /* finished with hash table */
5108 /* Step 6 */
5109 /* sort threads */
5110 root = mail_thread_sort (root,tc);
5111 fs_give ((void **) &tc); /* finished with sort buffer */
5112 return root; /* return sorted list */
5115 /* Fetch overview callback to load sortcache for threading
5116 * Accepts: MAIL stream
5117 * UID of this message
5118 * overview of this message
5119 * msgno of this message
5122 void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov,
5123 unsigned long msgno)
5125 if (msgno && ov) { /* just in case */
5126 MESSAGECACHE telt, *elt;
5127 ENVELOPE *env;
5128 SORTCACHE *s = (SORTCACHE *) (*mailcache) (stream,msgno,CH_SORTCACHE);
5129 if (!s->subject && ov->subject) {
5130 s->refwd = mail_strip_subject (ov->subject,&s->subject);
5131 s->dirty = T;
5133 if (!s->from && ov->from && ov->from->mailbox) {
5134 s->from = cpystr (ov->from->mailbox);
5135 s->dirty = T;
5137 if (!s->date && ov->date && mail_parse_date (&telt,ov->date)) {
5138 s->date = mail_longdate (&telt);
5139 s->dirty = T;
5141 if (!s->message_id && ov->message_id) {
5142 s->message_id = mail_thread_parse_msgid (ov->message_id,NIL);
5143 s->dirty = T;
5145 if (!s->references &&
5146 !(s->references = mail_thread_parse_references (ov->references,T))
5147 && stream->dtb && !strcmp(stream->dtb->name, "imap")
5148 && (elt = mail_elt (stream, msgno)) != NULL
5149 && (env = elt->private.msg.env) != NULL
5150 && env->in_reply_to
5151 && !(s->references = mail_thread_parse_references(env->in_reply_to, NIL))) {
5152 /* don't do In-Reply-To with NNTP mailboxes */
5153 s->references = mail_newstringlist ();
5154 s->dirty = T;
5156 if (!s->size && ov->optional.octets) {
5157 s->size = ov->optional.octets;
5158 s->dirty = T;
5163 /* Thread parse Message ID
5164 * Accepts: pointer to purported Message ID
5165 * pointer to return pointer
5166 * Returns: Message ID or NIL, return pointer updated
5169 char *mail_thread_parse_msgid (char *s,char **ss)
5171 char *ret = NIL;
5172 char *t = NIL;
5173 ADDRESS *adr;
5174 if (s) { /* only for non-NIL strings */
5175 rfc822_skipws (&s); /* skip whitespace */
5176 /* ignore phrases */
5177 if (((*s == '<') || (s = rfc822_parse_phrase (s))) &&
5178 (adr = rfc822_parse_routeaddr (s,&t,BADHOST))) {
5179 /* make return msgid */
5180 if (adr->mailbox && adr->host)
5181 sprintf (ret = (char *) fs_get (strlen (adr->mailbox) +
5182 strlen (adr->host) + 2),"%s@%s",
5183 adr->mailbox,adr->host);
5184 mail_free_address (&adr); /* don't need temporary address */
5187 if (ss) *ss = t; /* update return pointer */
5188 return ret;
5192 /* Thread parse references
5193 * Accepts: pointer to purported references
5194 * parse multiple references flag
5195 * Returns: references or NIL
5198 STRINGLIST *mail_thread_parse_references (char *s,long flag)
5200 char *t;
5201 STRINGLIST *ret = NIL;
5202 STRINGLIST *cur;
5203 /* found first reference? */
5204 if (t = mail_thread_parse_msgid (s,&s)) {
5205 (ret = mail_newstringlist ())->text.data = (unsigned char *) t;
5206 ret->text.size = strlen (t);
5207 if (flag) /* parse subsequent references */
5208 for (cur = ret; t = mail_thread_parse_msgid (s,&s); cur = cur->next) {
5209 (cur->next = mail_newstringlist ())->text.data = (unsigned char *) t;
5210 cur->next->text.size = strlen (t);
5213 return ret;
5216 /* Prune dummy messages
5217 * Accepts: candidate container to prune
5218 * older sibling of container, if any
5219 * Returns: container in this position, possibly pruned
5220 * All children and younger siblings are also pruned
5223 container_t mail_thread_prune_dummy (container_t msg,container_t ane)
5225 /* prune container and children */
5226 container_t ret = msg ? mail_thread_prune_dummy_work (msg,ane) : NIL;
5227 /* prune all younger sisters */
5228 if (ret) for (ane = ret; ane && (msg = SIBLING (ane)); ane = msg)
5229 msg = mail_thread_prune_dummy_work (msg,ane);
5230 return ret;
5234 /* Prune dummy messages worker routine
5235 * Accepts: candidate container to prune
5236 * older sibling of container, if any
5237 * Returns: container in this position, possibly pruned
5238 * All children are also pruned
5241 container_t mail_thread_prune_dummy_work (container_t msg,container_t ane)
5243 container_t cur;
5244 /* get children, if any */
5245 container_t nxt = mail_thread_prune_dummy (CHILD (msg),NIL);
5246 /* just update children if container has msg */
5247 if (CACHE (msg)) SETCHILD (msg,nxt);
5248 else if (!nxt) { /* delete dummy with no children */
5249 nxt = SIBLING (msg); /* get younger sister */
5250 if (ane) SETSIBLING (ane,nxt);
5251 /* prune younger sister if exists */
5252 msg = nxt ? mail_thread_prune_dummy_work (nxt,ane) : NIL;
5254 /* not if parent root & multiple children */
5255 else if ((cur = PARENT (msg)) || !SIBLING (nxt)) {
5256 /* OK to promote, try younger sister of aunt */
5257 if (ane) SETSIBLING (ane,nxt);
5258 /* otherwise promote to child of grandmother */
5259 else if (cur) SETCHILD (cur,nxt);
5260 SETPARENT (nxt,cur); /* set parent as well */
5261 /* look for end of siblings in new container */
5262 for (cur = nxt; SIBLING (cur); cur = SIBLING (cur));
5263 /* reattach deleted container's siblings */
5264 SETSIBLING (cur,SIBLING (msg));
5265 /* prune and return new container */
5266 msg = mail_thread_prune_dummy_work (nxt,ane);
5268 else SETCHILD (msg,nxt); /* in case child pruned */
5269 return msg; /* return this message */
5272 /* Test that purported mother is not a child of purported daughter
5273 * Accepts: mother
5274 * purported daugher
5275 * Returns: T if circular parentage exists, else NIL
5278 long mail_thread_check_child (container_t mother,container_t daughter)
5280 if (mother) { /* only if mother non-NIL */
5281 if (mother == daughter) return T;
5282 for (daughter = CHILD (daughter); daughter; daughter = SIBLING (daughter))
5283 if (mail_thread_check_child (mother,daughter)) return T;
5285 return NIL;
5289 /* Generate threadnodes from containers
5290 * Accepts: Mail stream
5291 * container
5292 * flags
5293 * Return: threadnode list
5296 THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags)
5298 THREADNODE *ret,*cur;
5299 SORTCACHE *s;
5300 container_t nxt;
5301 /* for each container */
5302 for (ret = cur = NIL; con; con = SIBLING (con)) {
5303 s = CACHE (con); /* yes, get its sortcache */
5304 /* create node for it */
5305 if (ret) cur = cur->branch = mail_newthreadnode (s);
5306 else ret = cur = mail_newthreadnode (s);
5307 /* attach sequence or UID for non-dummy */
5308 if (s) cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
5309 /* attach the children */
5310 if (nxt = CHILD (con)) cur->next = mail_thread_c2node (stream,nxt,flags);
5312 return ret;
5315 /* Sort thread tree by date
5316 * Accepts: thread tree to sort
5317 * qsort vector to sort
5318 * Returns: sorted thread tree
5321 THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc)
5323 unsigned long i,j;
5324 THREADNODE *cur;
5325 /* sort children of each thread */
5326 for (cur = thr; cur; cur = cur->branch)
5327 if (cur->next) cur->next = mail_thread_sort (cur->next,tc);
5328 /* Must do this in a separate pass since recursive call will clobber tc */
5329 /* load threadcache and count nodes to sort */
5330 for (i = 0, cur = thr; cur; cur = cur->branch) tc[i++] = cur;
5331 if (i > 1) { /* only if need to sort */
5332 qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
5333 /* relink root siblings */
5334 for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
5335 tc[j]->branch = NIL; /* end of root */
5337 return i ? tc[0] : NIL; /* return new head of list */
5341 /* Thread compare date
5342 * Accept: first message sort cache element
5343 * second message sort cache element
5344 * Returns: -1 if a1 < a2, 1 if a1 > a2
5346 * This assumes that a sort cache element is either a message (with a
5347 * sortcache entry) or a dummy with a message (with sortcache entry) child.
5348 * This is true of both the ORDEREDSUBJECT (no dummies) and REFERENCES
5349 * (dummies only at top-level, and with non-dummy children).
5351 * If a new algorithm allows a dummy parent to have a dummy child, this
5352 * routine must be changed if it is to be used by that algorithm.
5354 * Messages with bogus dates are always sorted at the top.
5357 int mail_thread_compare_date (const void *a1,const void *a2)
5359 THREADNODE *t1 = *(THREADNODE **) a1;
5360 THREADNODE *t2 = *(THREADNODE **) a2;
5361 SORTCACHE *s1 = t1->sc ? t1->sc : t1->next->sc;
5362 SORTCACHE *s2 = t2->sc ? t2->sc : t2->next->sc;
5363 int ret = compare_ulong (s1->date,s2->date);
5364 /* use number as final tie-breaker */
5365 return ret ? ret : compare_ulong (s1->num,s2->num);
5368 /* Mail parse sequence
5369 * Accepts: mail stream
5370 * sequence to parse
5371 * Returns: T if parse successful, else NIL
5374 long mail_sequence (MAILSTREAM *stream,unsigned char *sequence)
5376 unsigned long i,j,x;
5377 for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
5378 while (sequence && *sequence){/* while there is something to parse */
5379 if (*sequence == '*') { /* maximum message */
5380 if (stream->nmsgs) i = stream->nmsgs;
5381 else {
5382 MM_LOG ("No messages, so no maximum message number",ERROR);
5383 return NIL;
5385 sequence++; /* skip past * */
5387 /* parse and validate message number */
5388 else if (!isdigit (*sequence)) {
5389 MM_LOG ("Syntax error in sequence",ERROR);
5390 return NIL;
5392 else if (!(i = strtoul (sequence,(char **) &sequence,10)) ||
5393 (i > stream->nmsgs)) {
5394 MM_LOG ("Sequence out of range",ERROR);
5395 return NIL;
5397 switch (*sequence) { /* see what the delimiter is */
5398 case ':': /* sequence range */
5399 if (*++sequence == '*') { /* maximum message */
5400 if (stream->nmsgs) j = stream->nmsgs;
5401 else {
5402 MM_LOG ("No messages, so no maximum message number",ERROR);
5403 return NIL;
5405 sequence++; /* skip past * */
5407 /* parse end of range */
5408 else if (!(j = strtoul (sequence,(char **) &sequence,10)) ||
5409 (j > stream->nmsgs)) {
5410 MM_LOG ("Sequence range invalid",ERROR);
5411 return NIL;
5413 if (*sequence && *sequence++ != ',') {
5414 MM_LOG ("Sequence range syntax error",ERROR);
5415 return NIL;
5417 if (i > j) { /* swap the range if backwards */
5418 x = i; i = j; j = x;
5420 /* mark each item in the sequence */
5421 while (i <= j) mail_elt (stream,j--)->sequence = T;
5422 break;
5423 case ',': /* single message */
5424 ++sequence; /* skip the delimiter, fall into end case */
5425 case '\0': /* end of sequence, mark this message */
5426 mail_elt (stream,i)->sequence = T;
5427 break;
5428 default: /* anything else is a syntax error! */
5429 MM_LOG ("Sequence syntax error",ERROR);
5430 return NIL;
5433 return T; /* successfully parsed sequence */
5436 /* Parse flag list
5437 * Accepts: MAIL stream
5438 * flag list as a character string
5439 * pointer to user flags to return
5440 * Returns: system flags
5443 long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf)
5445 char *t,*n,*s,tmp[MAILTMPLEN],msg[MAILTMPLEN];
5446 short f = 0;
5447 long i,j;
5448 *uf = 0; /* initially no user flags */
5449 if (flag && *flag) { /* no-op if no flag string */
5450 /* check if a list and make sure valid */
5451 if (((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) ||
5452 (strlen (flag) >= MAILTMPLEN)) {
5453 MM_LOG ("Bad flag list",ERROR);
5454 return NIL;
5456 /* copy the flag string w/o list construct */
5457 strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i)));
5458 tmp[j] = '\0';
5459 while ((t = n) && *t) { /* parse the flags */
5460 /* find end of flag */
5461 if (n = strchr (t,' ')) *n++ = '\0';
5462 if (*t == '\\') { /* system flag? */
5463 if (!compare_cstring (t+1,"SEEN")) f |= fSEEN;
5464 else if (!compare_cstring (t+1,"DELETED")) f |= fDELETED;
5465 else if (!compare_cstring (t+1,"FLAGGED")) f |= fFLAGGED;
5466 else if (!compare_cstring (t+1,"ANSWERED")) f |= fANSWERED;
5467 else if (!compare_cstring (t+1,"DRAFT")) f |= fDRAFT;
5468 else {
5469 sprintf (msg,"Unsupported system flag: %.80s",t);
5470 MM_LOG (msg,WARN);
5474 else { /* keyword flag */
5475 for (i = j = 0; /* user flag, search through table */
5476 !i && (j < NUSERFLAGS) && (s = stream->user_flags[j]); ++j)
5477 if (!compare_cstring (t,s)) *uf |= i = 1 << j;
5478 if (!i) { /* flag not found, can it be created? */
5479 if (stream->kwd_create && (j < NUSERFLAGS) && *t &&
5480 (strlen (t) <= MAXUSERFLAG)) {
5481 for (s = t; t && *s; s++) switch (*s) {
5482 default: /* all other characters */
5483 /* SPACE, CTL, or not CHAR */
5484 if ((*s > ' ') && (*s < 0x7f)) break;
5485 case '*': case '%': /* list_wildcards */
5486 case '"': case '\\':/* quoted-specials */
5487 /* atom_specials */
5488 case '(': case ')': case '{':
5489 case ']': /* resp-specials */
5490 sprintf (msg,"Invalid flag: %.80s",t);
5491 MM_LOG (msg,WARN);
5492 t = NIL;
5494 if (t) { /* only if valid */
5495 *uf |= 1 << j; /* set the bit */
5496 stream->user_flags[j] = cpystr (t);
5497 /* if out of user flags */
5498 if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
5501 else {
5502 if (*t) sprintf (msg,"Unknown flag: %.80s",t);
5503 else strcpy (msg,"Empty flag invalid");
5504 MM_LOG (msg,WARN);
5510 return f;
5513 /* Mail check network stream for usability with new name
5514 * Accepts: MAIL stream
5515 * candidate new name
5516 * Returns: T if stream can be used, NIL otherwise
5519 long mail_usable_network_stream (MAILSTREAM *stream,char *name)
5521 NETMBX smb,nmb,omb;
5522 return (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL) &&
5523 mail_valid_net_parse (name,&nmb) &&
5524 mail_valid_net_parse (stream->mailbox,&smb) &&
5525 mail_valid_net_parse (stream->original_mailbox,&omb) &&
5526 ((!compare_cstring (smb.host,
5527 trustdns ? tcp_canonical (nmb.host) : nmb.host)&&
5528 !strcmp (smb.service,nmb.service) &&
5529 (!nmb.port || (smb.port == nmb.port)) &&
5530 (nmb.anoflag == stream->anonymous) &&
5531 (!nmb.user[0] || !strcmp (smb.user,nmb.user))) ||
5532 (!compare_cstring (omb.host,nmb.host) &&
5533 !strcmp (omb.service,nmb.service) &&
5534 (!nmb.port || (omb.port == nmb.port)) &&
5535 (nmb.anoflag == stream->anonymous) &&
5536 (!nmb.user[0] || !strcmp (omb.user,nmb.user))))) ? LONGT : NIL;
5539 /* Mail data structure instantiation routines */
5542 /* Mail instantiate cache elt
5543 * Accepts: initial message number
5544 * Returns: new cache elt
5547 MESSAGECACHE *mail_new_cache_elt (unsigned long msgno)
5549 MESSAGECACHE *elt = (MESSAGECACHE *) memset (fs_get (sizeof (MESSAGECACHE)),
5550 0,sizeof (MESSAGECACHE));
5551 elt->lockcount = 1; /* initially only cache references it */
5552 elt->msgno = msgno; /* message number */
5553 return elt;
5557 /* Mail instantiate envelope
5558 * Returns: new envelope
5561 ENVELOPE *mail_newenvelope (void)
5563 return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE));
5567 /* Mail instantiate address
5568 * Returns: new address
5571 ADDRESS *mail_newaddr (void)
5573 return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS));
5576 /* Mail instantiate body
5577 * Returns: new body
5580 BODY *mail_newbody (void)
5582 return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
5586 /* Mail initialize body
5587 * Accepts: body
5588 * Returns: body
5591 BODY *mail_initbody (BODY *body)
5593 memset ((void *) body,0,sizeof (BODY));
5594 body->type = TYPETEXT; /* content type */
5595 body->encoding = ENC7BIT; /* content encoding */
5596 return body;
5600 /* Mail instantiate body parameter
5601 * Returns: new body part
5604 PARAMETER *mail_newbody_parameter (void)
5606 return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER));
5610 /* Mail instantiate body part
5611 * Returns: new body part
5614 PART *mail_newbody_part (void)
5616 PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART));
5617 mail_initbody (&part->body); /* initialize the body */
5618 return part;
5622 /* Mail instantiate body message part
5623 * Returns: new body message part
5626 MESSAGE *mail_newmsg (void)
5628 return (MESSAGE *) memset (fs_get (sizeof (MESSAGE)),0,sizeof (MESSAGE));
5631 /* Mail instantiate string list
5632 * Returns: new string list
5635 STRINGLIST *mail_newstringlist (void)
5637 return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0,
5638 sizeof (STRINGLIST));
5642 /* Mail instantiate new search program
5643 * Returns: new search program
5646 SEARCHPGM *mail_newsearchpgm (void)
5648 return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM));
5652 /* Mail instantiate new search program
5653 * Accepts: header line name
5654 * Returns: new search program
5657 SEARCHHEADER *mail_newsearchheader (char *line,char *text)
5659 SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)),
5660 0,sizeof (SEARCHHEADER));
5661 hdr->line.size = strlen ((char *) (hdr->line.data =
5662 (unsigned char *) cpystr (line)));
5663 hdr->text.size = strlen ((char *) (hdr->text.data =
5664 (unsigned char *) cpystr (text)));
5665 return hdr;
5669 /* Mail instantiate new search set
5670 * Returns: new search set
5673 SEARCHSET *mail_newsearchset (void)
5675 return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET));
5679 /* Mail instantiate new search or
5680 * Returns: new search or
5683 SEARCHOR *mail_newsearchor (void)
5685 SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0,
5686 sizeof (SEARCHOR));
5687 or->first = mail_newsearchpgm ();
5688 or->second = mail_newsearchpgm ();
5689 return or;
5692 /* Mail instantiate new searchpgmlist
5693 * Returns: new searchpgmlist
5696 SEARCHPGMLIST *mail_newsearchpgmlist (void)
5698 SEARCHPGMLIST *pgl = (SEARCHPGMLIST *)
5699 memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST));
5700 pgl->pgm = mail_newsearchpgm ();
5701 return pgl;
5705 /* Mail instantiate new sortpgm
5706 * Returns: new sortpgm
5709 SORTPGM *mail_newsortpgm (void)
5711 return (SORTPGM *) memset (fs_get (sizeof (SORTPGM)),0,sizeof (SORTPGM));
5715 /* Mail instantiate new threadnode
5716 * Accepts: sort cache for thread node
5717 * Returns: new threadnode
5720 THREADNODE *mail_newthreadnode (SORTCACHE *sc)
5722 THREADNODE *thr = (THREADNODE *) memset (fs_get (sizeof (THREADNODE)),0,
5723 sizeof (THREADNODE));
5724 if (sc) thr->sc = sc; /* initialize sortcache */
5725 return thr;
5729 /* Mail instantiate new acllist
5730 * Returns: new acllist
5733 ACLLIST *mail_newacllist (void)
5735 return (ACLLIST *) memset (fs_get (sizeof (ACLLIST)),0,sizeof (ACLLIST));
5739 /* Mail instantiate new quotalist
5740 * Returns: new quotalist
5743 QUOTALIST *mail_newquotalist (void)
5745 return (QUOTALIST *) memset (fs_get (sizeof (QUOTALIST)),0,
5746 sizeof (QUOTALIST));
5749 /* Mail garbage collection routines */
5752 /* Mail garbage collect body
5753 * Accepts: pointer to body pointer
5756 void mail_free_body (BODY **body)
5758 if (*body) { /* only free if exists */
5759 mail_free_body_data (*body);/* free its data */
5760 fs_give ((void **) body); /* return body to free storage */
5765 /* Mail garbage collect body data
5766 * Accepts: body pointer
5769 void mail_free_body_data (BODY *body)
5771 switch (body->type) { /* free contents */
5772 case TYPEMULTIPART: /* multiple part */
5773 mail_free_body_part (&body->nested.part);
5774 break;
5775 case TYPEMESSAGE: /* encapsulated message */
5776 if (body->subtype && !strcmp (body->subtype,"RFC822")) {
5777 mail_free_stringlist (&body->nested.msg->lines);
5778 mail_gc_msg (body->nested.msg,GC_ENV | GC_TEXTS);
5780 if (body->nested.msg) fs_give ((void **) &body->nested.msg);
5781 break;
5782 default:
5783 break;
5785 if (body->subtype) fs_give ((void **) &body->subtype);
5786 mail_free_body_parameter (&body->parameter);
5787 if (body->id) fs_give ((void **) &body->id);
5788 if (body->description) fs_give ((void **) &body->description);
5789 if (body->disposition.type) fs_give ((void **) &body->disposition.type);
5790 if (body->disposition.parameter)
5791 mail_free_body_parameter (&body->disposition.parameter);
5792 if (body->language) mail_free_stringlist (&body->language);
5793 if (body->location) fs_give ((void **) &body->location);
5794 if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
5795 if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
5796 if (body->md5) fs_give ((void **) &body->md5);
5797 if (mailfreebodysparep && body->sparep)
5798 (*mailfreebodysparep) (&body->sparep);
5801 /* Mail garbage collect body parameter
5802 * Accepts: pointer to body parameter pointer
5805 void mail_free_body_parameter (PARAMETER **parameter)
5807 if (*parameter) { /* only free if exists */
5808 if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
5809 if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
5810 /* run down the list as necessary */
5811 mail_free_body_parameter (&(*parameter)->next);
5812 /* return body part to free storage */
5813 fs_give ((void **) parameter);
5818 /* Mail garbage collect body part
5819 * Accepts: pointer to body part pointer
5822 void mail_free_body_part (PART **part)
5824 if (*part) { /* only free if exists */
5825 mail_free_body_data (&(*part)->body);
5826 /* run down the list as necessary */
5827 mail_free_body_part (&(*part)->next);
5828 fs_give ((void **) part); /* return body part to free storage */
5832 /* Mail garbage collect message cache
5833 * Accepts: mail stream
5835 * The message cache is set to NIL when this function finishes.
5838 void mail_free_cache (MAILSTREAM *stream)
5840 /* do driver specific stuff first */
5841 mail_gc (stream,GC_ELT | GC_ENV | GC_TEXTS);
5842 /* flush the cache */
5843 (*mailcache) (stream,(long) 0,CH_INIT);
5847 /* Mail garbage collect cache element
5848 * Accepts: pointer to cache element pointer
5851 void mail_free_elt (MESSAGECACHE **elt)
5853 /* only free if exists and no sharers */
5854 if (*elt && !--(*elt)->lockcount) {
5855 mail_gc_msg (&(*elt)->private.msg,GC_ENV | GC_TEXTS);
5856 if (mailfreeeltsparep && (*elt)->sparep)
5857 (*mailfreeeltsparep) (&(*elt)->sparep);
5858 fs_give ((void **) elt);
5860 else *elt = NIL; /* else simply drop pointer */
5863 /* Mail garbage collect envelope
5864 * Accepts: pointer to envelope pointer
5867 void mail_free_envelope (ENVELOPE **env)
5869 if (*env) { /* only free if exists */
5870 if ((*env)->remail) fs_give ((void **) &(*env)->remail);
5871 mail_free_address (&(*env)->return_path);
5872 if ((*env)->date) fs_give ((void **) &(*env)->date);
5873 mail_free_address (&(*env)->from);
5874 mail_free_address (&(*env)->sender);
5875 mail_free_address (&(*env)->reply_to);
5876 if ((*env)->subject) fs_give ((void **) &(*env)->subject);
5877 mail_free_address (&(*env)->to);
5878 mail_free_address (&(*env)->cc);
5879 mail_free_address (&(*env)->bcc);
5880 if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
5881 if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
5882 if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
5883 if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to);
5884 if ((*env)->references) fs_give ((void **) &(*env)->references);
5885 if (mailfreeenvelopesparep && (*env)->sparep)
5886 (*mailfreeenvelopesparep) (&(*env)->sparep);
5887 fs_give ((void **) env); /* return envelope to free storage */
5892 /* Mail garbage collect address
5893 * Accepts: pointer to address pointer
5896 void mail_free_address (ADDRESS **address)
5898 if (*address) { /* only free if exists */
5899 if ((*address)->personal) fs_give ((void **) &(*address)->personal);
5900 if ((*address)->adl) fs_give ((void **) &(*address)->adl);
5901 if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
5902 if ((*address)->host) fs_give ((void **) &(*address)->host);
5903 if ((*address)->error) fs_give ((void **) &(*address)->error);
5904 if ((*address)->orcpt.type) fs_give ((void **) &(*address)->orcpt.type);
5905 if ((*address)->orcpt.addr) fs_give ((void **) &(*address)->orcpt.addr);
5906 mail_free_address (&(*address)->next);
5907 fs_give ((void **) address);/* return address to free storage */
5912 /* Mail garbage collect stringlist
5913 * Accepts: pointer to stringlist pointer
5916 void mail_free_stringlist (STRINGLIST **string)
5918 if (*string) { /* only free if exists */
5919 if ((*string)->text.data) fs_give ((void **) &(*string)->text.data);
5920 mail_free_stringlist (&(*string)->next);
5921 fs_give ((void **) string); /* return string to free storage */
5925 /* Mail garbage collect searchpgm
5926 * Accepts: pointer to searchpgm pointer
5929 void mail_free_searchpgm (SEARCHPGM **pgm)
5931 if (*pgm) { /* only free if exists */
5932 mail_free_searchset (&(*pgm)->msgno);
5933 mail_free_searchset (&(*pgm)->uid);
5934 mail_free_searchor (&(*pgm)->or);
5935 mail_free_searchpgmlist (&(*pgm)->not);
5936 mail_free_searchheader (&(*pgm)->header);
5937 mail_free_stringlist (&(*pgm)->bcc);
5938 mail_free_stringlist (&(*pgm)->body);
5939 mail_free_stringlist (&(*pgm)->cc);
5940 mail_free_stringlist (&(*pgm)->from);
5941 mail_free_stringlist (&(*pgm)->keyword);
5942 mail_free_stringlist (&(*pgm)->subject);
5943 mail_free_stringlist (&(*pgm)->text);
5944 mail_free_stringlist (&(*pgm)->to);
5945 fs_give ((void **) pgm); /* return program to free storage */
5950 /* Mail garbage collect searchheader
5951 * Accepts: pointer to searchheader pointer
5954 void mail_free_searchheader (SEARCHHEADER **hdr)
5956 if (*hdr) { /* only free if exists */
5957 if ((*hdr)->line.data) fs_give ((void **) &(*hdr)->line.data);
5958 if ((*hdr)->text.data) fs_give ((void **) &(*hdr)->text.data);
5959 mail_free_searchheader (&(*hdr)->next);
5960 fs_give ((void **) hdr); /* return header to free storage */
5965 /* Mail garbage collect searchset
5966 * Accepts: pointer to searchset pointer
5969 void mail_free_searchset (SEARCHSET **set)
5971 if (*set) { /* only free if exists */
5972 mail_free_searchset (&(*set)->next);
5973 fs_give ((void **) set); /* return set to free storage */
5977 /* Mail garbage collect searchor
5978 * Accepts: pointer to searchor pointer
5981 void mail_free_searchor (SEARCHOR **orl)
5983 if (*orl) { /* only free if exists */
5984 mail_free_searchpgm (&(*orl)->first);
5985 mail_free_searchpgm (&(*orl)->second);
5986 mail_free_searchor (&(*orl)->next);
5987 fs_give ((void **) orl); /* return searchor to free storage */
5992 /* Mail garbage collect search program list
5993 * Accepts: pointer to searchpgmlist pointer
5996 void mail_free_searchpgmlist (SEARCHPGMLIST **pgl)
5998 if (*pgl) { /* only free if exists */
5999 mail_free_searchpgm (&(*pgl)->pgm);
6000 mail_free_searchpgmlist (&(*pgl)->next);
6001 fs_give ((void **) pgl); /* return searchpgmlist to free storage */
6006 /* Mail garbage collect namespace
6007 * Accepts: poiner to namespace
6010 void mail_free_namespace (NAMESPACE **n)
6012 if (*n) {
6013 fs_give ((void **) &(*n)->name);
6014 mail_free_namespace (&(*n)->next);
6015 mail_free_body_parameter (&(*n)->param);
6016 fs_give ((void **) n); /* return namespace to free storage */
6020 /* Mail garbage collect sort program
6021 * Accepts: pointer to sortpgm pointer
6024 void mail_free_sortpgm (SORTPGM **pgm)
6026 if (*pgm) { /* only free if exists */
6027 mail_free_sortpgm (&(*pgm)->next);
6028 fs_give ((void **) pgm); /* return sortpgm to free storage */
6033 /* Mail garbage collect thread node
6034 * Accepts: pointer to threadnode pointer
6037 void mail_free_threadnode (THREADNODE **thr)
6039 if (*thr) { /* only free if exists */
6040 mail_free_threadnode (&(*thr)->branch);
6041 mail_free_threadnode (&(*thr)->next);
6042 fs_give ((void **) thr); /* return threadnode to free storage */
6047 /* Mail garbage collect acllist
6048 * Accepts: pointer to acllist pointer
6051 void mail_free_acllist (ACLLIST **al)
6053 if (*al) { /* only free if exists */
6054 if ((*al)->identifier) fs_give ((void **) &(*al)->identifier);
6055 if ((*al)->rights) fs_give ((void **) &(*al)->rights);
6056 mail_free_acllist (&(*al)->next);
6057 fs_give ((void **) al); /* return acllist to free storage */
6062 /* Mail garbage collect quotalist
6063 * Accepts: pointer to quotalist pointer
6066 void mail_free_quotalist (QUOTALIST **ql)
6068 if (*ql) { /* only free if exists */
6069 if ((*ql)->name) fs_give ((void **) &(*ql)->name);
6070 mail_free_quotalist (&(*ql)->next);
6071 fs_give ((void **) ql); /* return quotalist to free storage */
6075 /* Link authenicator
6076 * Accepts: authenticator to add to list
6079 void auth_link (AUTHENTICATOR *auth)
6081 if (!auth->valid || (*auth->valid) ()) {
6082 AUTHENTICATOR **a = &mailauthenticators;
6083 while (*a) a = &(*a)->next; /* find end of list of authenticators */
6084 *a = auth; /* put authenticator at the end */
6085 auth->next = NIL; /* this authenticator is the end of the list */
6090 /* Authenticate access
6091 * Accepts: mechanism name
6092 * responder function
6093 * argument count
6094 * argument vector
6095 * Returns: authenticated user name or NIL
6098 char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
6100 AUTHENTICATOR *auth;
6101 for (auth = mailauthenticators; auth; auth = auth->next)
6102 if (auth->server && !compare_cstring (auth->name,mechanism))
6103 return (!(auth->flags & AU_DISABLE) &&
6104 ((auth->flags & AU_SECURE) ||
6105 !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) ?
6106 (*auth->server) (resp,argc,argv) : NIL;
6107 return NIL; /* no authenticator found */
6110 /* Lookup authenticator index
6111 * Accepts: authenticator index
6112 * Returns: authenticator, or 0 if not found
6115 AUTHENTICATOR *mail_lookup_auth (unsigned long i)
6117 AUTHENTICATOR *auth = mailauthenticators;
6118 while (auth && --i) auth = auth->next;
6119 return auth;
6123 /* Lookup authenticator name
6124 * Accepts: authenticator name
6125 * required authenticator flags
6126 * Returns: index in authenticator chain, or 0 if not found
6129 unsigned int mail_lookup_auth_name (char *mechanism,long flags)
6131 int i;
6132 AUTHENTICATOR *auth;
6133 for (i = 1, auth = mailauthenticators; auth; i++, auth = auth->next)
6134 if (auth->client && !(flags & ~auth->flags) &&
6135 !(auth->flags & AU_DISABLE) && !compare_cstring (auth->name,mechanism))
6136 return i;
6137 return 0;
6140 /* Standard TCP/IP network driver */
6142 static NETDRIVER tcpdriver = {
6143 tcp_open, /* open connection */
6144 tcp_aopen, /* open preauthenticated connection */
6145 tcp_getline, /* get a line */
6146 tcp_getbuffer, /* get a buffer */
6147 tcp_soutr, /* output pushed data */
6148 tcp_sout, /* output string */
6149 tcp_close, /* close connection */
6150 tcp_host, /* return host name */
6151 tcp_remotehost, /* return remote host name */
6152 tcp_port, /* return port number */
6153 tcp_localhost /* return local host name */
6157 /* Network open
6158 * Accepts: NETMBX specifier to open
6159 * default network driver
6160 * default port
6161 * SSL driver
6162 * SSL service name
6163 * SSL driver port
6164 * Returns: Network stream if success, else NIL
6167 NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port,
6168 NETDRIVER *ssld,char *ssls,unsigned long sslp)
6170 NETSTREAM *stream = NIL;
6171 char tmp[MAILTMPLEN];
6172 unsigned long flags = mb->novalidate ? NET_NOVALIDATECERT : 0;
6173 if (strlen (mb->host) >= NETMAXHOST) {
6174 sprintf (tmp,"Invalid host name: %.80s",mb->host);
6175 MM_LOG (tmp,ERROR);
6177 /* use designated driver if given */
6178 else if (dv) stream = net_open_work (dv,mb->host,mb->service,port,mb->port,
6179 flags);
6180 else if (mb->sslflag && ssld) /* use ssl if sslflag lit */
6181 stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,flags);
6182 /* if trysslfirst and can open ssl... */
6183 else if ((mb->trysslflag || trysslfirst) && ssld &&
6184 (stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,
6185 flags | NET_SILENT | NET_TRYSSL))) {
6186 if (net_sout (stream,"",0)) mb->sslflag = T;
6187 else {
6188 net_close (stream); /* flush fake SSL stream */
6189 stream = NIL;
6192 /* default to TCP driver */
6193 else stream = net_open_work (&tcpdriver,mb->host,mb->service,port,mb->port,
6194 flags);
6195 return stream;
6198 /* Network open worker routine
6199 * Accepts: network driver
6200 * host name
6201 * service name to look up port
6202 * port number if service name not found
6203 * port number to override service name
6204 * flags (passed on top of port)
6205 * Returns: Network stream if success, else NIL
6208 NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service,
6209 unsigned long port,unsigned long portoverride,
6210 unsigned long flags)
6212 NETSTREAM *stream = NIL;
6213 void *tstream;
6214 if (service && (*service == '*')) {
6215 flags |= NET_NOOPENTIMEOUT; /* mark that no timeout is desired */
6216 ++service; /* no longer need the no timeout indicator */
6218 if (portoverride) { /* explicit port number? */
6219 service = NIL; /* yes, override service name */
6220 port = portoverride; /* use that instead of default port */
6222 if (tstream = (*dv->open) (host,service,port | flags)) {
6223 stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
6224 stream->stream = tstream;
6225 stream->dtb = dv;
6227 return stream;
6231 /* Network authenticated open
6232 * Accepts: network driver
6233 * NETMBX specifier
6234 * service specifier
6235 * return user name buffer
6236 * Returns: Network stream if success else NIL
6239 NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *user)
6241 NETSTREAM *stream = NIL;
6242 void *tstream;
6243 if (!dv) dv = &tcpdriver; /* default to TCP driver */
6244 if (tstream = (*dv->aopen) (mb,service,user)) {
6245 stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
6246 stream->stream = tstream;
6247 stream->dtb = dv;
6249 return stream;
6252 /* Network receive line
6253 * Accepts: Network stream
6254 * Returns: text line string or NIL if failure
6257 char *net_getline (NETSTREAM *stream)
6259 return (*stream->dtb->getline) (stream->stream);
6263 /* Network receive buffer
6264 * Accepts: Network stream (must be void * for use as readfn_t)
6265 * size in bytes
6266 * buffer to read into
6267 * Returns: T if success, NIL otherwise
6270 long net_getbuffer (void *st,unsigned long size,char *buffer)
6272 NETSTREAM *stream = (NETSTREAM *) st;
6273 return (*stream->dtb->getbuffer) (stream->stream,size,buffer);
6277 /* Network send null-terminated string
6278 * Accepts: Network stream
6279 * string pointer
6280 * Returns: T if success else NIL
6283 long net_soutr (NETSTREAM *stream,char *string)
6285 return (*stream->dtb->soutr) (stream->stream,string);
6289 /* Network send string
6290 * Accepts: Network stream
6291 * string pointer
6292 * byte count
6293 * Returns: T if success else NIL
6296 long net_sout (NETSTREAM *stream,char *string,unsigned long size)
6298 return (*stream->dtb->sout) (stream->stream,string,size);
6301 /* Network close
6302 * Accepts: Network stream
6305 void net_close (NETSTREAM *stream)
6307 if (stream->stream) (*stream->dtb->close) (stream->stream);
6308 fs_give ((void **) &stream);
6312 /* Network get host name
6313 * Accepts: Network stream
6314 * Returns: host name for this stream
6317 char *net_host (NETSTREAM *stream)
6319 return (*stream->dtb->host) (stream->stream);
6323 /* Network get remote host name
6324 * Accepts: Network stream
6325 * Returns: host name for this stream
6328 char *net_remotehost (NETSTREAM *stream)
6330 return (*stream->dtb->remotehost) (stream->stream);
6333 /* Network return port for this stream
6334 * Accepts: Network stream
6335 * Returns: port number for this stream
6338 unsigned long net_port (NETSTREAM *stream)
6340 return (*stream->dtb->port) (stream->stream);
6344 /* Network get local host name
6345 * Accepts: Network stream
6346 * Returns: local host name
6349 char *net_localhost (NETSTREAM *stream)
6351 return (*stream->dtb->localhost) (stream->stream);