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