Blob


1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include <thread.h>
6 #include "dat.h"
8 extern char* dirtab[]; /* jpc */
10 typedef struct Header Header;
12 struct Header {
13 char *type;
14 void (*f)(Message*, Header*, char*);
15 int len;
16 };
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 };
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, },
53 [Mhead] { "content-type:", ctype, },
54 { "content-transfer-encoding:", cencoding, },
55 { "content-disposition:", cdisposition, },
56 { 0, }
57 };
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*);
73 int debug;
75 char *Enotme = "path not served by this file server";
77 enum
78 {
79 Chunksize = 1024
80 };
82 Mailboxinit *boxinit[] = {
83 imap4mbox,
84 pop3mbox,
85 plan9mbox
86 };
88 char*
89 syncmbox(Mailbox *mb, int doplumb)
90 {
91 return (*mb->sync)(mb, doplumb);
92 }
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;
102 initheaders();
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";
116 strncpy(mb->name, p, sizeof(mb->name)-1);
117 } else {
118 strncpy(mb->name, name, sizeof(mb->name)-1);
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";
131 /* on error, give up */
132 if(rv){
133 free(mb);
134 return rv;
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;
153 /* always try locking */
154 mb->dolock = 1;
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);
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);
169 rv = syncmbox(mb, 0);
170 qunlock(&mb->ql);
172 return rv;
175 /* close the named mailbox */
176 void
177 freembox(char *name)
179 Mailbox **l, *mb;
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;
190 hfree(PATH(0, Qtop), name);
191 qunlock(&mbllock);
194 static void
195 initheaders(void)
197 Header *h;
198 static int already;
200 if(already)
201 return;
202 already = 1;
204 for(h = head; h->type != nil; h++)
205 h->len = strlen(h->type);
208 /*
209 * parse a Unix style header
210 */
211 void
212 parseunix(Message *m)
214 char *p;
215 String *h;
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);
223 m->unixfrom = s_parse(h, s_reset(m->unixfrom));
224 m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
226 s_free(h);
229 /*
230 * parse a message
231 */
232 void
233 parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
235 String *hl;
236 Header *h;
237 char *p, *q;
238 int i;
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);
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);
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;
265 s_reset(hl);
267 s_free(hl);
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;
276 if(*p == '\n')
277 p++;
278 m->rbody = m->body = p;
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);
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;
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++;
309 if(p){
310 *p = '\0';
311 m->unixdate = date822tounix(q+1);
312 *p = '\n';
317 /* fall back on the rfc822 date */
318 if(m->unixdate==nil && m->date822)
319 m->unixdate = date822tounix(s_to_c(m->date822));
322 if(m->unixheader != nil)
323 s_free(m->unixheader);
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;
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, "???");
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");
348 s_append(m->unixheader, "\n");
351 String*
352 promote(String **sp)
354 String *s;
356 if(*sp != nil)
357 s = s_clone(*sp);
358 else
359 s = nil;
360 return s;
363 void
364 parsebody(Message *m, Mailbox *mb)
366 Message *nm;
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;
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);
389 void
390 parse(Message *m, int justmime, Mailbox *mb, int addfrom)
392 parseheaders(m, justmime, mb, addfrom);
393 parsebody(m, mb);
396 static void
397 parseattachments(Message *m, Mailbox *mb)
399 Message *nm, **l;
400 char *p, *x;
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));
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;
417 /* boundary must be at the start of a line */
418 if(x != m->body && *(x-1) != '\n'){
419 p = x+1;
420 continue;
423 if(nm != nil)
424 nm->rbend = nm->bend = nm->end = x;
425 x += strlen(s_to_c(m->boundary));
427 /* is this the last part? ignore anything after it */
428 if(strncmp(x, "--", 2) == 0)
429 break;
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;
440 for(nm = m->part; nm != nil; nm = nm->next)
441 parse(nm, 1, mb, 0);
442 return;
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);
455 /*
456 * pick up a header line
457 */
458 static int
459 headerline(char **pp, String *hl)
461 char *p, *x;
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, ' ');
480 *pp = p;
481 return 1;
484 static String*
485 addr822(char *p)
487 String *s, *list;
488 int incomment, addrdone, inanticomment, quoted;
489 int n;
490 int c;
492 list = s_new();
493 s = s_new();
494 quoted = incomment = addrdone = inanticomment = 0;
495 n = 0;
496 for(; *p; p++){
497 c = *p;
499 /* whitespace is ignored */
500 if(!quoted && isspace(c) || c == '\r')
501 continue;
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;
517 if(*p == 0)
518 break;
519 quoted = 0;
520 continue;
523 /* ignore everything in an expicit comment */
524 if(!quoted && c == '('){
525 incomment = 1;
526 continue;
528 if(incomment){
529 if(!quoted && c == ')')
530 incomment = 0;
531 quoted = 0;
532 continue;
535 /* anticomments makes everything outside of them comments */
536 if(!quoted && c == '<' && !inanticomment){
537 inanticomment = 1;
538 s = s_reset(s);
539 continue;
541 if(!quoted && c == '>' && inanticomment){
542 addrdone = 1;
543 inanticomment = 0;
544 continue;
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;
558 /* what's left is part of the address */
559 s_putc(s, c);
561 /* quoted characters are recognized only as characters */
562 if(c == '\\')
563 quoted = 1;
564 else
565 quoted = 0;
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));
575 s_free(s);
577 if(n == 0){
578 s_free(list);
579 return nil;
581 return list;
584 static void
585 to822(Message *m, Header *h, char *p)
587 p += strlen(h->type);
588 s_free(m->to822);
589 m->to822 = addr822(p);
592 static void
593 cc822(Message *m, Header *h, char *p)
595 p += strlen(h->type);
596 s_free(m->cc822);
597 m->cc822 = addr822(p);
600 static void
601 bcc822(Message *m, Header *h, char *p)
603 p += strlen(h->type);
604 s_free(m->bcc822);
605 m->bcc822 = addr822(p);
608 static void
609 from822(Message *m, Header *h, char *p)
611 p += strlen(h->type);
612 s_free(m->from822);
613 m->from822 = addr822(p);
616 static void
617 sender822(Message *m, Header *h, char *p)
619 p += strlen(h->type);
620 s_free(m->sender822);
621 m->sender822 = addr822(p);
624 static void
625 replyto822(Message *m, Header *h, char *p)
627 p += strlen(h->type);
628 s_free(m->replyto822);
629 m->replyto822 = addr822(p);
632 static void
633 mimeversion(Message *m, Header *h, char *p)
635 p += strlen(h->type);
636 s_free(m->mimeversion);
637 m->mimeversion = addr822(p);
640 static void
641 killtrailingwhite(char *p)
643 char *e;
645 e = p + strlen(p) - 1;
646 while(e > p && isspace(*e))
647 *e-- = 0;
650 static void
651 date822(Message *m, Header *h, char *p)
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);
661 static void
662 subject822(Message *m, Header *h, char *p)
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);
672 static void
673 inreplyto822(Message *m, Header *h, char *p)
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);
683 static void
684 messageid822(Message *m, Header *h, char *p)
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);
694 static int
695 isattribute(char **pp, char *attr)
697 char *p;
698 int n;
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;
715 static void
716 ctype(Message *m, Header *h, char *p)
718 String *s;
720 p += h->len;
721 p = skipwhite(p);
723 p = getstring(p, m->type, 1);
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);
745 p = skiptosemi(p);
749 static void
750 cencoding(Message *m, Header *h, char *p)
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;
760 static void
761 cdisposition(Message *m, Header *h, char *p)
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);
774 p = skiptosemi(p);
779 ulong msgallocd, msgfreed;
781 Message*
782 newmessage(Message *parent)
784 /* static int id; jpc */
785 Message *m;
787 msgallocd++;
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;
804 /* delete a message from a mailbox */
805 void
806 delmessage(Mailbox *mb, Message *m)
808 Message **l;
809 int i;
811 mb->vers++;
812 msgfreed++;
814 if(m->whole != m){
815 /* unchain from parent */
816 for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
818 if(*l != nil)
819 *l = m->next;
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]);
830 /* recurse through sub-parts */
831 while(m->part)
832 delmessage(mb, m->part);
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);
862 free(m);
865 /* mark messages (identified by path) for deletion */
866 void
867 delmessages(int ac, char **av)
869 Mailbox *mb;
870 Message *m;
871 int i, needwrite;
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;
879 qunlock(&mbllock);
880 if(mb == nil)
881 return;
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);
893 break;
896 if(needwrite)
897 syncmbox(mb, 1);
898 qunlock(&mb->ql);
901 /*
902 * the following are called with the mailbox qlocked
903 */
904 void
905 msgincref(Message *m)
907 m->refs++;
909 void
910 msgdecref(Mailbox *mb, Message *m)
912 m->refs--;
913 if(m->refs == 0 && m->deleted)
914 syncmbox(mb, 1);
917 /*
918 * the following are called with mbllock'd
919 */
920 void
921 mboxincref(Mailbox *mb)
923 assert(mb->refs > 0);
924 mb->refs++;
926 void
927 mboxdecref(Mailbox *mb)
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);
943 int
944 cistrncmp(char *a, char *b, int n)
946 while(n-- > 0){
947 if(tolower(*a++) != tolower(*b++))
948 return -1;
950 return 0;
953 int
954 cistrcmp(char *a, char *b)
956 for(;;){
957 if(tolower(*a) != tolower(*b++))
958 return -1;
959 if(*a++ == 0)
960 break;
962 return 0;
965 static char*
966 skipwhite(char *p)
968 while(isspace(*p))
969 p++;
970 return p;
973 static char*
974 skiptosemi(char *p)
976 while(*p && *p != ';')
977 p++;
978 while(*p == ';' || isspace(*p))
979 p++;
980 return p;
983 static char*
984 getstring(char *p, String *s, int dolower)
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);
999 return p;
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);
1009 return p;
1012 static void
1013 setfilename(Message *m, char *p)
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 = '_';
1022 /* */
1023 /* undecode message body */
1024 /* */
1025 void
1026 decode(Message *m)
1028 int i, len;
1029 char *x;
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;
1058 m->decoded = 1;
1061 /* convert latin1 to utf */
1062 void
1063 convert(Message *m)
1065 int len;
1066 char *x;
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;
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;
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;
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;
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;
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;
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;
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;
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;
1158 m->converted = 1;
1161 enum
1163 Self= 1,
1164 Hex= 2
1166 uchar tableqp[256];
1168 static void
1169 initquoted(void)
1171 int c;
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;
1182 static int
1183 hex2int(int x)
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;
1194 static char*
1195 decquotedline(char *out, char *in, char *e)
1197 int c, soft;
1199 /* dump trailing white space */
1200 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1201 e--;
1203 /* trailing '=' means no newline */
1204 if(*e == '='){
1205 soft = 1;
1206 e--;
1207 } else
1208 soft = 0;
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;
1223 if(!soft)
1224 *out++ = '\n';
1225 *out = 0;
1227 return out;
1230 int
1231 decquoted(char *out, char *in, char *e)
1233 char *p, *nl;
1235 if(tableqp[' '] == 0)
1236 initquoted();
1238 p = out;
1239 while((nl = strchr(in, '\n')) != nil && nl < e){
1240 p = decquotedline(p, in, nl);
1241 in = nl + 1;
1243 if(in < e)
1244 p = decquotedline(p, in, e-1);
1246 /* make sure we end with a new line */
1247 if(*(p-1) != '\n'){
1248 *p++ = '\n';
1249 *p = 0;
1252 return p - out;
1255 #if 0 /* jpc */
1256 static char*
1257 lowercase(char *p)
1259 char *op;
1260 int c;
1262 for(op = p; c = *p; p++)
1263 if(isupper(c))
1264 *p = tolower(c);
1265 return op;
1267 #endif
1270 * return number of 8 bit characters
1272 static int
1273 is8bit(Message *m)
1275 int count = 0;
1276 char *p;
1278 for(p = m->body; p < m->bend; p++)
1279 if(*p & 0x80)
1280 count++;
1281 return count;
1284 /* translate latin1 directly since it fits neatly in utf */
1285 int
1286 latin1toutf(char *out, char *in, char *e)
1288 Rune r;
1289 char *p;
1291 p = out;
1292 for(; in < e; in++){
1293 r = (*in) & 0xff;
1294 p += runetochar(p, &r);
1296 *p = 0;
1297 return p - out;
1300 /* translate any thing else using the tcs program */
1301 int
1302 xtoutf(char *charset, char **out, char *in, char *e)
1304 char *av[4];
1305 int totcs[2];
1306 int fromtcs[2];
1307 int n, len, sofar;
1308 char *p;
1310 len = e-in+1;
1311 sofar = 0;
1312 *out = p = malloc(len+1);
1313 if(p == nil)
1314 return 0;
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;
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;
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;
1373 close(fromtcs[0]);
1374 break;
1376 break;
1378 return sofar;
1381 enum {
1382 Winstart= 0x7f,
1383 Winend= 0x9f
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'Ÿ'
1394 int
1395 windows1257toutf(char *out, char *in, char *e)
1397 Rune r;
1398 char *p;
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);
1407 *p = 0;
1408 return p - out;
1411 void *
1412 emalloc(ulong n)
1414 void *p;
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");
1421 setmalloctag(p, getcallerpc(&n));
1422 return p;
1425 void *
1426 erealloc(void *p, ulong n)
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");
1435 setrealloctag(p, getcallerpc(&p));
1436 return p;
1439 void
1440 mailplumb(Mailbox *mb, Message *m, int delete)
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;
1449 if(m->subject822 == nil)
1450 subject = "";
1451 else
1452 subject = s_to_c(m->subject822);
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 = "";
1461 if(m->unixdate != nil)
1462 date = s_to_c(m->unixdate);
1463 else
1464 date = "";
1466 sprint(lenstr, "%ld", m->end-m->start);
1468 if(biffing && !delete)
1469 print("[ %s / %s / %s ]\n", from, subject, lenstr);
1471 if(!plumbing)
1472 return;
1474 if(fd < 0)
1475 fd = plumbopen("send", OWRITE);
1476 if(fd < 0)
1477 return;
1479 p.src = "mailfs";
1480 p.dst = "seemail";
1481 p.wdir = "/mail/fs";
1482 p.type = "text";
1484 ai = 0;
1485 a[ai].name = "filetype";
1486 a[ai].value = "mail";
1488 a[++ai].name = "sender";
1489 a[ai].value = from;
1490 a[ai-1].next = &a[ai];
1492 a[++ai].name = "length";
1493 a[ai].value = lenstr;
1494 a[ai-1].next = &a[ai];
1496 a[++ai].name = "mailtype";
1497 a[ai].value = delete?"delete":"new";
1498 a[ai-1].next = &a[ai];
1500 a[++ai].name = "date";
1501 a[ai].value = date;
1502 a[ai-1].next = &a[ai];
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];
1510 a[ai].next = nil;
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;
1518 plumbsend(fd, &p);
1521 /* */
1522 /* count the number of lines in the body (for imap4) */
1523 /* */
1524 void
1525 countlines(Message *m)
1527 int i;
1528 char *p;
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);
1536 char *LOG = "fs";
1538 void
1539 logmsg(char *s, Message *m)
1541 int pid;
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));
1556 * squeeze nulls out of the body
1558 static void
1559 nullsqueeze(Message *m)
1561 char *p, *q;
1563 q = memchr(m->body, 0, m->end-m->body);
1564 if(q == nil)
1565 return;
1567 for(p = m->body; q < m->end; q++){
1568 if(*q == 0)
1569 continue;
1570 *p++ = *q;
1572 m->bend = m->rbend = m->end = p;
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 /* */
1586 extern int strtotm(char*, Tm*);
1587 String*
1588 date822tounix(char *s)
1590 char *p, *q;
1591 Tm tm;
1593 if(strtotm(s, &tm) < 0)
1594 return nil;
1596 p = asctime(&tm);
1597 if(q = strchr(p, '\n'))
1598 *q = '\0';
1599 return s_copy(p);