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