8 extern char* dirtab[]; /* jpc */
10 typedef struct Header Header;
14 void (*f)(Message*, Header*, char*);
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*);
36 Mhead= 11, /* offset of first mime header */
41 { "date:", date822, },
42 { "from:", from822, },
44 { "sender:", sender822, },
45 { "reply-to:", replyto822, },
46 { "subject:", subject822, },
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, },
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*);
75 char *Enotme = "path not served by this file server";
82 Mailboxinit *boxinit[] = {
89 syncmbox(Mailbox *mb, int doplumb)
91 return (*mb->sync)(mb, doplumb);
94 /* create a new mailbox */
96 newmbox(char *path, char *name, int std)
104 mb = emalloc(sizeof(*mb));
105 strncpy(mb->path, path, sizeof(mb->path)-1);
107 p = strrchr(path, '/');
114 return "bad mbox name";
116 strncpy(mb->name, p, sizeof(mb->name)-1);
118 strncpy(mb->name, name, sizeof(mb->name)-1);
122 /* check for a mailbox type */
123 for(i=0; i<nelem(boxinit); i++)
124 if((rv = (*boxinit[i])(mb, path)) != Enotme)
126 if(i == nelem(boxinit)){
131 /* on error, give up */
137 /* make sure name isn't taken */
139 for(l = &mbl; *l != nil; l = &(*l)->next){
140 if(strcmp((*l)->name, mb->name) == 0){
141 if(strcmp(path, (*l)->path) == 0)
144 rv = "mbox name in use";
153 /* always try locking */
159 mb->root = newmessage(nil);
166 henter(PATH(mb->id, Qmbox), "ctl",
167 (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
169 rv = syncmbox(mb, 0);
175 /* close the named mailbox */
182 for(l=&mbl; *l != nil; l=&(*l)->next){
183 if(strcmp(name, (*l)->name) == 0){
190 hfree(PATH(0, Qtop), name);
204 for(h = head; h->type != nil; h++)
205 h->len = strlen(h->type);
209 * parse a Unix style header
212 parseunix(Message *m)
218 for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
223 m->unixfrom = s_parse(h, s_reset(m->unixfrom));
224 m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
233 parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
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);
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 */
254 while(headerline(&p, hl)){
260 if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
261 (*h->f)(m, h, s_to_c(hl));
269 /* the blank line isn't really part of the body or header */
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)
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. */
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){
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){
304 while((p = strchr(p, '\n')) != nil){
305 if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
311 m->unixdate = date822tounix(q+1);
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){
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));
338 s_append(m->unixheader, s_to_c(m->from822));
340 s_append(m->unixheader, "???");
342 s_append(m->unixheader, " ");
344 s_append(m->unixheader, s_to_c(m->unixdate));
346 s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970");
348 s_append(m->unixheader, "\n");
364 parsebody(Message *m, Mailbox *mb)
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){
373 parseattachments(m, mb);
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);
390 parse(Message *m, int justmime, Mailbox *mb, int addfrom)
392 parseheaders(m, justmime, mb, addfrom);
397 parseattachments(Message *m, Mailbox *mb)
402 /* if there's a boundary, recurse... */
403 if(m->boundary != nil){
408 x = strstr(p, s_to_c(m->boundary));
410 /* no boundary, we're done */
413 nm->rbend = nm->bend = nm->end = m->bend;
417 /* boundary must be at the start of a line */
418 if(x != m->body && *(x-1) != '\n'){
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)
435 nm->start = nm->header = nm->body = nm->rbody = ++p;
436 nm->mheader = nm->header;
440 for(nm = m->part; nm != nil; nm = nm->next)
445 /* if we've got an rfc822 message, recurse... */
446 if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
449 nm->start = nm->header = nm->body = nm->rbody = m->body;
450 nm->end = nm->bend = nm->rbend = m->bend;
456 * pick up a header line
459 headerline(char **pp, String *hl)
465 x = strpbrk(p, ":\n");
466 if(x == nil || *x == '\n')
472 s_nappend(hl, p, x-p);
474 if(*p != '\n' || *++p != ' ' && *p != '\t')
476 while(*p == ' ' || *p == '\t')
488 int incomment, addrdone, inanticomment, quoted;
494 quoted = incomment = addrdone = inanticomment = 0;
499 /* whitespace is ignored */
500 if(!quoted && isspace(c) || c == '\r')
503 /* strings are always treated as atoms */
504 if(!quoted && c == '"'){
505 if(!addrdone && !incomment)
508 if(!addrdone && !incomment)
510 if(!quoted && *p == '"')
523 /* ignore everything in an expicit comment */
524 if(!quoted && c == '('){
529 if(!quoted && c == ')')
535 /* anticomments makes everything outside of them comments */
536 if(!quoted && c == '<' && !inanticomment){
541 if(!quoted && c == '>' && inanticomment){
547 /* commas separate addresses */
548 if(!quoted && c == ',' && !inanticomment){
553 s_append(list, s_to_c(s));
558 /* what's left is part of the address */
561 /* quoted characters are recognized only as characters */
573 s_append(list, s_to_c(s));
585 to822(Message *m, Header *h, char *p)
587 p += strlen(h->type);
589 m->to822 = addr822(p);
593 cc822(Message *m, Header *h, char *p)
595 p += strlen(h->type);
597 m->cc822 = addr822(p);
601 bcc822(Message *m, Header *h, char *p)
603 p += strlen(h->type);
605 m->bcc822 = addr822(p);
609 from822(Message *m, Header *h, char *p)
611 p += strlen(h->type);
613 m->from822 = addr822(p);
617 sender822(Message *m, Header *h, char *p)
619 p += strlen(h->type);
620 s_free(m->sender822);
621 m->sender822 = addr822(p);
625 replyto822(Message *m, Header *h, char *p)
627 p += strlen(h->type);
628 s_free(m->replyto822);
629 m->replyto822 = addr822(p);
633 mimeversion(Message *m, Header *h, char *p)
635 p += strlen(h->type);
636 s_free(m->mimeversion);
637 m->mimeversion = addr822(p);
641 killtrailingwhite(char *p)
645 e = p + strlen(p) - 1;
646 while(e > p && isspace(*e))
651 date822(Message *m, Header *h, char *p)
653 p += strlen(h->type);
656 m->date822 = s_copy(p);
657 p = s_to_c(m->date822);
658 killtrailingwhite(p);
662 subject822(Message *m, Header *h, char *p)
664 p += strlen(h->type);
666 s_free(m->subject822);
667 m->subject822 = s_copy(p);
668 p = s_to_c(m->subject822);
669 killtrailingwhite(p);
673 inreplyto822(Message *m, Header *h, char *p)
675 p += strlen(h->type);
677 s_free(m->inreplyto822);
678 m->inreplyto822 = s_copy(p);
679 p = s_to_c(m->inreplyto822);
680 killtrailingwhite(p);
684 messageid822(Message *m, Header *h, char *p)
686 p += strlen(h->type);
688 s_free(m->messageid822);
689 m->messageid822 = s_copy(p);
690 p = s_to_c(m->messageid822);
691 killtrailingwhite(p);
695 isattribute(char **pp, char *attr)
702 if(cistrncmp(p, attr, n) != 0)
716 ctype(Message *m, Header *h, char *p)
723 p = getstring(p, m->type, 1);
726 if(isattribute(&p, "boundary")){
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));
733 } else if(cistrncmp(p, "multipart", 9) == 0){
735 * the first unbounded part of a multipart message,
736 * the preamble, is not displayed or saved
738 } else if(isattribute(&p, "name")){
739 if(m->filename == nil)
741 } else if(isattribute(&p, "charset")){
742 p = getstring(p, s_reset(m->charset), 0);
750 cencoding(Message *m, Header *h, char *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;
761 cdisposition(Message *m, Header *h, char *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){
779 ulong msgallocd, msgfreed;
782 newmessage(Message *parent)
784 /* static int id; jpc */
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");
796 sprint(m->name, "%d", ++(parent->subname));
804 /* delete a message from a mailbox */
806 delmessage(Mailbox *mb, Message *m)
815 /* unchain from parent */
816 for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
821 /* clear out of name lookup hash table */
822 if(m->whole->whole == m->whole)
823 hfree(PATH(mb->id, Qmbox), m->name);
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 */
832 delmessage(mb, m->part);
843 s_free(m->unixheader);
845 s_free(m->sender822);
849 s_free(m->replyto822);
851 s_free(m->inreplyto822);
852 s_free(m->subject822);
853 s_free(m->messageid822);
855 s_free(m->mimeversion);
865 /* mark messages (identified by path) for deletion */
867 delmessages(int ac, char **av)
874 for(mb = mbl; mb != nil; mb = mb->next)
875 if(strcmp(av[0], mb->name) == 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){
891 logmsg("deleting", m);
902 * the following are called with the mailbox qlocked
905 msgincref(Message *m)
910 msgdecref(Mailbox *mb, Message *m)
913 if(m->refs == 0 && m->deleted)
918 * the following are called with mbllock'd
921 mboxincref(Mailbox *mb)
923 assert(mb->refs > 0);
927 mboxdecref(Mailbox *mb)
929 assert(mb->refs > 0);
933 delmessage(mb, mb->root);
935 hfree(PATH(mb->id, Qmbox), "ctl");
944 cistrncmp(char *a, char *b, int n)
947 if(tolower(*a++) != tolower(*b++))
954 cistrcmp(char *a, char *b)
957 if(tolower(*a) != tolower(*b++))
976 while(*p && *p != ';')
978 while(*p == ';' || isspace(*p))
984 getstring(char *p, String *s, int dolower)
990 for(;*p && *p != '"'; p++)
992 s_putc(s, tolower(*p));
1002 for(; *p && !isspace(*p) && *p != ';'; p++)
1004 s_putc(s, tolower(*p));
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 == ';')
1023 /* undecode message body */
1033 switch(m->encoding){
1035 len = m->bend - m->body;
1036 i = (len*3)/4+1; /* room for max chars + null */
1038 len = dec64((uchar*)x, i, m->body, len);
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);
1061 /* convert latin1 to utf */
1068 /* don't convert if we're not a leaf, not text, or already converted */
1073 if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
1076 if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
1077 cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
1080 len = 2*len + m->bend - m->body + 1;
1082 len = latin1toutf(x, m->body, m->bend);
1089 } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
1090 len = xtoutf("8859-2", &x, m->body, m->bend);
1098 } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
1099 len = xtoutf("8859-15", &x, m->body, m->bend);
1107 } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
1108 len = xtoutf("big5", &x, m->body, m->bend);
1116 } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
1117 len = xtoutf("jis", &x, m->body, m->bend);
1125 } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
1126 || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
1129 len = 2*len + m->bend - m->body + 1;
1131 len = windows1257toutf(x, m->body, m->bend);
1138 } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
1139 len = xtoutf("cp1251", &x, m->body, m->bend);
1147 } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
1148 len = xtoutf("koi8", &x, m->body, m->bend);
1173 memset(tableqp, 0, 256);
1174 for(c = ' '; c <= '<'; c++)
1176 for(c = '>'; c <= '~'; c++)
1178 tableqp['\t'] = Self;
1185 if(x >= '0' && x <= '9')
1187 if(x >= 'A' && x <= 'F')
1188 return (x - 'A') + 10;
1189 if(x >= 'a' && x <= 'f')
1190 return (x - 'a') + 10;
1195 decquotedline(char *out, char *in, char *e)
1199 /* dump trailing white space */
1200 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1203 /* trailing '=' means no newline */
1217 c = hex2int(*in++)<<4;
1218 c |= hex2int(*in++);
1231 decquoted(char *out, char *in, char *e)
1235 if(tableqp[' '] == 0)
1239 while((nl = strchr(in, '\n')) != nil && nl < e){
1240 p = decquotedline(p, in, nl);
1244 p = decquotedline(p, in, e-1);
1246 /* make sure we end with a new line */
1262 for(op = p; c = *p; p++)
1270 * return number of 8 bit characters
1278 for(p = m->body; p < m->bend; p++)
1284 /* translate latin1 directly since it fits neatly in utf */
1286 latin1toutf(char *out, char *in, char *e)
1292 for(; in < e; in++){
1294 p += runetochar(p, &r);
1300 /* translate any thing else using the tcs program */
1302 xtoutf(char *charset, char **out, char *in, char *e)
1312 *out = p = malloc(len+1);
1322 if(pipe(fromtcs) < 0){
1323 close(totcs[0]); close(totcs[1]);
1326 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1328 close(fromtcs[0]); close(fromtcs[1]);
1329 close(totcs[0]); close(totcs[1]);
1332 close(fromtcs[0]); close(totcs[1]);
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);
1342 close(fromtcs[1]); close(totcs[0]);
1343 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
1345 close(fromtcs[0]); close(totcs[1]);
1350 n = write(totcs[1], in, e-in);
1361 n = read(fromtcs[0], &p[sofar], len-sofar);
1368 *out = p = realloc(p, len+1);
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'Ÿ'
1395 windows1257toutf(char *out, char *in, char *e)
1401 for(; in < e; in++){
1403 if(r >= 0x7f && r <= 0x9f)
1404 r = winchars[r-0x7f];
1405 p += runetochar(p, &r);
1418 fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
1419 threadexits("out of memory");
1421 setmalloctag(p, getcallerpc(&n));
1426 erealloc(void *p, ulong n)
1432 fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
1433 threadexits("out of memory");
1435 setrealloctag(p, getcallerpc(&p));
1440 mailplumb(Mailbox *mb, Message *m, int delete)
1446 char lenstr[10], *from, *subject, *date;
1449 if(m->subject822 == nil)
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);
1461 if(m->unixdate != nil)
1462 date = s_to_c(m->unixdate);
1466 sprint(lenstr, "%ld", m->end-m->start);
1468 if(biffing && !delete)
1469 print("[ %s / %s / %s ]\n", from, subject, lenstr);
1475 fd = plumbopen("send", OWRITE);
1481 p.wdir = "/mail/fs";
1485 a[ai].name = "filetype";
1486 a[ai].value = "mail";
1488 a[++ai].name = "sender";
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";
1502 a[ai-1].next = &a[ai];
1505 a[++ai].name = "digest";
1506 a[ai].value = s_to_c(m->sdigest);
1507 a[ai-1].next = &a[ai];
1513 snprint(buf, sizeof(buf), "%s/%s/%s",
1514 mntpt, mb->name, m->name);
1515 p.ndata = strlen(buf);
1522 /* count the number of lines in the body (for imap4) */
1525 countlines(Message *m)
1531 for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
1533 sprint(m->lines, "%d", i);
1539 logmsg(char *s, Message *m)
1547 syslog(0, LOG, "%s.%d: %s", user, pid, s);
1549 syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
1551 m->from822 ? s_to_c(m->from822) : "?",
1552 s_to_c(m->sdigest));
1556 * squeeze nulls out of the body
1559 nullsqueeze(Message *m)
1563 q = memchr(m->body, 0, m->end-m->body);
1567 for(p = m->body; q < m->end; q++){
1572 m->bend = m->rbend = m->end = p;
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. */
1582 /* people don't follow RFC822 particularly closely, */
1583 /* so we use strtotm, which is a bunch of heuristics. */
1586 extern int strtotm(char*, Tm*);
1588 date822tounix(char *s)
1593 if(strtotm(s, &tm) < 0)
1597 if(q = strchr(p, '\n'))