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