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 static void 582 to822(Message *m, Header *h, char *p) 583 { 584 p += strlen(h->type); 585 s_free(m->to822); 586 m->to822 = addr822(p); 587 } 588 589 static void 590 cc822(Message *m, Header *h, char *p) 591 { 592 p += strlen(h->type); 593 s_free(m->cc822); 594 m->cc822 = addr822(p); 595 } 596 597 static void 598 bcc822(Message *m, Header *h, char *p) 599 { 600 p += strlen(h->type); 601 s_free(m->bcc822); 602 m->bcc822 = addr822(p); 603 } 604 605 static void 606 from822(Message *m, Header *h, char *p) 607 { 608 p += strlen(h->type); 609 s_free(m->from822); 610 m->from822 = addr822(p); 611 } 612 613 static void 614 sender822(Message *m, Header *h, char *p) 615 { 616 p += strlen(h->type); 617 s_free(m->sender822); 618 m->sender822 = addr822(p); 619 } 620 621 static void 622 replyto822(Message *m, Header *h, char *p) 623 { 624 p += strlen(h->type); 625 s_free(m->replyto822); 626 m->replyto822 = addr822(p); 627 } 628 629 static void 630 mimeversion(Message *m, Header *h, char *p) 631 { 632 p += strlen(h->type); 633 s_free(m->mimeversion); 634 m->mimeversion = addr822(p); 635 } 636 637 static void 638 killtrailingwhite(char *p) 639 { 640 char *e; 641 642 e = p + strlen(p) - 1; 643 while(e > p && isspace(*e)) 644 *e-- = 0; 645 } 646 647 static void 648 date822(Message *m, Header *h, char *p) 649 { 650 p += strlen(h->type); 651 p = skipwhite(p); 652 s_free(m->date822); 653 m->date822 = s_copy(p); 654 p = s_to_c(m->date822); 655 killtrailingwhite(p); 656 } 657 658 static void 659 subject822(Message *m, Header *h, char *p) 660 { 661 p += strlen(h->type); 662 p = skipwhite(p); 663 s_free(m->subject822); 664 m->subject822 = s_copy(p); 665 p = s_to_c(m->subject822); 666 killtrailingwhite(p); 667 } 668 669 static void 670 inreplyto822(Message *m, Header *h, char *p) 671 { 672 p += strlen(h->type); 673 p = skipwhite(p); 674 s_free(m->inreplyto822); 675 m->inreplyto822 = s_copy(p); 676 p = s_to_c(m->inreplyto822); 677 killtrailingwhite(p); 678 } 679 680 static void 681 messageid822(Message *m, Header *h, char *p) 682 { 683 p += strlen(h->type); 684 p = skipwhite(p); 685 s_free(m->messageid822); 686 m->messageid822 = s_copy(p); 687 p = s_to_c(m->messageid822); 688 killtrailingwhite(p); 689 } 690 691 static int 692 isattribute(char **pp, char *attr) 693 { 694 char *p; 695 int n; 696 697 n = strlen(attr); 698 p = *pp; 699 if(cistrncmp(p, attr, n) != 0) 700 return 0; 701 p += n; 702 while(*p == ' ') 703 p++; 704 if(*p++ != '=') 705 return 0; 706 while(*p == ' ') 707 p++; 708 *pp = p; 709 return 1; 710 } 711 712 static void 713 ctype(Message *m, Header *h, char *p) 714 { 715 String *s; 716 717 p += h->len; 718 p = skipwhite(p); 719 720 p = getstring(p, m->type, 1); 721 722 while(*p){ 723 if(isattribute(&p, "boundary")){ 724 s = s_new(); 725 p = getstring(p, s, 0); 726 m->boundary = s_reset(m->boundary); 727 s_append(m->boundary, "--"); 728 s_append(m->boundary, s_to_c(s)); 729 s_free(s); 730 } else if(cistrncmp(p, "multipart", 9) == 0){ 731 /* 732 * the first unbounded part of a multipart message, 733 * the preamble, is not displayed or saved 734 */ 735 } else if(isattribute(&p, "name")){ 736 if(m->filename == nil) 737 setfilename(m, p); 738 } else if(isattribute(&p, "charset")){ 739 p = getstring(p, s_reset(m->charset), 0); 740 } 741 742 p = skiptosemi(p); 743 } 744 } 745 746 static void 747 cencoding(Message *m, Header *h, char *p) 748 { 749 p += h->len; 750 p = skipwhite(p); 751 if(cistrncmp(p, "base64", 6) == 0) 752 m->encoding = Ebase64; 753 else if(cistrncmp(p, "quoted-printable", 16) == 0) 754 m->encoding = Equoted; 755 } 756 757 static void 758 cdisposition(Message *m, Header *h, char *p) 759 { 760 p += h->len; 761 p = skipwhite(p); 762 while(*p){ 763 if(cistrncmp(p, "inline", 6) == 0){ 764 m->disposition = Dinline; 765 } else if(cistrncmp(p, "attachment", 10) == 0){ 766 m->disposition = Dfile; 767 } else if(cistrncmp(p, "filename=", 9) == 0){ 768 p += 9; 769 setfilename(m, p); 770 } 771 p = skiptosemi(p); 772 } 773 774 } 775 776 ulong msgallocd, msgfreed; 777 778 Message* 779 newmessage(Message *parent) 780 { 781 static int id; 782 Message *m; 783 784 msgallocd++; 785 786 m = emalloc(sizeof(*m)); 787 memset(m, 0, sizeof(*m)); 788 m->disposition = Dnone; 789 m->type = s_copy("text/plain"); 790 m->charset = s_copy("iso-8859-1"); 791 m->id = newid(); 792 if(parent) 793 sprint(m->name, "%d", ++(parent->subname)); 794 if(parent == nil) 795 parent = m; 796 m->whole = parent; 797 m->hlen = -1; 798 return m; 799 } 800 801 // delete a message from a mailbox 802 void 803 delmessage(Mailbox *mb, Message *m) 804 { 805 Message **l; 806 int i; 807 808 mb->vers++; 809 msgfreed++; 810 811 if(m->whole != m){ 812 // unchain from parent 813 for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) 814 ; 815 if(*l != nil) 816 *l = m->next; 817 818 // clear out of name lookup hash table 819 if(m->whole->whole == m->whole) 820 hfree(PATH(mb->id, Qmbox), m->name); 821 else 822 hfree(PATH(m->whole->id, Qdir), m->name); 823 for(i = 0; i < Qmax; i++) 824 hfree(PATH(m->id, Qdir), dirtab[i]); 825 } 826 827 /* recurse through sub-parts */ 828 while(m->part) 829 delmessage(mb, m->part); 830 831 /* free memory */ 832 if(m->mallocd) 833 free(m->start); 834 if(m->hallocd) 835 free(m->header); 836 if(m->ballocd) 837 free(m->body); 838 s_free(m->unixfrom); 839 s_free(m->unixdate); 840 s_free(m->unixheader); 841 s_free(m->from822); 842 s_free(m->sender822); 843 s_free(m->to822); 844 s_free(m->bcc822); 845 s_free(m->cc822); 846 s_free(m->replyto822); 847 s_free(m->date822); 848 s_free(m->inreplyto822); 849 s_free(m->subject822); 850 s_free(m->messageid822); 851 s_free(m->addrs); 852 s_free(m->mimeversion); 853 s_free(m->sdigest); 854 s_free(m->boundary); 855 s_free(m->type); 856 s_free(m->charset); 857 s_free(m->filename); 858 859 free(m); 860 } 861 862 // mark messages (identified by path) for deletion 863 void 864 delmessages(int ac, char **av) 865 { 866 Mailbox *mb; 867 Message *m; 868 int i, needwrite; 869 870 qlock(&mbllock); 871 for(mb = mbl; mb != nil; mb = mb->next) 872 if(strcmp(av[0], mb->name) == 0){ 873 qlock(mb); 874 break; 875 } 876 qunlock(&mbllock); 877 if(mb == nil) 878 return; 879 880 needwrite = 0; 881 for(i = 1; i < ac; i++){ 882 for(m = mb->root->part; m != nil; m = m->next) 883 if(strcmp(m->name, av[i]) == 0){ 884 if(!m->deleted){ 885 mailplumb(mb, m, 1); 886 needwrite = 1; 887 m->deleted = 1; 888 logmsg("deleting", m); 889 } 890 break; 891 } 892 } 893 if(needwrite) 894 syncmbox(mb, 1); 895 qunlock(mb); 896 } 897 898 /* 899 * the following are called with the mailbox qlocked 900 */ 901 void 902 msgincref(Message *m) 903 { 904 m->refs++; 905 } 906 void 907 msgdecref(Mailbox *mb, Message *m) 908 { 909 m->refs--; 910 if(m->refs == 0 && m->deleted) 911 syncmbox(mb, 1); 912 } 913 914 /* 915 * the following are called with mbllock'd 916 */ 917 void 918 mboxincref(Mailbox *mb) 919 { 920 assert(mb->refs > 0); 921 mb->refs++; 922 } 923 void 924 mboxdecref(Mailbox *mb) 925 { 926 assert(mb->refs > 0); 927 qlock(mb); 928 mb->refs--; 929 if(mb->refs == 0){ 930 delmessage(mb, mb->root); 931 if(mb->ctl) 932 hfree(PATH(mb->id, Qmbox), "ctl"); 933 if(mb->close) 934 (*mb->close)(mb); 935 free(mb); 936 } else 937 qunlock(mb); 938 } 939 940 int 941 cistrncmp(char *a, char *b, int n) 942 { 943 while(n-- > 0){ 944 if(tolower(*a++) != tolower(*b++)) 945 return -1; 946 } 947 return 0; 948 } 949 950 int 951 cistrcmp(char *a, char *b) 952 { 953 for(;;){ 954 if(tolower(*a) != tolower(*b++)) 955 return -1; 956 if(*a++ == 0) 957 break; 958 } 959 return 0; 960 } 961 962 static char* 963 skipwhite(char *p) 964 { 965 while(isspace(*p)) 966 p++; 967 return p; 968 } 969 970 static char* 971 skiptosemi(char *p) 972 { 973 while(*p && *p != ';') 974 p++; 975 while(*p == ';' || isspace(*p)) 976 p++; 977 return p; 978 } 979 980 static char* 981 getstring(char *p, String *s, int dolower) 982 { 983 s = s_reset(s); 984 p = skipwhite(p); 985 if(*p == '"'){ 986 p++; 987 for(;*p && *p != '"'; p++) 988 if(dolower) 989 s_putc(s, tolower(*p)); 990 else 991 s_putc(s, *p); 992 if(*p == '"') 993 p++; 994 s_terminate(s); 995 996 return p; 997 } 998 999 for(; *p && !isspace(*p) && *p != ';'; p++) 1000 if(dolower) 1001 s_putc(s, tolower(*p)); 1002 else 1003 s_putc(s, *p); 1004 s_terminate(s); 1005 1006 return p; 1007 } 1008 1009 static void 1010 setfilename(Message *m, char *p) 1011 { 1012 m->filename = s_reset(m->filename); 1013 getstring(p, m->filename, 0); 1014 for(p = s_to_c(m->filename); *p; p++) 1015 if(*p == ' ' || *p == '\t' || *p == ';') 1016 *p = '_'; 1017 } 1018 1019 // 1020 // undecode message body 1021 // 1022 void 1023 decode(Message *m) 1024 { 1025 int i, len; 1026 char *x; 1027 1028 if(m->decoded) 1029 return; 1030 switch(m->encoding){ 1031 case Ebase64: 1032 len = m->bend - m->body; 1033 i = (len*3)/4+1; // room for max chars + null 1034 x = emalloc(i); 1035 len = dec64((uchar*)x, i, m->body, len); 1036 if(m->ballocd) 1037 free(m->body); 1038 m->body = x; 1039 m->bend = x + len; 1040 m->ballocd = 1; 1041 break; 1042 case Equoted: 1043 len = m->bend - m->body; 1044 x = emalloc(len+2); // room for null and possible extra nl 1045 len = decquoted(x, m->body, m->bend); 1046 if(m->ballocd) 1047 free(m->body); 1048 m->body = x; 1049 m->bend = x + len; 1050 m->ballocd = 1; 1051 break; 1052 default: 1053 break; 1054 } 1055 m->decoded = 1; 1056 } 1057 1058 // convert latin1 to utf 1059 void 1060 convert(Message *m) 1061 { 1062 int len; 1063 char *x; 1064 1065 // don't convert if we're not a leaf, not text, or already converted 1066 if(m->converted) 1067 return; 1068 if(m->part != nil) 1069 return; 1070 if(cistrncmp(s_to_c(m->type), "text", 4) != 0) 1071 return; 1072 1073 if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 || 1074 cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){ 1075 len = is8bit(m); 1076 if(len > 0){ 1077 len = 2*len + m->bend - m->body + 1; 1078 x = emalloc(len); 1079 len = latin1toutf(x, m->body, m->bend); 1080 if(m->ballocd) 1081 free(m->body); 1082 m->body = x; 1083 m->bend = x + len; 1084 m->ballocd = 1; 1085 } 1086 } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){ 1087 len = xtoutf("8859-2", &x, m->body, m->bend); 1088 if(len != 0){ 1089 if(m->ballocd) 1090 free(m->body); 1091 m->body = x; 1092 m->bend = x + len; 1093 m->ballocd = 1; 1094 } 1095 } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){ 1096 len = xtoutf("8859-15", &x, m->body, m->bend); 1097 if(len != 0){ 1098 if(m->ballocd) 1099 free(m->body); 1100 m->body = x; 1101 m->bend = x + len; 1102 m->ballocd = 1; 1103 } 1104 } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){ 1105 len = xtoutf("big5", &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 } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){ 1114 len = xtoutf("jis", &x, m->body, m->bend); 1115 if(len != 0){ 1116 if(m->ballocd) 1117 free(m->body); 1118 m->body = x; 1119 m->bend = x + len; 1120 m->ballocd = 1; 1121 } 1122 } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0 1123 || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){ 1124 len = is8bit(m); 1125 if(len > 0){ 1126 len = 2*len + m->bend - m->body + 1; 1127 x = emalloc(len); 1128 len = windows1257toutf(x, m->body, m->bend); 1129 if(m->ballocd) 1130 free(m->body); 1131 m->body = x; 1132 m->bend = x + len; 1133 m->ballocd = 1; 1134 } 1135 } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){ 1136 len = xtoutf("cp1251", &x, m->body, m->bend); 1137 if(len != 0){ 1138 if(m->ballocd) 1139 free(m->body); 1140 m->body = x; 1141 m->bend = x + len; 1142 m->ballocd = 1; 1143 } 1144 } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){ 1145 len = xtoutf("koi8", &x, m->body, m->bend); 1146 if(len != 0){ 1147 if(m->ballocd) 1148 free(m->body); 1149 m->body = x; 1150 m->bend = x + len; 1151 m->ballocd = 1; 1152 } 1153 } 1154 1155 m->converted = 1; 1156 } 1157 1158 enum 1159 { 1160 Self= 1, 1161 Hex= 2, 1162 }; 1163 uchar tableqp[256]; 1164 1165 static void 1166 initquoted(void) 1167 { 1168 int c; 1169 1170 memset(tableqp, 0, 256); 1171 for(c = ' '; c <= '<'; c++) 1172 tableqp[c] = Self; 1173 for(c = '>'; c <= '~'; c++) 1174 tableqp[c] = Self; 1175 tableqp['\t'] = Self; 1176 tableqp['='] = Hex; 1177 } 1178 1179 static int 1180 hex2int(int x) 1181 { 1182 if(x >= '0' && x <= '9') 1183 return x - '0'; 1184 if(x >= 'A' && x <= 'F') 1185 return (x - 'A') + 10; 1186 if(x >= 'a' && x <= 'f') 1187 return (x - 'a') + 10; 1188 return 0; 1189 } 1190 1191 static char* 1192 decquotedline(char *out, char *in, char *e) 1193 { 1194 int c, soft; 1195 1196 /* dump trailing white space */ 1197 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) 1198 e--; 1199 1200 /* trailing '=' means no newline */ 1201 if(*e == '='){ 1202 soft = 1; 1203 e--; 1204 } else 1205 soft = 0; 1206 1207 while(in <= e){ 1208 c = (*in++) & 0xff; 1209 switch(tableqp[c]){ 1210 case Self: 1211 *out++ = c; 1212 break; 1213 case Hex: 1214 c = hex2int(*in++)<<4; 1215 c |= hex2int(*in++); 1216 *out++ = c; 1217 break; 1218 } 1219 } 1220 if(!soft) 1221 *out++ = '\n'; 1222 *out = 0; 1223 1224 return out; 1225 } 1226 1227 int 1228 decquoted(char *out, char *in, char *e) 1229 { 1230 char *p, *nl; 1231 1232 if(tableqp[' '] == 0) 1233 initquoted(); 1234 1235 p = out; 1236 while((nl = strchr(in, '\n')) != nil && nl < e){ 1237 p = decquotedline(p, in, nl); 1238 in = nl + 1; 1239 } 1240 if(in < e) 1241 p = decquotedline(p, in, e-1); 1242 1243 // make sure we end with a new line 1244 if(*(p-1) != '\n'){ 1245 *p++ = '\n'; 1246 *p = 0; 1247 } 1248 1249 return p - out; 1250 } 1251 1252 static char* 1253 lowercase(char *p) 1254 { 1255 char *op; 1256 int c; 1257 1258 for(op = p; c = *p; p++) 1259 if(isupper(c)) 1260 *p = tolower(c); 1261 return op; 1262 } 1263 1264 /* 1265 * return number of 8 bit characters 1266 */ 1267 static int 1268 is8bit(Message *m) 1269 { 1270 int count = 0; 1271 char *p; 1272 1273 for(p = m->body; p < m->bend; p++) 1274 if(*p & 0x80) 1275 count++; 1276 return count; 1277 } 1278 1279 // translate latin1 directly since it fits neatly in utf 1280 int 1281 latin1toutf(char *out, char *in, char *e) 1282 { 1283 Rune r; 1284 char *p; 1285 1286 p = out; 1287 for(; in < e; in++){ 1288 r = (*in) & 0xff; 1289 p += runetochar(p, &r); 1290 } 1291 *p = 0; 1292 return p - out; 1293 } 1294 1295 // translate any thing else using the tcs program 1296 int 1297 xtoutf(char *charset, char **out, char *in, char *e) 1298 { 1299 char *av[4]; 1300 int totcs[2]; 1301 int fromtcs[2]; 1302 int n, len, sofar; 1303 char *p; 1304 1305 len = e-in+1; 1306 sofar = 0; 1307 *out = p = malloc(len+1); 1308 if(p == nil) 1309 return 0; 1310 1311 av[0] = charset; 1312 av[1] = "-f"; 1313 av[2] = charset; 1314 av[3] = 0; 1315 if(pipe(totcs) < 0) 1316 return 0; 1317 if(pipe(fromtcs) < 0){ 1318 close(totcs[0]); close(totcs[1]); 1319 return 0; 1320 } 1321 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ 1322 case -1: 1323 close(fromtcs[0]); close(fromtcs[1]); 1324 close(totcs[0]); close(totcs[1]); 1325 return 0; 1326 case 0: 1327 close(fromtcs[0]); close(totcs[1]); 1328 dup(fromtcs[1], 1); 1329 dup(totcs[0], 0); 1330 close(fromtcs[1]); close(totcs[0]); 1331 dup(open("/dev/null", OWRITE), 2); 1332 exec("/bin/tcs", av); 1333 _exits(0); 1334 default: 1335 close(fromtcs[1]); close(totcs[0]); 1336 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ 1337 case -1: 1338 close(fromtcs[0]); close(totcs[1]); 1339 return 0; 1340 case 0: 1341 close(fromtcs[0]); 1342 while(in < e){ 1343 n = write(totcs[1], in, e-in); 1344 if(n <= 0) 1345 break; 1346 in += n; 1347 } 1348 close(totcs[1]); 1349 _exits(0); 1350 default: 1351 close(totcs[1]); 1352 for(;;){ 1353 n = read(fromtcs[0], &p[sofar], len-sofar); 1354 if(n <= 0) 1355 break; 1356 sofar += n; 1357 p[sofar] = 0; 1358 if(sofar == len){ 1359 len += 1024; 1360 *out = p = realloc(p, len+1); 1361 if(p == nil) 1362 return 0; 1363 } 1364 } 1365 close(fromtcs[0]); 1366 break; 1367 } 1368 break; 1369 } 1370 return sofar; 1371 } 1372 1373 enum { 1374 Winstart= 0x7f, 1375 Winend= 0x9f, 1376 }; 1377 1378 Rune winchars[] = { 1379 L'•', 1380 L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡', 1381 L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•', 1382 L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—', 1383 L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ', 1384 }; 1385 1386 int 1387 windows1257toutf(char *out, char *in, char *e) 1388 { 1389 Rune r; 1390 char *p; 1391 1392 p = out; 1393 for(; in < e; in++){ 1394 r = (*in) & 0xff; 1395 if(r >= 0x7f && r <= 0x9f) 1396 r = winchars[r-0x7f]; 1397 p += runetochar(p, &r); 1398 } 1399 *p = 0; 1400 return p - out; 1401 } 1402 1403 void * 1404 emalloc(ulong n) 1405 { 1406 void *p; 1407 1408 p = mallocz(n, 1); 1409 if(!p){ 1410 fprint(2, "%s: out of memory alloc %lud\n", argv0, n); 1411 exits("out of memory"); 1412 } 1413 setmalloctag(p, getcallerpc(&n)); 1414 return p; 1415 } 1416 1417 void * 1418 erealloc(void *p, ulong n) 1419 { 1420 if(n == 0) 1421 n = 1; 1422 p = realloc(p, n); 1423 if(!p){ 1424 fprint(2, "%s: out of memory realloc %lud\n", argv0, n); 1425 exits("out of memory"); 1426 } 1427 setrealloctag(p, getcallerpc(&p)); 1428 return p; 1429 } 1430 1431 void 1432 mailplumb(Mailbox *mb, Message *m, int delete) 1433 { 1434 Plumbmsg p; 1435 Plumbattr a[7]; 1436 char buf[256]; 1437 int ai; 1438 char lenstr[10], *from, *subject, *date; 1439 static int fd = -1; 1440 1441 if(m->subject822 == nil) 1442 subject = ""; 1443 else 1444 subject = s_to_c(m->subject822); 1445 1446 if(m->from822 != nil) 1447 from = s_to_c(m->from822); 1448 else if(m->unixfrom != nil) 1449 from = s_to_c(m->unixfrom); 1450 else 1451 from = ""; 1452 1453 if(m->unixdate != nil) 1454 date = s_to_c(m->unixdate); 1455 else 1456 date = ""; 1457 1458 sprint(lenstr, "%ld", m->end-m->start); 1459 1460 if(biffing && !delete) 1461 print("[ %s / %s / %s ]\n", from, subject, lenstr); 1462 1463 if(!plumbing) 1464 return; 1465 1466 if(fd < 0) 1467 fd = plumbopen("send", OWRITE); 1468 if(fd < 0) 1469 return; 1470 1471 p.src = "mailfs"; 1472 p.dst = "seemail"; 1473 p.wdir = "/mail/fs"; 1474 p.type = "text"; 1475 1476 ai = 0; 1477 a[ai].name = "filetype"; 1478 a[ai].value = "mail"; 1479 1480 a[++ai].name = "sender"; 1481 a[ai].value = from; 1482 a[ai-1].next = &a[ai]; 1483 1484 a[++ai].name = "length"; 1485 a[ai].value = lenstr; 1486 a[ai-1].next = &a[ai]; 1487 1488 a[++ai].name = "mailtype"; 1489 a[ai].value = delete?"delete":"new"; 1490 a[ai-1].next = &a[ai]; 1491 1492 a[++ai].name = "date"; 1493 a[ai].value = date; 1494 a[ai-1].next = &a[ai]; 1495 1496 if(m->sdigest){ 1497 a[++ai].name = "digest"; 1498 a[ai].value = s_to_c(m->sdigest); 1499 a[ai-1].next = &a[ai]; 1500 } 1501 1502 a[ai].next = nil; 1503 1504 p.attr = a; 1505 snprint(buf, sizeof(buf), "%s/%s/%s", 1506 mntpt, mb->name, m->name); 1507 p.ndata = strlen(buf); 1508 p.data = buf; 1509 1510 plumbsend(fd, &p); 1511 } 1512 1513 // 1514 // count the number of lines in the body (for imap4) 1515 // 1516 void 1517 countlines(Message *m) 1518 { 1519 int i; 1520 char *p; 1521 1522 i = 0; 1523 for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) 1524 i++; 1525 sprint(m->lines, "%d", i); 1526 } 1527 1528 char *LOG = "fs"; 1529 1530 void 1531 logmsg(char *s, Message *m) 1532 { 1533 int pid; 1534 1535 if(!logging) 1536 return; 1537 pid = getpid(); 1538 if(m == nil) 1539 syslog(0, LOG, "%s.%d: %s", user, pid, s); 1540 else 1541 syslog(0, LOG, "%s.%d: %s msg from %s digest %s", 1542 user, pid, s, 1543 m->from822 ? s_to_c(m->from822) : "?", 1544 s_to_c(m->sdigest)); 1545 } 1546 1547 /* 1548 * squeeze nulls out of the body 1549 */ 1550 static void 1551 nullsqueeze(Message *m) 1552 { 1553 char *p, *q; 1554 1555 q = memchr(m->body, 0, m->end-m->body); 1556 if(q == nil) 1557 return; 1558 1559 for(p = m->body; q < m->end; q++){ 1560 if(*q == 0) 1561 continue; 1562 *p++ = *q; 1563 } 1564 m->bend = m->rbend = m->end = p; 1565 } 1566 1567 1568 // 1569 // convert an RFC822 date into a Unix style date 1570 // for when the Unix From line isn't there (e.g. POP3). 1571 // enough client programs depend on having a Unix date 1572 // that it's easiest to write this conversion code once, right here. 1573 // 1574 // people don't follow RFC822 particularly closely, 1575 // so we use strtotm, which is a bunch of heuristics. 1576 // 1577 1578 extern int strtotm(char*, Tm*); 1579 String* 1580 date822tounix(char *s) 1581 { 1582 char *p, *q; 1583 Tm tm; 1584 1585 if(strtotm(s, &tm) < 0) 1586 return nil; 1587 1588 p = asctime(&tm); 1589 if(q = strchr(p, '\n')) 1590 *q = '\0'; 1591 return s_copy(p); 1592 } 1593 1594