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