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