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, 0); 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 len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend); 1074 if(len > 0){ 1075 if(m->ballocd) 1076 free(m->body); 1077 m->body = x; 1078 m->bend = x + len; 1079 m->ballocd = 1; 1080 } 1081 m->converted = 1; 1082 } 1083 1084 static int 1085 hex2int(int x) 1086 { 1087 if(x >= '0' && x <= '9') 1088 return x - '0'; 1089 if(x >= 'A' && x <= 'F') 1090 return (x - 'A') + 10; 1091 if(x >= 'a' && x <= 'f') 1092 return (x - 'a') + 10; 1093 return 0; 1094 } 1095 1096 // underscores are translated in 2047 headers (uscores=1) 1097 // but not in the body (uscores=0) 1098 static char* 1099 decquotedline(char *out, char *in, char *e, int uscores) 1100 { 1101 int c, soft; 1102 1103 /* dump trailing white space */ 1104 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) 1105 e--; 1106 1107 /* trailing '=' means no newline */ 1108 if(*e == '='){ 1109 soft = 1; 1110 e--; 1111 } else 1112 soft = 0; 1113 1114 while(in <= e){ 1115 c = (*in++) & 0xff; 1116 switch(c){ 1117 case '_': 1118 if(uscores){ 1119 *out++ = ' '; 1120 break; 1121 } 1122 default: 1123 *out++ = c; 1124 break; 1125 case '=': 1126 c = hex2int(*in++)<<4; 1127 c |= hex2int(*in++); 1128 *out++ = c; 1129 break; 1130 } 1131 } 1132 if(!soft) 1133 *out++ = '\n'; 1134 *out = 0; 1135 1136 return out; 1137 } 1138 1139 int 1140 decquoted(char *out, char *in, char *e, int uscores) 1141 { 1142 char *p, *nl; 1143 1144 p = out; 1145 while((nl = strchr(in, '\n')) != nil && nl < e){ 1146 p = decquotedline(p, in, nl, uscores); 1147 in = nl + 1; 1148 } 1149 if(in < e) 1150 p = decquotedline(p, in, e-1, uscores); 1151 1152 // make sure we end with a new line 1153 if(*(p-1) != '\n'){ 1154 *p++ = '\n'; 1155 *p = 0; 1156 } 1157 1158 return p - out; 1159 } 1160 1161 static char* 1162 lowercase(char *p) 1163 { 1164 char *op; 1165 int c; 1166 1167 for(op = p; c = *p; p++) 1168 if(isupper(c)) 1169 *p = tolower(c); 1170 return op; 1171 } 1172 1173 // translate latin1 directly since it fits neatly in utf 1174 static int 1175 latin1toutf(char **out, char *in, char *e) 1176 { 1177 int n; 1178 char *p; 1179 Rune r; 1180 1181 n = 0; 1182 for(p = in; p < e; p++) 1183 if(*p & 0x80) 1184 n++; 1185 if(n == 0) 1186 return 0; 1187 1188 n += e-in; 1189 *out = p = malloc(n+1); 1190 if(p == nil) 1191 return 0; 1192 1193 for(; in < e; in++){ 1194 r = (uchar)*in; 1195 p += runetochar(p, &r); 1196 } 1197 *p = 0; 1198 return p - *out; 1199 } 1200 1201 // translate any thing using the tcs program 1202 int 1203 xtoutf(char *charset, char **out, char *in, char *e) 1204 { 1205 char *av[4]; 1206 int totcs[2]; 1207 int fromtcs[2]; 1208 int n, len, sofar; 1209 char *p; 1210 1211 // might not need to convert 1212 if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0) 1213 return 0; 1214 if(cistrcmp(charset, "iso-8859-1") == 0) 1215 return latin1toutf(out, in, e); 1216 1217 len = e-in+1; 1218 sofar = 0; 1219 *out = p = malloc(len+1); 1220 if(p == nil) 1221 return 0; 1222 1223 av[0] = charset; 1224 av[1] = "-f"; 1225 av[2] = charset; 1226 av[3] = 0; 1227 if(pipe(totcs) < 0) 1228 goto error; 1229 if(pipe(fromtcs) < 0){ 1230 close(totcs[0]); close(totcs[1]); 1231 goto error; 1232 } 1233 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ 1234 case -1: 1235 close(fromtcs[0]); close(fromtcs[1]); 1236 close(totcs[0]); close(totcs[1]); 1237 goto error; 1238 case 0: 1239 close(fromtcs[0]); close(totcs[1]); 1240 dup(fromtcs[1], 1); 1241 dup(totcs[0], 0); 1242 close(fromtcs[1]); close(totcs[0]); 1243 dup(open("/dev/null", OWRITE), 2); 1244 exec("/bin/tcs", av); 1245 _exits(0); 1246 default: 1247 close(fromtcs[1]); close(totcs[0]); 1248 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ 1249 case -1: 1250 close(fromtcs[0]); close(totcs[1]); 1251 goto error; 1252 case 0: 1253 close(fromtcs[0]); 1254 while(in < e){ 1255 n = write(totcs[1], in, e-in); 1256 if(n <= 0) 1257 break; 1258 in += n; 1259 } 1260 close(totcs[1]); 1261 _exits(0); 1262 default: 1263 close(totcs[1]); 1264 for(;;){ 1265 n = read(fromtcs[0], &p[sofar], len-sofar); 1266 if(n <= 0) 1267 break; 1268 sofar += n; 1269 p[sofar] = 0; 1270 if(sofar == len){ 1271 len += 1024; 1272 p = realloc(p, len+1); 1273 if(p == nil) 1274 goto error; 1275 *out = p; 1276 } 1277 } 1278 close(fromtcs[0]); 1279 break; 1280 } 1281 break; 1282 } 1283 if(sofar == 0) 1284 goto error; 1285 return sofar; 1286 1287 error: 1288 free(*out); 1289 *out = nil; 1290 return 0; 1291 } 1292 1293 void * 1294 emalloc(ulong n) 1295 { 1296 void *p; 1297 1298 p = mallocz(n, 1); 1299 if(!p){ 1300 fprint(2, "%s: out of memory alloc %lud\n", argv0, n); 1301 exits("out of memory"); 1302 } 1303 setmalloctag(p, getcallerpc(&n)); 1304 return p; 1305 } 1306 1307 void * 1308 erealloc(void *p, ulong n) 1309 { 1310 if(n == 0) 1311 n = 1; 1312 p = realloc(p, n); 1313 if(!p){ 1314 fprint(2, "%s: out of memory realloc %lud\n", argv0, n); 1315 exits("out of memory"); 1316 } 1317 setrealloctag(p, getcallerpc(&p)); 1318 return p; 1319 } 1320 1321 void 1322 mailplumb(Mailbox *mb, Message *m, int delete) 1323 { 1324 Plumbmsg p; 1325 Plumbattr a[7]; 1326 char buf[256]; 1327 int ai; 1328 char lenstr[10], *from, *subject, *date; 1329 static int fd = -1; 1330 1331 if(m->subject822 == nil) 1332 subject = ""; 1333 else 1334 subject = s_to_c(m->subject822); 1335 1336 if(m->from822 != nil) 1337 from = s_to_c(m->from822); 1338 else if(m->unixfrom != nil) 1339 from = s_to_c(m->unixfrom); 1340 else 1341 from = ""; 1342 1343 if(m->unixdate != nil) 1344 date = s_to_c(m->unixdate); 1345 else 1346 date = ""; 1347 1348 sprint(lenstr, "%ld", m->end-m->start); 1349 1350 if(biffing && !delete) 1351 print("[ %s / %s / %s ]\n", from, subject, lenstr); 1352 1353 if(!plumbing) 1354 return; 1355 1356 if(fd < 0) 1357 fd = plumbopen("send", OWRITE); 1358 if(fd < 0) 1359 return; 1360 1361 p.src = "mailfs"; 1362 p.dst = "seemail"; 1363 p.wdir = "/mail/fs"; 1364 p.type = "text"; 1365 1366 ai = 0; 1367 a[ai].name = "filetype"; 1368 a[ai].value = "mail"; 1369 1370 a[++ai].name = "sender"; 1371 a[ai].value = from; 1372 a[ai-1].next = &a[ai]; 1373 1374 a[++ai].name = "length"; 1375 a[ai].value = lenstr; 1376 a[ai-1].next = &a[ai]; 1377 1378 a[++ai].name = "mailtype"; 1379 a[ai].value = delete?"delete":"new"; 1380 a[ai-1].next = &a[ai]; 1381 1382 a[++ai].name = "date"; 1383 a[ai].value = date; 1384 a[ai-1].next = &a[ai]; 1385 1386 if(m->sdigest){ 1387 a[++ai].name = "digest"; 1388 a[ai].value = s_to_c(m->sdigest); 1389 a[ai-1].next = &a[ai]; 1390 } 1391 1392 a[ai].next = nil; 1393 1394 p.attr = a; 1395 snprint(buf, sizeof(buf), "%s/%s/%s", 1396 mntpt, mb->name, m->name); 1397 p.ndata = strlen(buf); 1398 p.data = buf; 1399 1400 plumbsend(fd, &p); 1401 } 1402 1403 // 1404 // count the number of lines in the body (for imap4) 1405 // 1406 void 1407 countlines(Message *m) 1408 { 1409 int i; 1410 char *p; 1411 1412 i = 0; 1413 for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) 1414 i++; 1415 sprint(m->lines, "%d", i); 1416 } 1417 1418 char *LOG = "fs"; 1419 1420 void 1421 logmsg(char *s, Message *m) 1422 { 1423 int pid; 1424 1425 if(!logging) 1426 return; 1427 pid = getpid(); 1428 if(m == nil) 1429 syslog(0, LOG, "%s.%d: %s", user, pid, s); 1430 else 1431 syslog(0, LOG, "%s.%d: %s msg from %s digest %s", 1432 user, pid, s, 1433 m->from822 ? s_to_c(m->from822) : "?", 1434 s_to_c(m->sdigest)); 1435 } 1436 1437 /* 1438 * squeeze nulls out of the body 1439 */ 1440 static void 1441 nullsqueeze(Message *m) 1442 { 1443 char *p, *q; 1444 1445 q = memchr(m->body, 0, m->end-m->body); 1446 if(q == nil) 1447 return; 1448 1449 for(p = m->body; q < m->end; q++){ 1450 if(*q == 0) 1451 continue; 1452 *p++ = *q; 1453 } 1454 m->bend = m->rbend = m->end = p; 1455 } 1456 1457 1458 // 1459 // convert an RFC822 date into a Unix style date 1460 // for when the Unix From line isn't there (e.g. POP3). 1461 // enough client programs depend on having a Unix date 1462 // that it's easiest to write this conversion code once, right here. 1463 // 1464 // people don't follow RFC822 particularly closely, 1465 // so we use strtotm, which is a bunch of heuristics. 1466 // 1467 1468 extern int strtotm(char*, Tm*); 1469 String* 1470 date822tounix(char *s) 1471 { 1472 char *p, *q; 1473 Tm tm; 1474 1475 if(strtotm(s, &tm) < 0) 1476 return nil; 1477 1478 p = asctime(&tm); 1479 if(q = strchr(p, '\n')) 1480 *q = '\0'; 1481 return s_copy(p); 1482 } 1483 1484