1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include "dat.h"
6
7 typedef struct Header Header;
8
9 struct Header {
10 char *type;
11 void (*f)(Message*, Header*, char*);
12 int len;
13 };
14
15 /* headers */
16 static void ctype(Message*, Header*, char*);
17 static void cencoding(Message*, Header*, char*);
18 static void cdisposition(Message*, Header*, char*);
19 static void date822(Message*, Header*, char*);
20 static void from822(Message*, Header*, char*);
21 static void to822(Message*, Header*, char*);
22 static void sender822(Message*, Header*, char*);
23 static void replyto822(Message*, Header*, char*);
24 static void subject822(Message*, Header*, char*);
25 static void inreplyto822(Message*, Header*, char*);
26 static void cc822(Message*, Header*, char*);
27 static void bcc822(Message*, Header*, char*);
28 static void messageid822(Message*, Header*, char*);
29 static void mimeversion(Message*, Header*, char*);
30 static void nullsqueeze(Message*);
31 enum
32 {
33 Mhead= 11, /* offset of first mime header */
34 };
35
36 Header head[] =
37 {
38 { "date:", date822, },
39 { "from:", from822, },
40 { "to:", to822, },
41 { "sender:", sender822, },
42 { "reply-to:", replyto822, },
43 { "subject:", subject822, },
44 { "cc:", cc822, },
45 { "bcc:", bcc822, },
46 { "in-reply-to:", inreplyto822, },
47 { "mime-version:", mimeversion, },
48 { "message-id:", messageid822, },
49
50 [Mhead] { "content-type:", ctype, },
51 { "content-transfer-encoding:", cencoding, },
52 { "content-disposition:", cdisposition, },
53 { 0, },
54 };
55
56 static void fatal(char *fmt, ...);
57 static void initquoted(void);
58 static void startheader(Message*);
59 static void startbody(Message*);
60 static char* skipwhite(char*);
61 static char* skiptosemi(char*);
62 static char* getstring(char*, String*, int);
63 static void setfilename(Message*, char*);
64 static char* lowercase(char*);
65 static int is8bit(Message*);
66 static int headerline(char**, String*);
67 static void initheaders(void);
68 static void parseattachments(Message*, Mailbox*);
69
70 int debug;
71
72 char *Enotme = "path not served by this file server";
73
74 enum
75 {
76 Chunksize = 1024,
77 };
78
79 Mailboxinit *boxinit[] = {
80 imap4mbox,
81 pop3mbox,
82 planbmbox,
83 planbvmbox,
84 plan9mbox,
85 };
86
87 char*
syncmbox(Mailbox * mb,int doplumb)88 syncmbox(Mailbox *mb, int doplumb)
89 {
90 return (*mb->sync)(mb, doplumb);
91 }
92
93 /* create a new mailbox */
94 char*
newmbox(char * path,char * name,int std)95 newmbox(char *path, char *name, int std)
96 {
97 Mailbox *mb, **l;
98 char *p, *rv;
99 int i;
100
101 initheaders();
102
103 mb = emalloc(sizeof(*mb));
104 strncpy(mb->path, path, sizeof(mb->path)-1);
105 if(name == nil){
106 p = strrchr(path, '/');
107 if(p == nil)
108 p = path;
109 else
110 p++;
111 if(*p == 0){
112 free(mb);
113 return "bad mbox name";
114 }
115 strncpy(mb->name, p, sizeof(mb->name)-1);
116 } else {
117 strncpy(mb->name, name, sizeof(mb->name)-1);
118 }
119
120 rv = nil;
121 // check for a mailbox type
122 for(i=0; i<nelem(boxinit); i++)
123 if((rv = (*boxinit[i])(mb, path)) != Enotme)
124 break;
125 if(i == nelem(boxinit)){
126 free(mb);
127 return "bad path";
128 }
129
130 // on error, give up
131 if(rv){
132 free(mb);
133 return rv;
134 }
135
136 // make sure name isn't taken
137 qlock(&mbllock);
138 for(l = &mbl; *l != nil; l = &(*l)->next){
139 if(strcmp((*l)->name, mb->name) == 0){
140 if(strcmp(path, (*l)->path) == 0)
141 rv = nil;
142 else
143 rv = "mbox name in use";
144 if(mb->close)
145 (*mb->close)(mb);
146 free(mb);
147 qunlock(&mbllock);
148 return rv;
149 }
150 }
151
152 // always try locking
153 mb->dolock = 1;
154
155 mb->refs = 1;
156 mb->next = nil;
157 mb->id = newid();
158 mb->root = newmessage(nil);
159 mb->std = std;
160 *l = mb;
161 qunlock(&mbllock);
162
163 qlock(mb);
164 if(mb->ctl){
165 henter(PATH(mb->id, Qmbox), "ctl",
166 (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
167 }
168 rv = syncmbox(mb, 0);
169 qunlock(mb);
170
171 return rv;
172 }
173
174 // close the named mailbox
175 void
freembox(char * name)176 freembox(char *name)
177 {
178 Mailbox **l, *mb;
179
180 qlock(&mbllock);
181 for(l=&mbl; *l != nil; l=&(*l)->next){
182 if(strcmp(name, (*l)->name) == 0){
183 mb = *l;
184 *l = mb->next;
185 mboxdecref(mb);
186 break;
187 }
188 }
189 hfree(PATH(0, Qtop), name);
190 qunlock(&mbllock);
191 }
192
193 static void
initheaders(void)194 initheaders(void)
195 {
196 Header *h;
197 static int already;
198
199 if(already)
200 return;
201 already = 1;
202
203 for(h = head; h->type != nil; h++)
204 h->len = strlen(h->type);
205 }
206
207 /*
208 * parse a Unix style header
209 */
210 void
parseunix(Message * m)211 parseunix(Message *m)
212 {
213 char *p;
214 String *h;
215
216 h = s_new();
217 for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
218 s_putc(h, *p);
219 s_terminate(h);
220 s_restart(h);
221
222 m->unixfrom = s_parse(h, s_reset(m->unixfrom));
223 m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
224
225 s_free(h);
226 }
227
228 /*
229 * parse a message
230 */
231 void
parseheaders(Message * m,int justmime,Mailbox * mb,int addfrom)232 parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
233 {
234 String *hl;
235 Header *h;
236 char *p, *q;
237 int i;
238
239 if(m->whole == m->whole->whole){
240 henter(PATH(mb->id, Qmbox), m->name,
241 (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
242 } else {
243 henter(PATH(m->whole->id, Qdir), m->name,
244 (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
245 }
246 for(i = 0; i < Qmax; i++)
247 henter(PATH(m->id, Qdir), dirtab[i],
248 (Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
249
250 // parse mime headers
251 p = m->header;
252 hl = s_new();
253 while(headerline(&p, hl)){
254 if(justmime)
255 h = &head[Mhead];
256 else
257 h = head;
258 for(; h->type; h++){
259 if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
260 (*h->f)(m, h, s_to_c(hl));
261 break;
262 }
263 }
264 s_reset(hl);
265 }
266 s_free(hl);
267
268 // the blank line isn't really part of the body or header
269 if(justmime){
270 m->mhend = p;
271 m->hend = m->header;
272 } else {
273 m->hend = p;
274 }
275 if(*p == '\n')
276 p++;
277 m->rbody = m->body = p;
278
279 // if type is text, get any nulls out of the body. This is
280 // for the two seans and imap clients that get confused.
281 if(strncmp(s_to_c(m->type), "text/", 5) == 0)
282 nullsqueeze(m);
283
284 //
285 // cobble together Unix-style from line
286 // for local mailbox messages, we end up recreating the
287 // original header.
288 // for pop3 messages, the best we can do is
289 // use the From: information and the RFC822 date.
290 //
291 if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
292 || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
293 if(m->unixdate){
294 s_free(m->unixdate);
295 m->unixdate = nil;
296 }
297 // look for the date in the first Received: line.
298 // it's likely to be the right time zone (it's
299 // the local system) and in a convenient format.
300 if(cistrncmp(m->header, "received:", 9)==0){
301 if((q = strchr(m->header, ';')) != nil){
302 p = q;
303 while((p = strchr(p, '\n')) != nil){
304 if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
305 break;
306 p++;
307 }
308 if(p){
309 *p = '\0';
310 m->unixdate = date822tounix(q+1);
311 *p = '\n';
312 }
313 }
314 }
315
316 // fall back on the rfc822 date
317 if(m->unixdate==nil && m->date822)
318 m->unixdate = date822tounix(s_to_c(m->date822));
319 }
320
321 if(m->unixheader != nil)
322 s_free(m->unixheader);
323
324 // only fake header for top-level messages for pop3 and imap4
325 // clients (those protocols don't include the unix header).
326 // adding the unix header all the time screws up mime-attached
327 // rfc822 messages.
328 if(!addfrom && !m->unixfrom){
329 m->unixheader = nil;
330 return;
331 }
332
333 m->unixheader = s_copy("From ");
334 if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
335 s_append(m->unixheader, s_to_c(m->unixfrom));
336 else if(m->from822)
337 s_append(m->unixheader, s_to_c(m->from822));
338 else
339 s_append(m->unixheader, "???");
340
341 s_append(m->unixheader, " ");
342 if(m->unixdate)
343 s_append(m->unixheader, s_to_c(m->unixdate));
344 else
345 s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970");
346
347 s_append(m->unixheader, "\n");
348 }
349
350 String*
promote(String ** sp)351 promote(String **sp)
352 {
353 String *s;
354
355 if(*sp != nil)
356 s = s_clone(*sp);
357 else
358 s = nil;
359 return s;
360 }
361
362 void
parsebody(Message * m,Mailbox * mb)363 parsebody(Message *m, Mailbox *mb)
364 {
365 Message *nm;
366
367 // recurse
368 if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
369 parseattachments(m, mb);
370 } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
371 decode(m);
372 parseattachments(m, mb);
373 nm = m->part;
374
375 // promote headers
376 if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
377 m->from822 = promote(&nm->from822);
378 m->to822 = promote(&nm->to822);
379 m->date822 = promote(&nm->date822);
380 m->sender822 = promote(&nm->sender822);
381 m->replyto822 = promote(&nm->replyto822);
382 m->subject822 = promote(&nm->subject822);
383 m->unixdate = promote(&nm->unixdate);
384 }
385 }
386 }
387
388 void
parse(Message * m,int justmime,Mailbox * mb,int addfrom)389 parse(Message *m, int justmime, Mailbox *mb, int addfrom)
390 {
391 parseheaders(m, justmime, mb, addfrom);
392 parsebody(m, mb);
393 }
394
395 static void
parseattachments(Message * m,Mailbox * mb)396 parseattachments(Message *m, Mailbox *mb)
397 {
398 Message *nm, **l;
399 char *p, *x;
400
401 // if there's a boundary, recurse...
402 if(m->boundary != nil){
403 p = m->body;
404 nm = nil;
405 l = &m->part;
406 for(;;){
407 x = strstr(p, s_to_c(m->boundary));
408
409 /* no boundary, we're done */
410 if(x == nil){
411 if(nm != nil)
412 nm->rbend = nm->bend = nm->end = m->bend;
413 break;
414 }
415
416 /* boundary must be at the start of a line */
417 if(x != m->body && *(x-1) != '\n'){
418 p = x+1;
419 continue;
420 }
421
422 if(nm != nil)
423 nm->rbend = nm->bend = nm->end = x;
424 x += strlen(s_to_c(m->boundary));
425
426 /* is this the last part? ignore anything after it */
427 if(strncmp(x, "--", 2) == 0)
428 break;
429
430 p = strchr(x, '\n');
431 if(p == nil)
432 break;
433 nm = newmessage(m);
434 nm->start = nm->header = nm->body = nm->rbody = ++p;
435 nm->mheader = nm->header;
436 *l = nm;
437 l = &nm->next;
438 }
439 for(nm = m->part; nm != nil; nm = nm->next)
440 parse(nm, 1, mb, 0);
441 return;
442 }
443
444 // if we've got an rfc822 message, recurse...
445 if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
446 nm = newmessage(m);
447 m->part = nm;
448 nm->start = nm->header = nm->body = nm->rbody = m->body;
449 nm->end = nm->bend = nm->rbend = m->bend;
450 parse(nm, 0, mb, 0);
451 }
452 }
453
454 /*
455 * pick up a header line
456 */
457 static int
headerline(char ** pp,String * hl)458 headerline(char **pp, String *hl)
459 {
460 char *p, *x;
461
462 s_reset(hl);
463 p = *pp;
464 x = strpbrk(p, ":\n");
465 if(x == nil || *x == '\n')
466 return 0;
467 for(;;){
468 x = strchr(p, '\n');
469 if(x == nil)
470 x = p + strlen(p);
471 s_nappend(hl, p, x-p);
472 p = x;
473 if(*p != '\n' || *++p != ' ' && *p != '\t')
474 break;
475 while(*p == ' ' || *p == '\t')
476 p++;
477 s_putc(hl, ' ');
478 }
479 *pp = p;
480 return 1;
481 }
482
483 /* returns nil iff there are no addressees */
484 static String*
addr822(char * p)485 addr822(char *p)
486 {
487 String *s, *list;
488 int incomment, addrdone, inanticomment, quoted;
489 int n;
490 int c;
491
492 list = s_new();
493 s = s_new();
494 quoted = incomment = addrdone = inanticomment = 0;
495 n = 0;
496 for(; *p; p++){
497 c = *p;
498
499 // whitespace is ignored
500 if(!quoted && isspace(c) || c == '\r')
501 continue;
502
503 // strings are always treated as atoms
504 if(!quoted && c == '"'){
505 if(!addrdone && !incomment)
506 s_putc(s, c);
507 for(p++; *p; p++){
508 if(!addrdone && !incomment)
509 s_putc(s, *p);
510 if(!quoted && *p == '"')
511 break;
512 if(*p == '\\')
513 quoted = 1;
514 else
515 quoted = 0;
516 }
517 if(*p == 0)
518 break;
519 quoted = 0;
520 continue;
521 }
522
523 // ignore everything in an expicit comment
524 if(!quoted && c == '('){
525 incomment = 1;
526 continue;
527 }
528 if(incomment){
529 if(!quoted && c == ')')
530 incomment = 0;
531 quoted = 0;
532 continue;
533 }
534
535 // anticomments makes everything outside of them comments
536 if(!quoted && c == '<' && !inanticomment){
537 inanticomment = 1;
538 s = s_reset(s);
539 continue;
540 }
541 if(!quoted && c == '>' && inanticomment){
542 addrdone = 1;
543 inanticomment = 0;
544 continue;
545 }
546
547 // commas separate addresses
548 if(!quoted && c == ',' && !inanticomment){
549 s_terminate(s);
550 addrdone = 0;
551 if(n++ != 0)
552 s_append(list, " ");
553 s_append(list, s_to_c(s));
554 s = s_reset(s);
555 continue;
556 }
557
558 // what's left is part of the address
559 s_putc(s, c);
560
561 // quoted characters are recognized only as characters
562 if(c == '\\')
563 quoted = 1;
564 else
565 quoted = 0;
566
567 }
568
569 if(*s_to_c(s) != 0){
570 s_terminate(s);
571 if(n++ != 0)
572 s_append(list, " ");
573 s_append(list, s_to_c(s));
574 }
575 s_free(s);
576
577 if(n == 0){ /* no addressees given, just the keyword */
578 s_free(list);
579 return nil;
580 }
581 return list;
582 }
583
584 /*
585 * per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by
586 * concatenating their values.
587 */
588
589 static void
to822(Message * m,Header * h,char * p)590 to822(Message *m, Header *h, char *p)
591 {
592 String *s;
593
594 p += strlen(h->type);
595 s = addr822(p);
596 if (m->to822 == nil)
597 m->to822 = s;
598 else if (s != nil) {
599 s_append(m->to822, " ");
600 s_append(m->to822, s_to_c(s));
601 s_free(s);
602 }
603 }
604
605 static void
cc822(Message * m,Header * h,char * p)606 cc822(Message *m, Header *h, char *p)
607 {
608 String *s;
609
610 p += strlen(h->type);
611 s = addr822(p);
612 if (m->cc822 == nil)
613 m->cc822 = s;
614 else if (s != nil) {
615 s_append(m->cc822, " ");
616 s_append(m->cc822, s_to_c(s));
617 s_free(s);
618 }
619 }
620
621 static void
bcc822(Message * m,Header * h,char * p)622 bcc822(Message *m, Header *h, char *p)
623 {
624 String *s;
625
626 p += strlen(h->type);
627 s = addr822(p);
628 if (m->bcc822 == nil)
629 m->bcc822 = s;
630 else if (s != nil) {
631 s_append(m->bcc822, " ");
632 s_append(m->bcc822, s_to_c(s));
633 s_free(s);
634 }
635 }
636
637 static void
from822(Message * m,Header * h,char * p)638 from822(Message *m, Header *h, char *p)
639 {
640 p += strlen(h->type);
641 s_free(m->from822);
642 m->from822 = addr822(p);
643 }
644
645 static void
sender822(Message * m,Header * h,char * p)646 sender822(Message *m, Header *h, char *p)
647 {
648 p += strlen(h->type);
649 s_free(m->sender822);
650 m->sender822 = addr822(p);
651 }
652
653 static void
replyto822(Message * m,Header * h,char * p)654 replyto822(Message *m, Header *h, char *p)
655 {
656 p += strlen(h->type);
657 s_free(m->replyto822);
658 m->replyto822 = addr822(p);
659 }
660
661 static void
mimeversion(Message * m,Header * h,char * p)662 mimeversion(Message *m, Header *h, char *p)
663 {
664 p += strlen(h->type);
665 s_free(m->mimeversion);
666 m->mimeversion = addr822(p);
667 }
668
669 static void
killtrailingwhite(char * p)670 killtrailingwhite(char *p)
671 {
672 char *e;
673
674 e = p + strlen(p) - 1;
675 while(e > p && isspace(*e))
676 *e-- = 0;
677 }
678
679 static void
date822(Message * m,Header * h,char * p)680 date822(Message *m, Header *h, char *p)
681 {
682 p += strlen(h->type);
683 p = skipwhite(p);
684 s_free(m->date822);
685 m->date822 = s_copy(p);
686 p = s_to_c(m->date822);
687 killtrailingwhite(p);
688 }
689
690 static void
subject822(Message * m,Header * h,char * p)691 subject822(Message *m, Header *h, char *p)
692 {
693 p += strlen(h->type);
694 p = skipwhite(p);
695 s_free(m->subject822);
696 m->subject822 = s_copy(p);
697 p = s_to_c(m->subject822);
698 killtrailingwhite(p);
699 }
700
701 static void
inreplyto822(Message * m,Header * h,char * p)702 inreplyto822(Message *m, Header *h, char *p)
703 {
704 p += strlen(h->type);
705 p = skipwhite(p);
706 s_free(m->inreplyto822);
707 m->inreplyto822 = s_copy(p);
708 p = s_to_c(m->inreplyto822);
709 killtrailingwhite(p);
710 }
711
712 static void
messageid822(Message * m,Header * h,char * p)713 messageid822(Message *m, Header *h, char *p)
714 {
715 p += strlen(h->type);
716 p = skipwhite(p);
717 s_free(m->messageid822);
718 m->messageid822 = s_copy(p);
719 p = s_to_c(m->messageid822);
720 killtrailingwhite(p);
721 }
722
723 static int
isattribute(char ** pp,char * attr)724 isattribute(char **pp, char *attr)
725 {
726 char *p;
727 int n;
728
729 n = strlen(attr);
730 p = *pp;
731 if(cistrncmp(p, attr, n) != 0)
732 return 0;
733 p += n;
734 while(*p == ' ')
735 p++;
736 if(*p++ != '=')
737 return 0;
738 while(*p == ' ')
739 p++;
740 *pp = p;
741 return 1;
742 }
743
744 static void
ctype(Message * m,Header * h,char * p)745 ctype(Message *m, Header *h, char *p)
746 {
747 String *s;
748
749 p += h->len;
750 p = skipwhite(p);
751
752 p = getstring(p, m->type, 1);
753
754 while(*p){
755 if(isattribute(&p, "boundary")){
756 s = s_new();
757 p = getstring(p, s, 0);
758 m->boundary = s_reset(m->boundary);
759 s_append(m->boundary, "--");
760 s_append(m->boundary, s_to_c(s));
761 s_free(s);
762 } else if(cistrncmp(p, "multipart", 9) == 0){
763 /*
764 * the first unbounded part of a multipart message,
765 * the preamble, is not displayed or saved
766 */
767 } else if(isattribute(&p, "name")){
768 if(m->filename == nil)
769 setfilename(m, p);
770 } else if(isattribute(&p, "charset")){
771 p = getstring(p, s_reset(m->charset), 0);
772 }
773
774 p = skiptosemi(p);
775 }
776 }
777
778 static void
cencoding(Message * m,Header * h,char * p)779 cencoding(Message *m, Header *h, char *p)
780 {
781 p += h->len;
782 p = skipwhite(p);
783 if(cistrncmp(p, "base64", 6) == 0)
784 m->encoding = Ebase64;
785 else if(cistrncmp(p, "quoted-printable", 16) == 0)
786 m->encoding = Equoted;
787 }
788
789 static void
cdisposition(Message * m,Header * h,char * p)790 cdisposition(Message *m, Header *h, char *p)
791 {
792 p += h->len;
793 p = skipwhite(p);
794 while(*p){
795 if(cistrncmp(p, "inline", 6) == 0){
796 m->disposition = Dinline;
797 } else if(cistrncmp(p, "attachment", 10) == 0){
798 m->disposition = Dfile;
799 } else if(cistrncmp(p, "filename=", 9) == 0){
800 p += 9;
801 setfilename(m, p);
802 }
803 p = skiptosemi(p);
804 }
805
806 }
807
808 ulong msgallocd, msgfreed;
809
810 Message*
newmessage(Message * parent)811 newmessage(Message *parent)
812 {
813 static int id;
814 Message *m;
815
816 msgallocd++;
817
818 m = emalloc(sizeof(*m));
819 memset(m, 0, sizeof(*m));
820 m->disposition = Dnone;
821 m->type = s_copy("text/plain");
822 m->charset = s_copy("iso-8859-1");
823 m->id = newid();
824 if(parent)
825 sprint(m->name, "%d", ++(parent->subname));
826 if(parent == nil)
827 parent = m;
828 m->whole = parent;
829 m->hlen = -1;
830 return m;
831 }
832
833 // delete a message from a mailbox
834 void
delmessage(Mailbox * mb,Message * m)835 delmessage(Mailbox *mb, Message *m)
836 {
837 Message **l;
838 int i;
839
840 mb->vers++;
841 msgfreed++;
842
843 if(m->whole != m){
844 // unchain from parent
845 for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
846 ;
847 if(*l != nil)
848 *l = m->next;
849
850 // clear out of name lookup hash table
851 if(m->whole->whole == m->whole)
852 hfree(PATH(mb->id, Qmbox), m->name);
853 else
854 hfree(PATH(m->whole->id, Qdir), m->name);
855 for(i = 0; i < Qmax; i++)
856 hfree(PATH(m->id, Qdir), dirtab[i]);
857 }
858
859 /* recurse through sub-parts */
860 while(m->part)
861 delmessage(mb, m->part);
862
863 /* free memory */
864 if(m->mallocd)
865 free(m->start);
866 if(m->hallocd)
867 free(m->header);
868 if(m->ballocd)
869 free(m->body);
870 s_free(m->unixfrom);
871 s_free(m->unixdate);
872 s_free(m->unixheader);
873 s_free(m->from822);
874 s_free(m->sender822);
875 s_free(m->to822);
876 s_free(m->bcc822);
877 s_free(m->cc822);
878 s_free(m->replyto822);
879 s_free(m->date822);
880 s_free(m->inreplyto822);
881 s_free(m->subject822);
882 s_free(m->messageid822);
883 s_free(m->addrs);
884 s_free(m->mimeversion);
885 s_free(m->sdigest);
886 s_free(m->boundary);
887 s_free(m->type);
888 s_free(m->charset);
889 s_free(m->filename);
890
891 free(m);
892 }
893
894 // mark messages (identified by path) for deletion
895 void
delmessages(int ac,char ** av)896 delmessages(int ac, char **av)
897 {
898 Mailbox *mb;
899 Message *m;
900 int i, needwrite;
901
902 qlock(&mbllock);
903 for(mb = mbl; mb != nil; mb = mb->next)
904 if(strcmp(av[0], mb->name) == 0){
905 qlock(mb);
906 break;
907 }
908 qunlock(&mbllock);
909 if(mb == nil)
910 return;
911
912 needwrite = 0;
913 for(i = 1; i < ac; i++){
914 for(m = mb->root->part; m != nil; m = m->next)
915 if(strcmp(m->name, av[i]) == 0){
916 if(!m->deleted){
917 mailplumb(mb, m, 1);
918 needwrite = 1;
919 m->deleted = 1;
920 logmsg("deleting", m);
921 }
922 break;
923 }
924 }
925 if(needwrite)
926 syncmbox(mb, 1);
927 qunlock(mb);
928 }
929
930 /*
931 * the following are called with the mailbox qlocked
932 */
933 void
msgincref(Message * m)934 msgincref(Message *m)
935 {
936 m->refs++;
937 }
938 void
msgdecref(Mailbox * mb,Message * m)939 msgdecref(Mailbox *mb, Message *m)
940 {
941 m->refs--;
942 if(m->refs == 0 && m->deleted)
943 syncmbox(mb, 1);
944 }
945
946 /*
947 * the following are called with mbllock'd
948 */
949 void
mboxincref(Mailbox * mb)950 mboxincref(Mailbox *mb)
951 {
952 assert(mb->refs > 0);
953 mb->refs++;
954 }
955 void
mboxdecref(Mailbox * mb)956 mboxdecref(Mailbox *mb)
957 {
958 assert(mb->refs > 0);
959 qlock(mb);
960 mb->refs--;
961 if(mb->refs == 0){
962 delmessage(mb, mb->root);
963 if(mb->ctl)
964 hfree(PATH(mb->id, Qmbox), "ctl");
965 if(mb->close)
966 (*mb->close)(mb);
967 free(mb);
968 } else
969 qunlock(mb);
970 }
971
972 int
cistrncmp(char * a,char * b,int n)973 cistrncmp(char *a, char *b, int n)
974 {
975 while(n-- > 0){
976 if(tolower(*a++) != tolower(*b++))
977 return -1;
978 }
979 return 0;
980 }
981
982 int
cistrcmp(char * a,char * b)983 cistrcmp(char *a, char *b)
984 {
985 for(;;){
986 if(tolower(*a) != tolower(*b++))
987 return -1;
988 if(*a++ == 0)
989 break;
990 }
991 return 0;
992 }
993
994 static char*
skipwhite(char * p)995 skipwhite(char *p)
996 {
997 while(isspace(*p))
998 p++;
999 return p;
1000 }
1001
1002 static char*
skiptosemi(char * p)1003 skiptosemi(char *p)
1004 {
1005 while(*p && *p != ';')
1006 p++;
1007 while(*p == ';' || isspace(*p))
1008 p++;
1009 return p;
1010 }
1011
1012 static char*
getstring(char * p,String * s,int dolower)1013 getstring(char *p, String *s, int dolower)
1014 {
1015 s = s_reset(s);
1016 p = skipwhite(p);
1017 if(*p == '"'){
1018 p++;
1019 for(;*p && *p != '"'; p++)
1020 if(dolower)
1021 s_putc(s, tolower(*p));
1022 else
1023 s_putc(s, *p);
1024 if(*p == '"')
1025 p++;
1026 s_terminate(s);
1027
1028 return p;
1029 }
1030
1031 for(; *p && !isspace(*p) && *p != ';'; p++)
1032 if(dolower)
1033 s_putc(s, tolower(*p));
1034 else
1035 s_putc(s, *p);
1036 s_terminate(s);
1037
1038 return p;
1039 }
1040
1041 static void
setfilename(Message * m,char * p)1042 setfilename(Message *m, char *p)
1043 {
1044 m->filename = s_reset(m->filename);
1045 getstring(p, m->filename, 0);
1046 for(p = s_to_c(m->filename); *p; p++)
1047 if(*p == ' ' || *p == '\t' || *p == ';')
1048 *p = '_';
1049 }
1050
1051 //
1052 // undecode message body
1053 //
1054 void
decode(Message * m)1055 decode(Message *m)
1056 {
1057 int i, len;
1058 char *x;
1059
1060 if(m->decoded)
1061 return;
1062 switch(m->encoding){
1063 case Ebase64:
1064 len = m->bend - m->body;
1065 i = (len*3)/4+1; // room for max chars + null
1066 x = emalloc(i);
1067 len = dec64((uchar*)x, i, m->body, len);
1068 if(m->ballocd)
1069 free(m->body);
1070 m->body = x;
1071 m->bend = x + len;
1072 m->ballocd = 1;
1073 break;
1074 case Equoted:
1075 len = m->bend - m->body;
1076 x = emalloc(len+2); // room for null and possible extra nl
1077 len = decquoted(x, m->body, m->bend, 0);
1078 if(m->ballocd)
1079 free(m->body);
1080 m->body = x;
1081 m->bend = x + len;
1082 m->ballocd = 1;
1083 break;
1084 default:
1085 break;
1086 }
1087 m->decoded = 1;
1088 }
1089
1090 // convert latin1 to utf
1091 void
convert(Message * m)1092 convert(Message *m)
1093 {
1094 int len;
1095 char *x;
1096
1097 // don't convert if we're not a leaf, not text, or already converted
1098 if(m->converted)
1099 return;
1100 if(m->part != nil)
1101 return;
1102 if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
1103 return;
1104
1105 len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend);
1106 if(len > 0){
1107 if(m->ballocd)
1108 free(m->body);
1109 m->body = x;
1110 m->bend = x + len;
1111 m->ballocd = 1;
1112 }
1113 m->converted = 1;
1114 }
1115
1116 static int
hex2int(int x)1117 hex2int(int x)
1118 {
1119 if(x >= '0' && x <= '9')
1120 return x - '0';
1121 if(x >= 'A' && x <= 'F')
1122 return (x - 'A') + 10;
1123 if(x >= 'a' && x <= 'f')
1124 return (x - 'a') + 10;
1125 return -1;
1126 }
1127
1128 // underscores are translated in 2047 headers (uscores=1)
1129 // but not in the body (uscores=0)
1130 static char*
decquotedline(char * out,char * in,char * e,int uscores)1131 decquotedline(char *out, char *in, char *e, int uscores)
1132 {
1133 int c, c2, soft;
1134
1135 /* dump trailing white space */
1136 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1137 e--;
1138
1139 /* trailing '=' means no newline */
1140 if(*e == '='){
1141 soft = 1;
1142 e--;
1143 } else
1144 soft = 0;
1145
1146 while(in <= e){
1147 c = (*in++) & 0xff;
1148 switch(c){
1149 case '_':
1150 if(uscores){
1151 *out++ = ' ';
1152 break;
1153 }
1154 default:
1155 *out++ = c;
1156 break;
1157 case '=':
1158 c = hex2int(*in++);
1159 c2 = hex2int(*in++);
1160 if (c != -1 && c2 != -1)
1161 *out++ = c<<4 | c2;
1162 else {
1163 *out++ = '=';
1164 in -= 2;
1165 }
1166 break;
1167 }
1168 }
1169 if(!soft)
1170 *out++ = '\n';
1171 *out = 0;
1172
1173 return out;
1174 }
1175
1176 int
decquoted(char * out,char * in,char * e,int uscores)1177 decquoted(char *out, char *in, char *e, int uscores)
1178 {
1179 char *p, *nl;
1180
1181 p = out;
1182 while((nl = strchr(in, '\n')) != nil && nl < e){
1183 p = decquotedline(p, in, nl, uscores);
1184 in = nl + 1;
1185 }
1186 if(in < e)
1187 p = decquotedline(p, in, e-1, uscores);
1188
1189 // make sure we end with a new line
1190 if(*(p-1) != '\n'){
1191 *p++ = '\n';
1192 *p = 0;
1193 }
1194
1195 return p - out;
1196 }
1197
1198 static char*
lowercase(char * p)1199 lowercase(char *p)
1200 {
1201 char *op;
1202 int c;
1203
1204 for(op = p; c = *p; p++)
1205 if(isupper(c))
1206 *p = tolower(c);
1207 return op;
1208 }
1209
1210 // translate latin1 directly since it fits neatly in utf
1211 static int
latin1toutf(char ** out,char * in,char * e)1212 latin1toutf(char **out, char *in, char *e)
1213 {
1214 int n;
1215 char *p;
1216 Rune r;
1217
1218 n = 0;
1219 for(p = in; p < e; p++)
1220 if(*p & 0x80)
1221 n++;
1222 if(n == 0)
1223 return 0;
1224
1225 n += e-in;
1226 *out = p = malloc(n+1);
1227 if(p == nil)
1228 return 0;
1229
1230 for(; in < e; in++){
1231 r = (uchar)*in;
1232 p += runetochar(p, &r);
1233 }
1234 *p = 0;
1235 return p - *out;
1236 }
1237
1238 // translate any thing using the tcs program
1239 int
xtoutf(char * charset,char ** out,char * in,char * e)1240 xtoutf(char *charset, char **out, char *in, char *e)
1241 {
1242 char *av[4];
1243 int totcs[2];
1244 int fromtcs[2];
1245 int n, len, sofar;
1246 char *p;
1247
1248 // might not need to convert
1249 if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0)
1250 return 0;
1251 if(cistrcmp(charset, "iso-8859-1") == 0)
1252 return latin1toutf(out, in, e);
1253
1254 len = e-in+1;
1255 sofar = 0;
1256 *out = p = malloc(len+1);
1257 if(p == nil)
1258 return 0;
1259
1260 av[0] = charset;
1261 av[1] = "-f";
1262 av[2] = charset;
1263 av[3] = 0;
1264 if(pipe(totcs) < 0)
1265 goto error;
1266 if(pipe(fromtcs) < 0){
1267 close(totcs[0]); close(totcs[1]);
1268 goto error;
1269 }
1270 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1271 case -1:
1272 close(fromtcs[0]); close(fromtcs[1]);
1273 close(totcs[0]); close(totcs[1]);
1274 goto error;
1275 case 0:
1276 close(fromtcs[0]); close(totcs[1]);
1277 dup(fromtcs[1], 1);
1278 dup(totcs[0], 0);
1279 close(fromtcs[1]); close(totcs[0]);
1280 dup(open("/dev/null", OWRITE), 2);
1281 exec("/bin/tcs", av);
1282 _exits(0);
1283 default:
1284 close(fromtcs[1]); close(totcs[0]);
1285 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1286 case -1:
1287 close(fromtcs[0]); close(totcs[1]);
1288 goto error;
1289 case 0:
1290 close(fromtcs[0]);
1291 while(in < e){
1292 n = write(totcs[1], in, e-in);
1293 if(n <= 0)
1294 break;
1295 in += n;
1296 }
1297 close(totcs[1]);
1298 _exits(0);
1299 default:
1300 close(totcs[1]);
1301 for(;;){
1302 n = read(fromtcs[0], &p[sofar], len-sofar);
1303 if(n <= 0)
1304 break;
1305 sofar += n;
1306 p[sofar] = 0;
1307 if(sofar == len){
1308 len += 1024;
1309 p = realloc(p, len+1);
1310 if(p == nil)
1311 goto error;
1312 *out = p;
1313 }
1314 }
1315 close(fromtcs[0]);
1316 break;
1317 }
1318 break;
1319 }
1320 if(sofar == 0)
1321 goto error;
1322 return sofar;
1323
1324 error:
1325 free(*out);
1326 *out = nil;
1327 return 0;
1328 }
1329
1330 void *
emalloc(ulong n)1331 emalloc(ulong n)
1332 {
1333 void *p;
1334
1335 p = mallocz(n, 1);
1336 if(!p){
1337 fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
1338 exits("out of memory");
1339 }
1340 setmalloctag(p, getcallerpc(&n));
1341 return p;
1342 }
1343
1344 void *
erealloc(void * p,ulong n)1345 erealloc(void *p, ulong n)
1346 {
1347 if(n == 0)
1348 n = 1;
1349 p = realloc(p, n);
1350 if(!p){
1351 fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
1352 exits("out of memory");
1353 }
1354 setrealloctag(p, getcallerpc(&p));
1355 return p;
1356 }
1357
1358 void
mailplumb(Mailbox * mb,Message * m,int delete)1359 mailplumb(Mailbox *mb, Message *m, int delete)
1360 {
1361 Plumbmsg p;
1362 Plumbattr a[7];
1363 char buf[256];
1364 int ai;
1365 char lenstr[10], *from, *subject, *date;
1366 static int fd = -1;
1367
1368 if(m->subject822 == nil)
1369 subject = "";
1370 else
1371 subject = s_to_c(m->subject822);
1372
1373 if(m->from822 != nil)
1374 from = s_to_c(m->from822);
1375 else if(m->unixfrom != nil)
1376 from = s_to_c(m->unixfrom);
1377 else
1378 from = "";
1379
1380 if(m->unixdate != nil)
1381 date = s_to_c(m->unixdate);
1382 else
1383 date = "";
1384
1385 sprint(lenstr, "%ld", m->end-m->start);
1386
1387 if(biffing && !delete)
1388 print("[ %s / %s / %s ]\n", from, subject, lenstr);
1389
1390 if(!plumbing)
1391 return;
1392
1393 if(fd < 0)
1394 fd = plumbopen("send", OWRITE);
1395 if(fd < 0)
1396 return;
1397
1398 p.src = "mailfs";
1399 p.dst = "seemail";
1400 p.wdir = "/mail/fs";
1401 p.type = "text";
1402
1403 ai = 0;
1404 a[ai].name = "filetype";
1405 a[ai].value = "mail";
1406
1407 a[++ai].name = "sender";
1408 a[ai].value = from;
1409 a[ai-1].next = &a[ai];
1410
1411 a[++ai].name = "length";
1412 a[ai].value = lenstr;
1413 a[ai-1].next = &a[ai];
1414
1415 a[++ai].name = "mailtype";
1416 a[ai].value = delete?"delete":"new";
1417 a[ai-1].next = &a[ai];
1418
1419 a[++ai].name = "date";
1420 a[ai].value = date;
1421 a[ai-1].next = &a[ai];
1422
1423 if(m->sdigest){
1424 a[++ai].name = "digest";
1425 a[ai].value = s_to_c(m->sdigest);
1426 a[ai-1].next = &a[ai];
1427 }
1428
1429 a[ai].next = nil;
1430
1431 p.attr = a;
1432 snprint(buf, sizeof(buf), "%s/%s/%s",
1433 mntpt, mb->name, m->name);
1434 p.ndata = strlen(buf);
1435 p.data = buf;
1436
1437 plumbsend(fd, &p);
1438 }
1439
1440 //
1441 // count the number of lines in the body (for imap4)
1442 //
1443 void
countlines(Message * m)1444 countlines(Message *m)
1445 {
1446 int i;
1447 char *p;
1448
1449 i = 0;
1450 for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
1451 i++;
1452 sprint(m->lines, "%d", i);
1453 }
1454
1455 char *LOG = "fs";
1456
1457 void
logmsg(char * s,Message * m)1458 logmsg(char *s, Message *m)
1459 {
1460 int pid;
1461
1462 if(!logging)
1463 return;
1464 pid = getpid();
1465 if(m == nil)
1466 syslog(0, LOG, "%s.%d: %s", user, pid, s);
1467 else
1468 syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
1469 user, pid, s,
1470 m->from822 ? s_to_c(m->from822) : "?",
1471 s_to_c(m->sdigest));
1472 }
1473
1474 /*
1475 * squeeze nulls out of the body
1476 */
1477 static void
nullsqueeze(Message * m)1478 nullsqueeze(Message *m)
1479 {
1480 char *p, *q;
1481
1482 q = memchr(m->body, 0, m->end-m->body);
1483 if(q == nil)
1484 return;
1485
1486 for(p = m->body; q < m->end; q++){
1487 if(*q == 0)
1488 continue;
1489 *p++ = *q;
1490 }
1491 m->bend = m->rbend = m->end = p;
1492 }
1493
1494
1495 //
1496 // convert an RFC822 date into a Unix style date
1497 // for when the Unix From line isn't there (e.g. POP3).
1498 // enough client programs depend on having a Unix date
1499 // that it's easiest to write this conversion code once, right here.
1500 //
1501 // people don't follow RFC822 particularly closely,
1502 // so we use strtotm, which is a bunch of heuristics.
1503 //
1504
1505 extern int strtotm(char*, Tm*);
1506 String*
date822tounix(char * s)1507 date822tounix(char *s)
1508 {
1509 char *p, *q;
1510 Tm tm;
1511
1512 if(strtotm(s, &tm) < 0)
1513 return nil;
1514
1515 p = asctime(&tm);
1516 if(q = strchr(p, '\n'))
1517 *q = '\0';
1518 return s_copy(p);
1519 }
1520
1521