1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <libsec.h> 5 #include <auth.h> 6 #include <fcall.h> 7 #include "imap4d.h" 8 9 static void body64(int in, int out); 10 static void bodystrip(int in, int out); 11 static void cleanupHeader(Header *h); 12 static char *domBang(char *s); 13 static void freeMAddr(MAddr *a); 14 static void freeMimeHdr(MimeHdr *mh); 15 static char *headAddrSpec(char *e, char *w); 16 static MAddr *headAddresses(void); 17 static MAddr *headAddress(void); 18 static char *headAtom(char *disallowed); 19 static int headChar(int eat); 20 static char *headDomain(char *e); 21 static MAddr *headMAddr(MAddr *old); 22 static char *headPhrase(char *e, char *w); 23 static char *headQuoted(int start, int stop); 24 static char *headSkipWhite(int); 25 static void headSkip(void); 26 static char *headSubDomain(void); 27 static char *headText(void); 28 static void headToEnd(void); 29 static char *headWord(void); 30 static void mimeDescription(Header *h); 31 static void mimeDisposition(Header *h); 32 static void mimeEncoding(Header *h); 33 static void mimeId(Header *h); 34 static void mimeLanguage(Header *h); 35 static void mimeMd5(Header *h); 36 static MimeHdr *mimeParams(void); 37 static void mimeType(Header *h); 38 static MimeHdr *mkMimeHdr(char *s, char *t, MimeHdr *next); 39 static void msgAddDate(Msg *m); 40 static void msgAddHead(Msg *m, char *head, char *body); 41 static int msgBodySize(Msg *m); 42 static int msgHeader(Msg *m, Header *h, char *file); 43 static long msgReadFile(Msg *m, char *file, char **ss); 44 static int msgUnix(Msg *m, int top); 45 static void stripQuotes(char *q); 46 static MAddr *unixFrom(char *s); 47 48 49 static char bogusBody[] = 50 "This message contains null characters, so it cannot be displayed correctly.\r\n" 51 "Most likely you were sent a bogus message or a binary file.\r\n" 52 "\r\n" 53 "Each of the following attachments has a different version of the message.\r\n" 54 "The first is inlined with all non-printable characters stripped.\r\n" 55 "The second contains the message as it was stored in your mailbox.\r\n" 56 "The third has the initial header stripped.\r\n"; 57 58 static char bogusMimeText[] = 59 "Content-Disposition: inline\r\n" 60 "Content-Type: text/plain; charset=\"US-ASCII\"\r\n" 61 "Content-Transfer-Encoding: 7bit\r\n"; 62 63 static char bogusMimeBinary[] = 64 "Content-Disposition: attachment\r\n" 65 "Content-Type: application/octet-stream\r\n" 66 "Content-Transfer-Encoding: base64\r\n"; 67 68 /* 69 * stop list for header fields 70 */ 71 static char *headFieldStop = ":"; 72 static char *mimeTokenStop = "()<>@,;:\\\"/[]?="; 73 static char *headAtomStop = "()<>@,;:\\\".[]"; 74 static uchar *headStr; 75 static uchar *lastWhite; 76 77 long 78 selectFields(char *dst, long n, char *hdr, SList *fields, int matches) 79 { 80 SList *f; 81 uchar *start; 82 char *s; 83 long m, nf; 84 85 headStr = (uchar*)hdr; 86 m = 0; 87 for(;;){ 88 start = headStr; 89 s = headAtom(headFieldStop); 90 if(s == nil) 91 break; 92 headSkip(); 93 for(f = fields; f != nil; f = f->next){ 94 if(cistrcmp(s, f->s) == !matches){ 95 nf = headStr - start; 96 if(m + nf > n) 97 return 0; 98 memmove(&dst[m], start, nf); 99 m += nf; 100 } 101 } 102 free(s); 103 } 104 if(m + 3 > n) 105 return 0; 106 dst[m++] = '\r'; 107 dst[m++] = '\n'; 108 dst[m] = '\0'; 109 return m; 110 } 111 112 void 113 freeMsg(Msg *m) 114 { 115 Msg *k, *last; 116 117 free(m->iBuf); 118 freeMAddr(m->to); 119 if(m->replyTo != m->from) 120 freeMAddr(m->replyTo); 121 if(m->sender != m->from) 122 freeMAddr(m->sender); 123 if(m->from != m->unixFrom) 124 freeMAddr(m->from); 125 freeMAddr(m->unixFrom); 126 freeMAddr(m->cc); 127 freeMAddr(m->bcc); 128 free(m->unixDate); 129 cleanupHeader(&m->head); 130 cleanupHeader(&m->mime); 131 for(k = m->kids; k != nil; ){ 132 last = k; 133 k = k->next; 134 freeMsg(last); 135 } 136 free(m); 137 } 138 139 ulong 140 msgSize(Msg *m) 141 { 142 return m->head.size + m->size; 143 } 144 145 int 146 infoIsNil(char *s) 147 { 148 return s == nil || s[0] == '\0'; 149 } 150 151 char* 152 maddrStr(MAddr *a) 153 { 154 char *host, *addr; 155 int n; 156 157 host = a->host; 158 if(host == nil) 159 host = ""; 160 n = strlen(a->box) + strlen(host) + 2; 161 if(a->personal != nil) 162 n += strlen(a->personal) + 3; 163 addr = emalloc(n); 164 if(a->personal != nil) 165 snprint(addr, n, "%s <%s@%s>", a->personal, a->box, host); 166 else 167 snprint(addr, n, "%s@%s", a->box, host); 168 return addr; 169 } 170 171 /* 172 * return actual name of f in m's fs directory 173 * this is special cased when opening m/rawbody, m/mimeheader, or m/rawheader, 174 * if the message was corrupted. in that case, 175 * a temporary file is made to hold the base64 encoding of m/raw. 176 */ 177 int 178 msgFile(Msg *m, char *f) 179 { 180 Msg *parent, *p; 181 Dir d; 182 Tm tm; 183 char buf[64], nbuf[2]; 184 uchar dbuf[64]; 185 int i, n, fd, fd1, fd2; 186 187 if(!m->bogus 188 || strcmp(f, "") != 0 && strcmp(f, "rawbody") != 0 189 && strcmp(f, "rawheader") != 0 && strcmp(f, "mimeheader") != 0 190 && strcmp(f, "info") != 0 && strcmp(f, "unixheader") != 0){ 191 if(strlen(f) > MsgNameLen) 192 bye("internal error: msgFile name too long"); 193 strcpy(m->efs, f); 194 return cdOpen(m->fsDir, m->fs, OREAD); 195 } 196 197 /* 198 * walk up the stupid runt message parts for non-multipart messages 199 */ 200 parent = m->parent; 201 if(parent != nil && parent->parent != nil){ 202 m = parent; 203 parent = m->parent; 204 } 205 p = m; 206 if(parent != nil) 207 p = parent; 208 209 if(strcmp(f, "info") == 0 || strcmp(f, "unixheader") == 0){ 210 strcpy(p->efs, f); 211 return cdOpen(p->fsDir, p->fs, OREAD); 212 } 213 214 fd = imapTmp(); 215 if(fd < 0) 216 return -1; 217 218 /* 219 * craft the message parts for bogus messages 220 */ 221 if(strcmp(f, "") == 0){ 222 /* 223 * make a fake directory for each kid 224 * all we care about is the name 225 */ 226 if(parent == nil){ 227 nulldir(&d); 228 d.mode = DMDIR|0600; 229 d.qid.type = QTDIR; 230 d.name = nbuf; 231 nbuf[1] = '\0'; 232 for(i = '1'; i <= '4'; i++){ 233 nbuf[0] = i; 234 n = convD2M(&d, dbuf, sizeof(dbuf)); 235 if(n <= BIT16SZ) 236 fprint(2, "bad convD2M %d\n", n); 237 write(fd, dbuf, n); 238 } 239 } 240 }else if(strcmp(f, "mimeheader") == 0){ 241 if(parent != nil){ 242 switch(m->id){ 243 case 1: 244 case 2: 245 fprint(fd, "%s", bogusMimeText); 246 break; 247 case 3: 248 case 4: 249 fprint(fd, "%s", bogusMimeBinary); 250 break; 251 } 252 } 253 }else if(strcmp(f, "rawheader") == 0){ 254 if(parent == nil){ 255 date2tm(&tm, m->unixDate); 256 rfc822date(buf, sizeof(buf), &tm); 257 fprint(fd, 258 "Date: %s\r\n" 259 "From: imap4 daemon <%s@%s>\r\n" 260 "To: <%s@%s>\r\n" 261 "Subject: This message was illegal or corrupted\r\n" 262 "MIME-Version: 1.0\r\n" 263 "Content-Type: multipart/mixed;\r\n\tboundary=\"upas-%s\"\r\n", 264 buf, username, site, username, site, m->info[IDigest]); 265 } 266 }else if(strcmp(f, "rawbody") == 0){ 267 fd1 = msgFile(p, "raw"); 268 strcpy(p->efs, "rawbody"); 269 fd2 = cdOpen(p->fsDir, p->fs, OREAD); 270 if(fd1 < 0 || fd2 < 0){ 271 close(fd); 272 close(fd1); 273 close(fd2); 274 return -1; 275 } 276 if(parent == nil){ 277 fprint(fd, 278 "This is a multi-part message in MIME format.\r\n" 279 "--upas-%s\r\n" 280 "%s" 281 "\r\n" 282 "%s" 283 "\r\n", 284 m->info[IDigest], bogusMimeText, bogusBody); 285 286 fprint(fd, 287 "--upas-%s\r\n" 288 "%s" 289 "\r\n", 290 m->info[IDigest], bogusMimeText); 291 bodystrip(fd1, fd); 292 293 fprint(fd, 294 "--upas-%s\r\n" 295 "%s" 296 "\r\n", 297 m->info[IDigest], bogusMimeBinary); 298 seek(fd1, 0, 0); 299 body64(fd1, fd); 300 301 fprint(fd, 302 "--upas-%s\r\n" 303 "%s" 304 "\r\n", 305 m->info[IDigest], bogusMimeBinary); 306 body64(fd2, fd); 307 308 fprint(fd, "--upas-%s--\r\n", m->info[IDigest]); 309 }else{ 310 switch(m->id){ 311 case 1: 312 fprint(fd, "%s", bogusBody); 313 break; 314 case 2: 315 bodystrip(fd1, fd); 316 break; 317 case 3: 318 body64(fd1, fd); 319 break; 320 case 4: 321 body64(fd2, fd); 322 break; 323 } 324 } 325 close(fd1); 326 close(fd2); 327 } 328 seek(fd, 0, 0); 329 return fd; 330 } 331 332 int 333 msgIsMulti(Header *h) 334 { 335 return h->type != nil && cistrcmp("multipart", h->type->s) == 0; 336 } 337 338 int 339 msgIsRfc822(Header *h) 340 { 341 return h->type != nil && cistrcmp("message", h->type->s) == 0 && cistrcmp("rfc822", h->type->t) == 0; 342 } 343 344 /* 345 * check if a message has been deleted by someone else 346 */ 347 void 348 msgDead(Msg *m) 349 { 350 if(m->expunged) 351 return; 352 *m->efs = '\0'; 353 if(!cdExists(m->fsDir, m->fs)) 354 m->expunged = 1; 355 } 356 357 /* 358 * make sure the message has valid associated info 359 * used for ISubject, IDigest, IInReplyTo, IMessageId. 360 */ 361 int 362 msgInfo(Msg *m) 363 { 364 char *s; 365 int i; 366 367 if(m->info[0] != nil) 368 return 1; 369 370 i = msgReadFile(m, "info", &m->iBuf); 371 if(i < 0) 372 return 0; 373 374 s = m->iBuf; 375 for(i = 0; i < IMax; i++){ 376 m->info[i] = s; 377 s = strchr(s, '\n'); 378 if(s == nil) 379 break; 380 *s++ = '\0'; 381 } 382 for(; i < IMax; i++) 383 m->info[i] = nil; 384 385 for(i = 0; i < IMax; i++) 386 if(infoIsNil(m->info[i])) 387 m->info[i] = nil; 388 389 return 1; 390 } 391 392 /* 393 * make sure the message has valid mime structure 394 * and sub-messages 395 */ 396 int 397 msgStruct(Msg *m, int top) 398 { 399 Msg *k, head, *last; 400 Dir *d; 401 char *s; 402 ulong max, id; 403 int i, nd, fd, ns; 404 405 if(m->kids != nil) 406 return 1; 407 408 if(m->expunged 409 || !msgInfo(m) 410 || !msgUnix(m, top) 411 || !msgBodySize(m) 412 || !msgHeader(m, &m->mime, "mimeheader") 413 || (top || msgIsRfc822(&m->mime) || msgIsMulti(&m->mime)) && !msgHeader(m, &m->head, "rawheader")){ 414 if(top && m->bogus && !(m->bogus & BogusTried)){ 415 m->bogus |= BogusTried; 416 return msgStruct(m, top); 417 } 418 msgDead(m); 419 return 0; 420 } 421 422 /* 423 * if a message has no kids, it has a kid which is just the body of the real message 424 */ 425 if(!msgIsMulti(&m->head) && !msgIsMulti(&m->mime) && !msgIsRfc822(&m->head) && !msgIsRfc822(&m->mime)){ 426 k = MKZ(Msg); 427 k->id = 1; 428 k->fsDir = m->fsDir; 429 k->bogus = m->bogus; 430 k->parent = m->parent; 431 ns = m->efs - m->fs; 432 k->fs = emalloc(ns + (MsgNameLen + 1)); 433 memmove(k->fs, m->fs, ns); 434 k->efs = k->fs + ns; 435 *k->efs = '\0'; 436 k->size = m->size; 437 m->kids = k; 438 return 1; 439 } 440 441 /* 442 * read in all child messages messages 443 */ 444 fd = msgFile(m, ""); 445 if(fd < 0){ 446 msgDead(m); 447 return 0; 448 } 449 450 max = 0; 451 head.next = nil; 452 last = &head; 453 while((nd = dirread(fd, &d)) > 0){ 454 for(i = 0; i < nd; i++){ 455 s = d[i].name; 456 id = strtol(s, &s, 10); 457 if(id <= max || *s != '\0' 458 || (d[i].mode & DMDIR) != DMDIR) 459 continue; 460 461 max = id; 462 463 k = MKZ(Msg); 464 k->id = id; 465 k->fsDir = m->fsDir; 466 k->bogus = m->bogus; 467 k->parent = m; 468 ns = strlen(m->fs); 469 k->fs = emalloc(ns + 2 * (MsgNameLen + 1)); 470 k->efs = seprint(k->fs, k->fs + ns + (MsgNameLen + 1), "%s%lud/", m->fs, id); 471 k->prev = last; 472 k->size = ~0UL; 473 k->lines = ~0UL; 474 last->next = k; 475 last = k; 476 } 477 } 478 close(fd); 479 m->kids = head.next; 480 481 /* 482 * if kids fail, just whack them 483 */ 484 top = top && (msgIsRfc822(&m->head) || msgIsMulti(&m->head)); 485 for(k = m->kids; k != nil; k = k->next){ 486 if(!msgStruct(k, top)){ 487 for(k = m->kids; k != nil; ){ 488 last = k; 489 k = k->next; 490 freeMsg(last); 491 } 492 m->kids = nil; 493 break; 494 } 495 } 496 return 1; 497 } 498 499 static long 500 msgReadFile(Msg *m, char *file, char **ss) 501 { 502 Dir *d; 503 char *s, buf[BufSize]; 504 vlong length; 505 long n, nn; 506 int fd; 507 508 fd = msgFile(m, file); 509 if(fd < 0){ 510 msgDead(m); 511 return -1; 512 } 513 514 n = read(fd, buf, BufSize); 515 if(n < BufSize){ 516 close(fd); 517 if(n < 0){ 518 *ss = nil; 519 return -1; 520 } 521 s = emalloc(n + 1); 522 memmove(s, buf, n); 523 s[n] = '\0'; 524 *ss = s; 525 return n; 526 } 527 528 d = dirfstat(fd); 529 if(d == nil){ 530 close(fd); 531 return -1; 532 } 533 length = d->length; 534 free(d); 535 nn = length; 536 s = emalloc(nn + 1); 537 memmove(s, buf, n); 538 if(nn > n) 539 nn = readn(fd, s+n, nn-n) + n; 540 close(fd); 541 if(nn != length){ 542 free(s); 543 return -1; 544 } 545 s[nn] = '\0'; 546 *ss = s; 547 return nn; 548 } 549 550 static void 551 freeMAddr(MAddr *a) 552 { 553 MAddr *p; 554 555 while(a != nil){ 556 p = a; 557 a = a->next; 558 free(p->personal); 559 free(p->box); 560 free(p->host); 561 free(p); 562 } 563 } 564 565 /* 566 * the message is corrupted or illegal. 567 * reset message fields. msgStruct will reparse the message, 568 * relying on msgFile to make up corrected body parts. 569 */ 570 static int 571 msgBogus(Msg *m, int flags) 572 { 573 if(!(m->bogus & flags)) 574 m->bogus |= flags; 575 m->lines = ~0; 576 free(m->head.buf); 577 free(m->mime.buf); 578 memset(&m->head, 0, sizeof(Header)); 579 memset(&m->mime, 0, sizeof(Header)); 580 return 0; 581 } 582 583 /* 584 * stolen from upas/marshal; base64 encodes from one fd to another. 585 * 586 * the size of buf is very important to enc64. Anything other than 587 * a multiple of 3 will cause enc64 to output a termination sequence. 588 * To ensure that a full buf corresponds to a multiple of complete lines, 589 * we make buf a multiple of 3*18 since that's how many enc64 sticks on 590 * a single line. This avoids short lines in the output which is pleasing 591 * but not necessary. 592 */ 593 static int 594 enc64x18(char *out, int lim, uchar *in, int n) 595 { 596 int m, mm, nn; 597 598 nn = 0; 599 for(; n > 0; n -= m){ 600 m = 18 * 3; 601 if(m > n) 602 m = n; 603 mm = enc64(out, lim - nn, in, m); 604 in += m; 605 out += mm; 606 *out++ = '\r'; 607 *out++ = '\n'; 608 nn += mm + 2; 609 } 610 return nn; 611 } 612 613 static void 614 body64(int in, int out) 615 { 616 uchar buf[3*18*54]; 617 char obuf[3*18*54*2]; 618 int m, n; 619 620 for(;;){ 621 n = read(in, buf, sizeof(buf)); 622 if(n < 0) 623 return; 624 if(n == 0) 625 break; 626 m = enc64x18(obuf, sizeof(obuf), buf, n); 627 if(write(out, obuf, m) < 0) 628 return; 629 } 630 } 631 632 /* 633 * strip all non-printable characters from a file 634 */ 635 static void 636 bodystrip(int in, int out) 637 { 638 uchar buf[3*18*54]; 639 int m, n, i, c; 640 641 for(;;){ 642 n = read(in, buf, sizeof(buf)); 643 if(n < 0) 644 return; 645 if(n == 0) 646 break; 647 m = 0; 648 for(i = 0; i < n; i++){ 649 c = buf[i]; 650 if(c > 0x1f && c < 0x7f /* normal characters */ 651 || c >= 0x9 && c <= 0xd) /* \t, \n, vertical tab, form feed, \r */ 652 buf[m++] = c; 653 } 654 655 if(m && write(out, buf, m) < 0) 656 return; 657 } 658 } 659 660 /* 661 * read in the message body to count \n without a preceding \r 662 */ 663 static int 664 msgBodySize(Msg *m) 665 { 666 Dir *d; 667 char buf[BufSize + 2], *s, *se; 668 vlong length; 669 ulong size, lines, bad; 670 int n, fd, c; 671 672 if(m->lines != ~0UL) 673 return 1; 674 fd = msgFile(m, "rawbody"); 675 if(fd < 0) 676 return 0; 677 d = dirfstat(fd); 678 if(d == nil){ 679 close(fd); 680 return 0; 681 } 682 length = d->length; 683 free(d); 684 685 size = 0; 686 lines = 0; 687 bad = 0; 688 buf[0] = ' '; 689 for(;;){ 690 n = read(fd, &buf[1], BufSize); 691 if(n <= 0) 692 break; 693 size += n; 694 se = &buf[n + 1]; 695 for(s = &buf[1]; s < se; s++){ 696 c = *s; 697 if(c == '\0'){ 698 close(fd); 699 return msgBogus(m, BogusBody); 700 } 701 if(c != '\n') 702 continue; 703 if(s[-1] != '\r') 704 bad++; 705 lines++; 706 } 707 buf[0] = buf[n]; 708 } 709 if(size != length) 710 bye("bad length reading rawbody"); 711 size += bad; 712 m->size = size; 713 m->lines = lines; 714 close(fd); 715 return 1; 716 } 717 718 /* 719 * retrieve information from the unixheader file 720 */ 721 static int 722 msgUnix(Msg *m, int top) 723 { 724 Tm tm; 725 char *s, *ss; 726 727 if(m->unixDate != nil) 728 return 1; 729 730 if(!top){ 731 bogus: 732 m->unixDate = estrdup(""); 733 m->unixFrom = unixFrom(nil); 734 return 1; 735 } 736 737 if(msgReadFile(m, "unixheader", &ss) < 0) 738 return 0; 739 s = ss; 740 s = strchr(s, ' '); 741 if(s == nil){ 742 free(ss); 743 goto bogus; 744 } 745 s++; 746 m->unixFrom = unixFrom(s); 747 s = (char*)headStr; 748 if(date2tm(&tm, s) == nil) 749 s = m->info[IUnixDate]; 750 m->unixDate = estrdup(s); 751 free(ss); 752 return 1; 753 } 754 755 /* 756 * parse the address in the unix header 757 * last line of defence, so must return something 758 */ 759 static MAddr * 760 unixFrom(char *s) 761 { 762 MAddr *a; 763 char *e, *t; 764 765 if(s == nil) 766 return nil; 767 headStr = (uchar*)s; 768 t = emalloc(strlen(s) + 2); 769 e = headAddrSpec(t, nil); 770 if(e == nil) 771 a = nil; 772 else{ 773 if(*e != '\0') 774 *e++ = '\0'; 775 else 776 e = site; 777 a = MKZ(MAddr); 778 779 a->box = estrdup(t); 780 a->host = estrdup(e); 781 } 782 free(t); 783 return a; 784 } 785 786 /* 787 * read in the entire header, 788 * and parse out any existing mime headers 789 */ 790 static int 791 msgHeader(Msg *m, Header *h, char *file) 792 { 793 char *s, *ss, *t, *te; 794 ulong lines, n, nn; 795 long ns; 796 int dated, c; 797 798 if(h->buf != nil) 799 return 1; 800 801 ns = msgReadFile(m, file, &ss); 802 if(ns < 0) 803 return 0; 804 s = ss; 805 n = ns; 806 807 /* 808 * count lines ending with \n and \r\n 809 * add an extra line at the end, since upas/fs headers 810 * don't have a terminating \r\n 811 */ 812 lines = 1; 813 te = s + ns; 814 for(t = s; t < te; t++){ 815 c = *t; 816 if(c == '\0') 817 return msgBogus(m, BogusHeader); 818 if(c != '\n') 819 continue; 820 if(t == s || t[-1] != '\r') 821 n++; 822 lines++; 823 } 824 if(t > s && t[-1] != '\n'){ 825 if(t[-1] != '\r') 826 n++; 827 n++; 828 } 829 n += 2; 830 h->buf = emalloc(n + 1); 831 h->size = n; 832 h->lines = lines; 833 834 /* 835 * make sure all headers end in \r\n 836 */ 837 nn = 0; 838 for(t = s; t < te; t++){ 839 c = *t; 840 if(c == '\n'){ 841 if(!nn || h->buf[nn - 1] != '\r') 842 h->buf[nn++] = '\r'; 843 lines++; 844 } 845 h->buf[nn++] = c; 846 } 847 if(nn && h->buf[nn-1] != '\n'){ 848 if(h->buf[nn-1] != '\r') 849 h->buf[nn++] = '\r'; 850 h->buf[nn++] = '\n'; 851 } 852 h->buf[nn++] = '\r'; 853 h->buf[nn++] = '\n'; 854 h->buf[nn] = '\0'; 855 if(nn != n) 856 bye("misconverted header %d %d", nn, n); 857 free(s); 858 859 /* 860 * and parse some mime headers 861 */ 862 headStr = (uchar*)h->buf; 863 dated = 0; 864 while(s = headAtom(headFieldStop)){ 865 if(cistrcmp(s, "content-type") == 0) 866 mimeType(h); 867 else if(cistrcmp(s, "content-transfer-encoding") == 0) 868 mimeEncoding(h); 869 else if(cistrcmp(s, "content-id") == 0) 870 mimeId(h); 871 else if(cistrcmp(s, "content-description") == 0) 872 mimeDescription(h); 873 else if(cistrcmp(s, "content-disposition") == 0) 874 mimeDisposition(h); 875 else if(cistrcmp(s, "content-md5") == 0) 876 mimeMd5(h); 877 else if(cistrcmp(s, "content-language") == 0) 878 mimeLanguage(h); 879 else if(h == &m->head && cistrcmp(s, "from") == 0) 880 m->from = headMAddr(m->from); 881 else if(h == &m->head && cistrcmp(s, "to") == 0) 882 m->to = headMAddr(m->to); 883 else if(h == &m->head && cistrcmp(s, "reply-to") == 0) 884 m->replyTo = headMAddr(m->replyTo); 885 else if(h == &m->head && cistrcmp(s, "sender") == 0) 886 m->sender = headMAddr(m->sender); 887 else if(h == &m->head && cistrcmp(s, "cc") == 0) 888 m->cc = headMAddr(m->cc); 889 else if(h == &m->head && cistrcmp(s, "bcc") == 0) 890 m->bcc = headMAddr(m->bcc); 891 else if(h == &m->head && cistrcmp(s, "date") == 0) 892 dated = 1; 893 headSkip(); 894 free(s); 895 } 896 897 if(h == &m->head){ 898 if(m->from == nil){ 899 m->from = m->unixFrom; 900 if(m->from != nil){ 901 s = maddrStr(m->from); 902 msgAddHead(m, "From", s); 903 free(s); 904 } 905 } 906 if(m->sender == nil) 907 m->sender = m->from; 908 if(m->replyTo == nil) 909 m->replyTo = m->from; 910 911 if(infoIsNil(m->info[IDate])) 912 m->info[IDate] = m->unixDate; 913 if(!dated && m->from != nil) 914 msgAddDate(m); 915 } 916 return 1; 917 } 918 919 /* 920 * prepend head: body to the cached header 921 */ 922 static void 923 msgAddHead(Msg *m, char *head, char *body) 924 { 925 char *s; 926 long size, n; 927 928 n = strlen(head) + strlen(body) + 4; 929 size = m->head.size + n; 930 s = emalloc(size + 1); 931 snprint(s, size + 1, "%s: %s\r\n%s", head, body, m->head.buf); 932 free(m->head.buf); 933 m->head.buf = s; 934 m->head.size = size; 935 m->head.lines++; 936 } 937 938 static void 939 msgAddDate(Msg *m) 940 { 941 Tm tm; 942 char buf[64]; 943 944 /* don't bother if we don't have a date */ 945 if(infoIsNil(m->info[IDate])) 946 return; 947 948 date2tm(&tm, m->info[IDate]); 949 rfc822date(buf, sizeof(buf), &tm); 950 msgAddHead(m, "Date", buf); 951 } 952 953 static MimeHdr* 954 mkMimeHdr(char *s, char *t, MimeHdr *next) 955 { 956 MimeHdr *mh; 957 958 mh = MK(MimeHdr); 959 mh->s = s; 960 mh->t = t; 961 mh->next = next; 962 return mh; 963 } 964 965 static void 966 freeMimeHdr(MimeHdr *mh) 967 { 968 MimeHdr *last; 969 970 while(mh != nil){ 971 last = mh; 972 mh = mh->next; 973 free(last->s); 974 free(last->t); 975 free(last); 976 } 977 } 978 979 static void 980 cleanupHeader(Header *h) 981 { 982 freeMimeHdr(h->type); 983 freeMimeHdr(h->id); 984 freeMimeHdr(h->description); 985 freeMimeHdr(h->encoding); 986 freeMimeHdr(h->md5); 987 freeMimeHdr(h->disposition); 988 freeMimeHdr(h->language); 989 } 990 991 /* 992 * parser for rfc822 & mime header fields 993 */ 994 995 /* 996 * type : 'content-type' ':' token '/' token params 997 */ 998 static void 999 mimeType(Header *h) 1000 { 1001 char *s, *t; 1002 1003 if(headChar(1) != ':') 1004 return; 1005 s = headAtom(mimeTokenStop); 1006 if(s == nil || headChar(1) != '/'){ 1007 free(s); 1008 return; 1009 } 1010 t = headAtom(mimeTokenStop); 1011 if(t == nil){ 1012 free(s); 1013 return; 1014 } 1015 h->type = mkMimeHdr(s, t, mimeParams()); 1016 } 1017 1018 /* 1019 * params : 1020 * | params ';' token '=' token 1021 * | params ';' token '=' quoted-str 1022 */ 1023 static MimeHdr* 1024 mimeParams(void) 1025 { 1026 MimeHdr head, *last; 1027 char *s, *t; 1028 1029 head.next = nil; 1030 last = &head; 1031 for(;;){ 1032 if(headChar(1) != ';') 1033 break; 1034 s = headAtom(mimeTokenStop); 1035 if(s == nil || headChar(1) != '='){ 1036 free(s); 1037 break; 1038 } 1039 if(headChar(0) == '"'){ 1040 t = headQuoted('"', '"'); 1041 stripQuotes(t); 1042 }else 1043 t = headAtom(mimeTokenStop); 1044 if(t == nil){ 1045 free(s); 1046 break; 1047 } 1048 last->next = mkMimeHdr(s, t, nil); 1049 last = last->next; 1050 } 1051 return head.next; 1052 } 1053 1054 /* 1055 * encoding : 'content-transfer-encoding' ':' token 1056 */ 1057 static void 1058 mimeEncoding(Header *h) 1059 { 1060 char *s; 1061 1062 if(headChar(1) != ':') 1063 return; 1064 s = headAtom(mimeTokenStop); 1065 if(s == nil) 1066 return; 1067 h->encoding = mkMimeHdr(s, nil, nil); 1068 } 1069 1070 /* 1071 * mailaddr : ':' addresses 1072 */ 1073 static MAddr* 1074 headMAddr(MAddr *old) 1075 { 1076 MAddr *a; 1077 1078 if(headChar(1) != ':') 1079 return old; 1080 1081 if(headChar(0) == '\n') 1082 return old; 1083 1084 a = headAddresses(); 1085 if(a == nil) 1086 return old; 1087 1088 freeMAddr(old); 1089 return a; 1090 } 1091 1092 /* 1093 * addresses : address | addresses ',' address 1094 */ 1095 static MAddr* 1096 headAddresses(void) 1097 { 1098 MAddr *addr, *tail, *a; 1099 1100 addr = headAddress(); 1101 if(addr == nil) 1102 return nil; 1103 tail = addr; 1104 while(headChar(0) == ','){ 1105 headChar(1); 1106 a = headAddress(); 1107 if(a == nil){ 1108 freeMAddr(addr); 1109 return nil; 1110 } 1111 tail->next = a; 1112 tail = a; 1113 } 1114 return addr; 1115 } 1116 1117 /* 1118 * address : mailbox | group 1119 * group : phrase ':' mboxes ';' | phrase ':' ';' 1120 * mailbox : addr-spec 1121 * | optphrase '<' addr-spec '>' 1122 * | optphrase '<' route ':' addr-spec '>' 1123 * optphrase : | phrase 1124 * route : '@' domain 1125 * | route ',' '@' domain 1126 * personal names are the phrase before '<', 1127 * or a comment before or after a simple addr-spec 1128 */ 1129 static MAddr* 1130 headAddress(void) 1131 { 1132 MAddr *addr; 1133 uchar *hs; 1134 char *s, *e, *w, *personal; 1135 int c; 1136 1137 s = emalloc(strlen((char*)headStr) + 2); 1138 e = s; 1139 personal = headSkipWhite(1); 1140 c = headChar(0); 1141 if(c == '<') 1142 w = nil; 1143 else{ 1144 w = headWord(); 1145 c = headChar(0); 1146 } 1147 if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){ 1148 lastWhite = headStr; 1149 e = headAddrSpec(s, w); 1150 if(personal == nil){ 1151 hs = headStr; 1152 headStr = lastWhite; 1153 personal = headSkipWhite(1); 1154 headStr = hs; 1155 } 1156 }else{ 1157 if(c != '<' || w != nil){ 1158 free(personal); 1159 if(!headPhrase(e, w)){ 1160 free(s); 1161 return nil; 1162 } 1163 1164 /* 1165 * ignore addresses with groups, 1166 * so the only thing left if < 1167 */ 1168 c = headChar(1); 1169 if(c != '<'){ 1170 free(s); 1171 return nil; 1172 } 1173 personal = estrdup(s); 1174 }else 1175 headChar(1); 1176 1177 /* 1178 * after this point, we need to free personal before returning. 1179 * set e to nil to everything afterwards fails. 1180 * 1181 * ignore routes, they are useless, and heavily discouraged in rfc1123. 1182 * imap4 reports them up to, but not including, the terminating : 1183 */ 1184 e = s; 1185 c = headChar(0); 1186 if(c == '@'){ 1187 for(;;){ 1188 c = headChar(1); 1189 if(c != '@'){ 1190 e = nil; 1191 break; 1192 } 1193 headDomain(e); 1194 c = headChar(1); 1195 if(c != ','){ 1196 e = s; 1197 break; 1198 } 1199 } 1200 if(c != ':') 1201 e = nil; 1202 } 1203 1204 if(e != nil) 1205 e = headAddrSpec(s, nil); 1206 if(headChar(1) != '>') 1207 e = nil; 1208 } 1209 1210 /* 1211 * e points to @host, or nil if an error occured 1212 */ 1213 if(e == nil){ 1214 free(personal); 1215 addr = nil; 1216 }else{ 1217 if(*e != '\0') 1218 *e++ = '\0'; 1219 else 1220 e = site; 1221 addr = MKZ(MAddr); 1222 1223 addr->personal = personal; 1224 addr->box = estrdup(s); 1225 addr->host = estrdup(e); 1226 } 1227 free(s); 1228 return addr; 1229 } 1230 1231 /* 1232 * phrase : word 1233 * | phrase word 1234 * w is the optional initial word of the phrase 1235 * returns the end of the phrase, or nil if a failure occured 1236 */ 1237 static char* 1238 headPhrase(char *e, char *w) 1239 { 1240 int c; 1241 1242 for(;;){ 1243 if(w == nil){ 1244 w = headWord(); 1245 if(w == nil) 1246 return nil; 1247 } 1248 if(w[0] == '"') 1249 stripQuotes(w); 1250 strcpy(e, w); 1251 free(w); 1252 w = nil; 1253 e = strchr(e, '\0'); 1254 c = headChar(0); 1255 if(c <= ' ' || strchr(headAtomStop, c) != nil && c != '"') 1256 break; 1257 *e++ = ' '; 1258 *e = '\0'; 1259 } 1260 return e; 1261 } 1262 1263 /* 1264 * addr-spec : local-part '@' domain 1265 * | local-part extension to allow ! and local names 1266 * local-part : word 1267 * | local-part '.' word 1268 * 1269 * if no '@' is present, rewrite d!e!f!u as @d,@e:u@f, 1270 * where d, e, f are valid domain components. 1271 * the @d,@e: is ignored, since routes are ignored. 1272 * perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas. 1273 * 1274 * returns a pointer to '@', the end if none, or nil if there was an error 1275 */ 1276 static char* 1277 headAddrSpec(char *e, char *w) 1278 { 1279 char *s, *at, *b, *bang, *dom; 1280 int c; 1281 1282 s = e; 1283 for(;;){ 1284 if(w == nil){ 1285 w = headWord(); 1286 if(w == nil) 1287 return nil; 1288 } 1289 strcpy(e, w); 1290 free(w); 1291 w = nil; 1292 e = strchr(e, '\0'); 1293 lastWhite = headStr; 1294 c = headChar(0); 1295 if(c != '.') 1296 break; 1297 headChar(1); 1298 *e++ = '.'; 1299 *e = '\0'; 1300 } 1301 1302 if(c != '@'){ 1303 /* 1304 * extenstion: allow name without domain 1305 * check for domain!xxx 1306 */ 1307 bang = domBang(s); 1308 if(bang == nil) 1309 return e; 1310 1311 /* 1312 * if dom1!dom2!xxx, ignore dom1! 1313 */ 1314 dom = s; 1315 for(; b = domBang(bang + 1); bang = b) 1316 dom = bang + 1; 1317 1318 /* 1319 * convert dom!mbox into mbox@dom 1320 */ 1321 *bang = '@'; 1322 strrev(dom, bang); 1323 strrev(bang+1, e); 1324 strrev(dom, e); 1325 bang = &dom[e - bang - 1]; 1326 if(dom > s){ 1327 bang -= dom - s; 1328 for(e = s; *e = *dom; e++) 1329 dom++; 1330 } 1331 1332 /* 1333 * eliminate a trailing '.' 1334 */ 1335 if(e[-1] == '.') 1336 e[-1] = '\0'; 1337 return bang; 1338 } 1339 headChar(1); 1340 1341 at = e; 1342 *e++ = '@'; 1343 *e = '\0'; 1344 if(!headDomain(e)) 1345 return nil; 1346 return at; 1347 } 1348 1349 /* 1350 * find the ! in domain!rest, where domain must have at least 1351 * one internal '.' 1352 */ 1353 static char* 1354 domBang(char *s) 1355 { 1356 int dot, c; 1357 1358 dot = 0; 1359 for(; c = *s; s++){ 1360 if(c == '!'){ 1361 if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0') 1362 return nil; 1363 return s; 1364 } 1365 if(c == '"') 1366 break; 1367 if(c == '.') 1368 dot++; 1369 } 1370 return nil; 1371 } 1372 1373 /* 1374 * domain : sub-domain 1375 * | domain '.' sub-domain 1376 * returns the end of the domain, or nil if a failure occured 1377 */ 1378 static char* 1379 headDomain(char *e) 1380 { 1381 char *w; 1382 1383 for(;;){ 1384 w = headSubDomain(); 1385 if(w == nil) 1386 return nil; 1387 strcpy(e, w); 1388 free(w); 1389 e = strchr(e, '\0'); 1390 lastWhite = headStr; 1391 if(headChar(0) != '.') 1392 break; 1393 headChar(1); 1394 *e++ = '.'; 1395 *e = '\0'; 1396 } 1397 return e; 1398 } 1399 1400 /* 1401 * id : 'content-id' ':' msg-id 1402 * msg-id : '<' addr-spec '>' 1403 */ 1404 static void 1405 mimeId(Header *h) 1406 { 1407 char *s, *e, *w; 1408 1409 if(headChar(1) != ':') 1410 return; 1411 if(headChar(1) != '<') 1412 return; 1413 1414 s = emalloc(strlen((char*)headStr) + 3); 1415 e = s; 1416 *e++ = '<'; 1417 e = headAddrSpec(e, nil); 1418 if(e == nil || headChar(1) != '>'){ 1419 free(s); 1420 return; 1421 } 1422 e = strchr(e, '\0'); 1423 *e++ = '>'; 1424 e[0] = '\0'; 1425 w = strdup(s); 1426 free(s); 1427 h->id = mkMimeHdr(w, nil, nil); 1428 } 1429 1430 /* 1431 * description : 'content-description' ':' *text 1432 */ 1433 static void 1434 mimeDescription(Header *h) 1435 { 1436 if(headChar(1) != ':') 1437 return; 1438 headSkipWhite(0); 1439 h->description = mkMimeHdr(headText(), nil, nil); 1440 } 1441 1442 /* 1443 * disposition : 'content-disposition' ':' token params 1444 */ 1445 static void 1446 mimeDisposition(Header *h) 1447 { 1448 char *s; 1449 1450 if(headChar(1) != ':') 1451 return; 1452 s = headAtom(mimeTokenStop); 1453 if(s == nil) 1454 return; 1455 h->disposition = mkMimeHdr(s, nil, mimeParams()); 1456 } 1457 1458 /* 1459 * md5 : 'content-md5' ':' token 1460 */ 1461 static void 1462 mimeMd5(Header *h) 1463 { 1464 char *s; 1465 1466 if(headChar(1) != ':') 1467 return; 1468 s = headAtom(mimeTokenStop); 1469 if(s == nil) 1470 return; 1471 h->md5 = mkMimeHdr(s, nil, nil); 1472 } 1473 1474 /* 1475 * language : 'content-language' ':' langs 1476 * langs : token 1477 * | langs commas token 1478 * commas : ',' 1479 * | commas ',' 1480 */ 1481 static void 1482 mimeLanguage(Header *h) 1483 { 1484 MimeHdr head, *last; 1485 char *s; 1486 1487 head.next = nil; 1488 last = &head; 1489 for(;;){ 1490 s = headAtom(mimeTokenStop); 1491 if(s == nil) 1492 break; 1493 last->next = mkMimeHdr(s, nil, nil); 1494 last = last->next; 1495 while(headChar(0) != ',') 1496 headChar(1); 1497 } 1498 h->language = head.next; 1499 } 1500 1501 /* 1502 * token : 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimeTokenStop> 1503 * atom : 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headAtomStop> 1504 * note this allows 8 bit characters, which occur in utf. 1505 */ 1506 static char* 1507 headAtom(char *disallowed) 1508 { 1509 char *s; 1510 int c, ns, as; 1511 1512 headSkipWhite(0); 1513 1514 s = emalloc(StrAlloc); 1515 as = StrAlloc; 1516 ns = 0; 1517 for(;;){ 1518 c = *headStr++; 1519 if(c <= ' ' || strchr(disallowed, c) != nil){ 1520 headStr--; 1521 break; 1522 } 1523 s[ns++] = c; 1524 if(ns >= as){ 1525 as += StrAlloc; 1526 s = erealloc(s, as); 1527 } 1528 } 1529 if(ns == 0){ 1530 free(s); 1531 return 0; 1532 } 1533 s[ns] = '\0'; 1534 return s; 1535 } 1536 1537 /* 1538 * sub-domain : atom | domain-lit 1539 */ 1540 static char * 1541 headSubDomain(void) 1542 { 1543 if(headChar(0) == '[') 1544 return headQuoted('[', ']'); 1545 return headAtom(headAtomStop); 1546 } 1547 1548 /* 1549 * word : atom | quoted-str 1550 */ 1551 static char * 1552 headWord(void) 1553 { 1554 if(headChar(0) == '"') 1555 return headQuoted('"', '"'); 1556 return headAtom(headAtomStop); 1557 } 1558 1559 /* 1560 * q is a quoted string. remove enclosing " and and \ escapes 1561 */ 1562 static void 1563 stripQuotes(char *q) 1564 { 1565 char *s; 1566 int c; 1567 1568 if(q == nil) 1569 return; 1570 s = q++; 1571 while(c = *q++){ 1572 if(c == '\\'){ 1573 c = *q++; 1574 if(!c) 1575 return; 1576 } 1577 *s++ = c; 1578 } 1579 s[-1] = '\0'; 1580 } 1581 1582 /* 1583 * quoted-str : '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"' 1584 * domain-lit : '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']' 1585 */ 1586 static char * 1587 headQuoted(int start, int stop) 1588 { 1589 char *s; 1590 int c, ns, as; 1591 1592 if(headChar(1) != start) 1593 return nil; 1594 s = emalloc(StrAlloc); 1595 as = StrAlloc; 1596 ns = 0; 1597 s[ns++] = start; 1598 for(;;){ 1599 c = *headStr; 1600 if(c == stop){ 1601 headStr++; 1602 break; 1603 } 1604 if(c == '\0'){ 1605 free(s); 1606 return nil; 1607 } 1608 if(c == '\r'){ 1609 headStr++; 1610 continue; 1611 } 1612 if(c == '\n'){ 1613 headStr++; 1614 while(*headStr == ' ' || *headStr == '\t' || *headStr == '\r' || *headStr == '\n') 1615 headStr++; 1616 c = ' '; 1617 }else if(c == '\\'){ 1618 headStr++; 1619 s[ns++] = c; 1620 c = *headStr; 1621 if(c == '\0'){ 1622 free(s); 1623 return nil; 1624 } 1625 headStr++; 1626 }else 1627 headStr++; 1628 s[ns++] = c; 1629 if(ns + 1 >= as){ /* leave room for \c or "0 */ 1630 as += StrAlloc; 1631 s = erealloc(s, as); 1632 } 1633 } 1634 s[ns++] = stop; 1635 s[ns] = '\0'; 1636 return s; 1637 } 1638 1639 /* 1640 * headText : contents of rest of header line 1641 */ 1642 static char * 1643 headText(void) 1644 { 1645 uchar *v; 1646 char *s; 1647 1648 v = headStr; 1649 headToEnd(); 1650 s = emalloc(headStr - v + 1); 1651 memmove(s, v, headStr - v); 1652 s[headStr - v] = '\0'; 1653 return s; 1654 } 1655 1656 /* 1657 * white space is ' ' '\t' or nested comments. 1658 * skip white space. 1659 * if com and a comment is seen, 1660 * return it's contents and stop processing white space. 1661 */ 1662 static char* 1663 headSkipWhite(int com) 1664 { 1665 char *s; 1666 int c, incom, as, ns; 1667 1668 s = nil; 1669 as = StrAlloc; 1670 ns = 0; 1671 if(com) 1672 s = emalloc(StrAlloc); 1673 incom = 0; 1674 for(; c = *headStr; headStr++){ 1675 switch(c){ 1676 case ' ': 1677 case '\t': 1678 case '\r': 1679 c = ' '; 1680 break; 1681 case '\n': 1682 c = headStr[1]; 1683 if(c != ' ' && c != '\t') 1684 goto breakout; 1685 c = ' '; 1686 break; 1687 case '\\': 1688 if(com && incom) 1689 s[ns++] = c; 1690 c = headStr[1]; 1691 if(c == '\0') 1692 goto breakout; 1693 headStr++; 1694 break; 1695 case '(': 1696 incom++; 1697 if(incom == 1) 1698 continue; 1699 break; 1700 case ')': 1701 incom--; 1702 if(com && !incom){ 1703 s[ns] = '\0'; 1704 return s; 1705 } 1706 break; 1707 default: 1708 if(!incom) 1709 goto breakout; 1710 break; 1711 } 1712 if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){ 1713 s[ns++] = c; 1714 if(ns + 1 >= as){ /* leave room for \c or 0 */ 1715 as += StrAlloc; 1716 s = erealloc(s, as); 1717 } 1718 } 1719 } 1720 breakout:; 1721 free(s); 1722 return nil; 1723 } 1724 1725 /* 1726 * return the next non-white character 1727 */ 1728 static int 1729 headChar(int eat) 1730 { 1731 int c; 1732 1733 headSkipWhite(0); 1734 c = *headStr; 1735 if(eat && c != '\0' && c != '\n') 1736 headStr++; 1737 return c; 1738 } 1739 1740 static void 1741 headToEnd(void) 1742 { 1743 uchar *s; 1744 int c; 1745 1746 for(;;){ 1747 s = headStr; 1748 c = *s++; 1749 while(c == '\r') 1750 c = *s++; 1751 if(c == '\n'){ 1752 c = *s++; 1753 if(c != ' ' && c != '\t') 1754 return; 1755 } 1756 if(c == '\0') 1757 return; 1758 headStr = s; 1759 } 1760 } 1761 1762 static void 1763 headSkip(void) 1764 { 1765 int c; 1766 1767 while(c = *headStr){ 1768 headStr++; 1769 if(c == '\n'){ 1770 c = *headStr; 1771 if(c == ' ' || c == '\t') 1772 continue; 1773 return; 1774 } 1775 } 1776 } 1777