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