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 if(s == nil){ 751 free(ss); 752 goto bogus; 753 } 754 m->unixDate = estrdup(s); 755 free(ss); 756 return 1; 757 } 758 759 /* 760 * parse the address in the unix header 761 * last line of defence, so must return something 762 */ 763 static MAddr * 764 unixFrom(char *s) 765 { 766 MAddr *a; 767 char *e, *t; 768 769 if(s == nil) 770 return nil; 771 headStr = (uchar*)s; 772 t = emalloc(strlen(s) + 2); 773 e = headAddrSpec(t, nil); 774 if(e == nil) 775 a = nil; 776 else{ 777 if(*e != '\0') 778 *e++ = '\0'; 779 else 780 e = site; 781 a = MKZ(MAddr); 782 783 a->box = estrdup(t); 784 a->host = estrdup(e); 785 } 786 free(t); 787 return a; 788 } 789 790 /* 791 * read in the entire header, 792 * and parse out any existing mime headers 793 */ 794 static int 795 msgHeader(Msg *m, Header *h, char *file) 796 { 797 char *s, *ss, *t, *te; 798 ulong lines, n, nn; 799 long ns; 800 int dated, c; 801 802 if(h->buf != nil) 803 return 1; 804 805 ns = msgReadFile(m, file, &ss); 806 if(ns < 0) 807 return 0; 808 s = ss; 809 n = ns; 810 811 /* 812 * count lines ending with \n and \r\n 813 * add an extra line at the end, since upas/fs headers 814 * don't have a terminating \r\n 815 */ 816 lines = 1; 817 te = s + ns; 818 for(t = s; t < te; t++){ 819 c = *t; 820 if(c == '\0') 821 return msgBogus(m, BogusHeader); 822 if(c != '\n') 823 continue; 824 if(t == s || t[-1] != '\r') 825 n++; 826 lines++; 827 } 828 if(t > s && t[-1] != '\n'){ 829 if(t[-1] != '\r') 830 n++; 831 n++; 832 } 833 n += 2; 834 h->buf = emalloc(n + 1); 835 h->size = n; 836 h->lines = lines; 837 838 /* 839 * make sure all headers end in \r\n 840 */ 841 nn = 0; 842 for(t = s; t < te; t++){ 843 c = *t; 844 if(c == '\n'){ 845 if(!nn || h->buf[nn - 1] != '\r') 846 h->buf[nn++] = '\r'; 847 lines++; 848 } 849 h->buf[nn++] = c; 850 } 851 if(nn && h->buf[nn-1] != '\n'){ 852 if(h->buf[nn-1] != '\r') 853 h->buf[nn++] = '\r'; 854 h->buf[nn++] = '\n'; 855 } 856 h->buf[nn++] = '\r'; 857 h->buf[nn++] = '\n'; 858 h->buf[nn] = '\0'; 859 if(nn != n) 860 bye("misconverted header %d %d", nn, n); 861 free(s); 862 863 /* 864 * and parse some mime headers 865 */ 866 headStr = (uchar*)h->buf; 867 dated = 0; 868 while(s = headAtom(headFieldStop)){ 869 if(cistrcmp(s, "content-type") == 0) 870 mimeType(h); 871 else if(cistrcmp(s, "content-transfer-encoding") == 0) 872 mimeEncoding(h); 873 else if(cistrcmp(s, "content-id") == 0) 874 mimeId(h); 875 else if(cistrcmp(s, "content-description") == 0) 876 mimeDescription(h); 877 else if(cistrcmp(s, "content-disposition") == 0) 878 mimeDisposition(h); 879 else if(cistrcmp(s, "content-md5") == 0) 880 mimeMd5(h); 881 else if(cistrcmp(s, "content-language") == 0) 882 mimeLanguage(h); 883 else if(h == &m->head && cistrcmp(s, "from") == 0) 884 m->from = headMAddr(m->from); 885 else if(h == &m->head && cistrcmp(s, "to") == 0) 886 m->to = headMAddr(m->to); 887 else if(h == &m->head && cistrcmp(s, "reply-to") == 0) 888 m->replyTo = headMAddr(m->replyTo); 889 else if(h == &m->head && cistrcmp(s, "sender") == 0) 890 m->sender = headMAddr(m->sender); 891 else if(h == &m->head && cistrcmp(s, "cc") == 0) 892 m->cc = headMAddr(m->cc); 893 else if(h == &m->head && cistrcmp(s, "bcc") == 0) 894 m->bcc = headMAddr(m->bcc); 895 else if(h == &m->head && cistrcmp(s, "date") == 0) 896 dated = 1; 897 headSkip(); 898 free(s); 899 } 900 901 if(h == &m->head){ 902 if(m->from == nil){ 903 m->from = m->unixFrom; 904 if(m->from != nil){ 905 s = maddrStr(m->from); 906 msgAddHead(m, "From", s); 907 free(s); 908 } 909 } 910 if(m->sender == nil) 911 m->sender = m->from; 912 if(m->replyTo == nil) 913 m->replyTo = m->from; 914 915 if(infoIsNil(m->info[IDate])) 916 m->info[IDate] = m->unixDate; 917 if(!dated && m->from != nil) 918 msgAddDate(m); 919 } 920 return 1; 921 } 922 923 /* 924 * prepend head: body to the cached header 925 */ 926 static void 927 msgAddHead(Msg *m, char *head, char *body) 928 { 929 char *s; 930 long size, n; 931 932 n = strlen(head) + strlen(body) + 4; 933 size = m->head.size + n; 934 s = emalloc(size + 1); 935 snprint(s, size + 1, "%s: %s\r\n%s", head, body, m->head.buf); 936 free(m->head.buf); 937 m->head.buf = s; 938 m->head.size = size; 939 m->head.lines++; 940 } 941 942 static void 943 msgAddDate(Msg *m) 944 { 945 Tm tm; 946 char buf[64]; 947 948 /* don't bother if we don't have a date */ 949 if(infoIsNil(m->info[IDate])) 950 return; 951 952 date2tm(&tm, m->info[IDate]); 953 rfc822date(buf, sizeof(buf), &tm); 954 msgAddHead(m, "Date", buf); 955 } 956 957 static MimeHdr* 958 mkMimeHdr(char *s, char *t, MimeHdr *next) 959 { 960 MimeHdr *mh; 961 962 mh = MK(MimeHdr); 963 mh->s = s; 964 mh->t = t; 965 mh->next = next; 966 return mh; 967 } 968 969 static void 970 freeMimeHdr(MimeHdr *mh) 971 { 972 MimeHdr *last; 973 974 while(mh != nil){ 975 last = mh; 976 mh = mh->next; 977 free(last->s); 978 free(last->t); 979 free(last); 980 } 981 } 982 983 static void 984 cleanupHeader(Header *h) 985 { 986 freeMimeHdr(h->type); 987 freeMimeHdr(h->id); 988 freeMimeHdr(h->description); 989 freeMimeHdr(h->encoding); 990 freeMimeHdr(h->md5); 991 freeMimeHdr(h->disposition); 992 freeMimeHdr(h->language); 993 } 994 995 /* 996 * parser for rfc822 & mime header fields 997 */ 998 999 /* 1000 * type : 'content-type' ':' token '/' token params 1001 */ 1002 static void 1003 mimeType(Header *h) 1004 { 1005 char *s, *t; 1006 1007 if(headChar(1) != ':') 1008 return; 1009 s = headAtom(mimeTokenStop); 1010 if(s == nil || headChar(1) != '/'){ 1011 free(s); 1012 return; 1013 } 1014 t = headAtom(mimeTokenStop); 1015 if(t == nil){ 1016 free(s); 1017 return; 1018 } 1019 h->type = mkMimeHdr(s, t, mimeParams()); 1020 } 1021 1022 /* 1023 * params : 1024 * | params ';' token '=' token 1025 * | params ';' token '=' quoted-str 1026 */ 1027 static MimeHdr* 1028 mimeParams(void) 1029 { 1030 MimeHdr head, *last; 1031 char *s, *t; 1032 1033 head.next = nil; 1034 last = &head; 1035 for(;;){ 1036 if(headChar(1) != ';') 1037 break; 1038 s = headAtom(mimeTokenStop); 1039 if(s == nil || headChar(1) != '='){ 1040 free(s); 1041 break; 1042 } 1043 if(headChar(0) == '"'){ 1044 t = headQuoted('"', '"'); 1045 stripQuotes(t); 1046 }else 1047 t = headAtom(mimeTokenStop); 1048 if(t == nil){ 1049 free(s); 1050 break; 1051 } 1052 last->next = mkMimeHdr(s, t, nil); 1053 last = last->next; 1054 } 1055 return head.next; 1056 } 1057 1058 /* 1059 * encoding : 'content-transfer-encoding' ':' token 1060 */ 1061 static void 1062 mimeEncoding(Header *h) 1063 { 1064 char *s; 1065 1066 if(headChar(1) != ':') 1067 return; 1068 s = headAtom(mimeTokenStop); 1069 if(s == nil) 1070 return; 1071 h->encoding = mkMimeHdr(s, nil, nil); 1072 } 1073 1074 /* 1075 * mailaddr : ':' addresses 1076 */ 1077 static MAddr* 1078 headMAddr(MAddr *old) 1079 { 1080 MAddr *a; 1081 1082 if(headChar(1) != ':') 1083 return old; 1084 1085 if(headChar(0) == '\n') 1086 return old; 1087 1088 a = headAddresses(); 1089 if(a == nil) 1090 return old; 1091 1092 freeMAddr(old); 1093 return a; 1094 } 1095 1096 /* 1097 * addresses : address | addresses ',' address 1098 */ 1099 static MAddr* 1100 headAddresses(void) 1101 { 1102 MAddr *addr, *tail, *a; 1103 1104 addr = headAddress(); 1105 if(addr == nil) 1106 return nil; 1107 tail = addr; 1108 while(headChar(0) == ','){ 1109 headChar(1); 1110 a = headAddress(); 1111 if(a == nil){ 1112 freeMAddr(addr); 1113 return nil; 1114 } 1115 tail->next = a; 1116 tail = a; 1117 } 1118 return addr; 1119 } 1120 1121 /* 1122 * address : mailbox | group 1123 * group : phrase ':' mboxes ';' | phrase ':' ';' 1124 * mailbox : addr-spec 1125 * | optphrase '<' addr-spec '>' 1126 * | optphrase '<' route ':' addr-spec '>' 1127 * optphrase : | phrase 1128 * route : '@' domain 1129 * | route ',' '@' domain 1130 * personal names are the phrase before '<', 1131 * or a comment before or after a simple addr-spec 1132 */ 1133 static MAddr* 1134 headAddress(void) 1135 { 1136 MAddr *addr; 1137 uchar *hs; 1138 char *s, *e, *w, *personal; 1139 int c; 1140 1141 s = emalloc(strlen((char*)headStr) + 2); 1142 e = s; 1143 personal = headSkipWhite(1); 1144 c = headChar(0); 1145 if(c == '<') 1146 w = nil; 1147 else{ 1148 w = headWord(); 1149 c = headChar(0); 1150 } 1151 if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){ 1152 lastWhite = headStr; 1153 e = headAddrSpec(s, w); 1154 if(personal == nil){ 1155 hs = headStr; 1156 headStr = lastWhite; 1157 personal = headSkipWhite(1); 1158 headStr = hs; 1159 } 1160 }else{ 1161 if(c != '<' || w != nil){ 1162 free(personal); 1163 if(!headPhrase(e, w)){ 1164 free(s); 1165 return nil; 1166 } 1167 1168 /* 1169 * ignore addresses with groups, 1170 * so the only thing left if < 1171 */ 1172 c = headChar(1); 1173 if(c != '<'){ 1174 free(s); 1175 return nil; 1176 } 1177 personal = estrdup(s); 1178 }else 1179 headChar(1); 1180 1181 /* 1182 * after this point, we need to free personal before returning. 1183 * set e to nil to everything afterwards fails. 1184 * 1185 * ignore routes, they are useless, and heavily discouraged in rfc1123. 1186 * imap4 reports them up to, but not including, the terminating : 1187 */ 1188 e = s; 1189 c = headChar(0); 1190 if(c == '@'){ 1191 for(;;){ 1192 c = headChar(1); 1193 if(c != '@'){ 1194 e = nil; 1195 break; 1196 } 1197 headDomain(e); 1198 c = headChar(1); 1199 if(c != ','){ 1200 e = s; 1201 break; 1202 } 1203 } 1204 if(c != ':') 1205 e = nil; 1206 } 1207 1208 if(e != nil) 1209 e = headAddrSpec(s, nil); 1210 if(headChar(1) != '>') 1211 e = nil; 1212 } 1213 1214 /* 1215 * e points to @host, or nil if an error occured 1216 */ 1217 if(e == nil){ 1218 free(personal); 1219 addr = nil; 1220 }else{ 1221 if(*e != '\0') 1222 *e++ = '\0'; 1223 else 1224 e = site; 1225 addr = MKZ(MAddr); 1226 1227 addr->personal = personal; 1228 addr->box = estrdup(s); 1229 addr->host = estrdup(e); 1230 } 1231 free(s); 1232 return addr; 1233 } 1234 1235 /* 1236 * phrase : word 1237 * | phrase word 1238 * w is the optional initial word of the phrase 1239 * returns the end of the phrase, or nil if a failure occured 1240 */ 1241 static char* 1242 headPhrase(char *e, char *w) 1243 { 1244 int c; 1245 1246 for(;;){ 1247 if(w == nil){ 1248 w = headWord(); 1249 if(w == nil) 1250 return nil; 1251 } 1252 if(w[0] == '"') 1253 stripQuotes(w); 1254 strcpy(e, w); 1255 free(w); 1256 w = nil; 1257 e = strchr(e, '\0'); 1258 c = headChar(0); 1259 if(c <= ' ' || strchr(headAtomStop, c) != nil && c != '"') 1260 break; 1261 *e++ = ' '; 1262 *e = '\0'; 1263 } 1264 return e; 1265 } 1266 1267 /* 1268 * addr-spec : local-part '@' domain 1269 * | local-part extension to allow ! and local names 1270 * local-part : word 1271 * | local-part '.' word 1272 * 1273 * if no '@' is present, rewrite d!e!f!u as @d,@e:u@f, 1274 * where d, e, f are valid domain components. 1275 * the @d,@e: is ignored, since routes are ignored. 1276 * perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas. 1277 * 1278 * returns a pointer to '@', the end if none, or nil if there was an error 1279 */ 1280 static char* 1281 headAddrSpec(char *e, char *w) 1282 { 1283 char *s, *at, *b, *bang, *dom; 1284 int c; 1285 1286 s = e; 1287 for(;;){ 1288 if(w == nil){ 1289 w = headWord(); 1290 if(w == nil) 1291 return nil; 1292 } 1293 strcpy(e, w); 1294 free(w); 1295 w = nil; 1296 e = strchr(e, '\0'); 1297 lastWhite = headStr; 1298 c = headChar(0); 1299 if(c != '.') 1300 break; 1301 headChar(1); 1302 *e++ = '.'; 1303 *e = '\0'; 1304 } 1305 1306 if(c != '@'){ 1307 /* 1308 * extenstion: allow name without domain 1309 * check for domain!xxx 1310 */ 1311 bang = domBang(s); 1312 if(bang == nil) 1313 return e; 1314 1315 /* 1316 * if dom1!dom2!xxx, ignore dom1! 1317 */ 1318 dom = s; 1319 for(; b = domBang(bang + 1); bang = b) 1320 dom = bang + 1; 1321 1322 /* 1323 * convert dom!mbox into mbox@dom 1324 */ 1325 *bang = '@'; 1326 strrev(dom, bang); 1327 strrev(bang+1, e); 1328 strrev(dom, e); 1329 bang = &dom[e - bang - 1]; 1330 if(dom > s){ 1331 bang -= dom - s; 1332 for(e = s; *e = *dom; e++) 1333 dom++; 1334 } 1335 1336 /* 1337 * eliminate a trailing '.' 1338 */ 1339 if(e[-1] == '.') 1340 e[-1] = '\0'; 1341 return bang; 1342 } 1343 headChar(1); 1344 1345 at = e; 1346 *e++ = '@'; 1347 *e = '\0'; 1348 if(!headDomain(e)) 1349 return nil; 1350 return at; 1351 } 1352 1353 /* 1354 * find the ! in domain!rest, where domain must have at least 1355 * one internal '.' 1356 */ 1357 static char* 1358 domBang(char *s) 1359 { 1360 int dot, c; 1361 1362 dot = 0; 1363 for(; c = *s; s++){ 1364 if(c == '!'){ 1365 if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0') 1366 return nil; 1367 return s; 1368 } 1369 if(c == '"') 1370 break; 1371 if(c == '.') 1372 dot++; 1373 } 1374 return nil; 1375 } 1376 1377 /* 1378 * domain : sub-domain 1379 * | domain '.' sub-domain 1380 * returns the end of the domain, or nil if a failure occured 1381 */ 1382 static char* 1383 headDomain(char *e) 1384 { 1385 char *w; 1386 1387 for(;;){ 1388 w = headSubDomain(); 1389 if(w == nil) 1390 return nil; 1391 strcpy(e, w); 1392 free(w); 1393 e = strchr(e, '\0'); 1394 lastWhite = headStr; 1395 if(headChar(0) != '.') 1396 break; 1397 headChar(1); 1398 *e++ = '.'; 1399 *e = '\0'; 1400 } 1401 return e; 1402 } 1403 1404 /* 1405 * id : 'content-id' ':' msg-id 1406 * msg-id : '<' addr-spec '>' 1407 */ 1408 static void 1409 mimeId(Header *h) 1410 { 1411 char *s, *e, *w; 1412 1413 if(headChar(1) != ':') 1414 return; 1415 if(headChar(1) != '<') 1416 return; 1417 1418 s = emalloc(strlen((char*)headStr) + 3); 1419 e = s; 1420 *e++ = '<'; 1421 e = headAddrSpec(e, nil); 1422 if(e == nil || headChar(1) != '>'){ 1423 free(s); 1424 return; 1425 } 1426 e = strchr(e, '\0'); 1427 *e++ = '>'; 1428 e[0] = '\0'; 1429 w = strdup(s); 1430 free(s); 1431 h->id = mkMimeHdr(w, nil, nil); 1432 } 1433 1434 /* 1435 * description : 'content-description' ':' *text 1436 */ 1437 static void 1438 mimeDescription(Header *h) 1439 { 1440 if(headChar(1) != ':') 1441 return; 1442 headSkipWhite(0); 1443 h->description = mkMimeHdr(headText(), nil, nil); 1444 } 1445 1446 /* 1447 * disposition : 'content-disposition' ':' token params 1448 */ 1449 static void 1450 mimeDisposition(Header *h) 1451 { 1452 char *s; 1453 1454 if(headChar(1) != ':') 1455 return; 1456 s = headAtom(mimeTokenStop); 1457 if(s == nil) 1458 return; 1459 h->disposition = mkMimeHdr(s, nil, mimeParams()); 1460 } 1461 1462 /* 1463 * md5 : 'content-md5' ':' token 1464 */ 1465 static void 1466 mimeMd5(Header *h) 1467 { 1468 char *s; 1469 1470 if(headChar(1) != ':') 1471 return; 1472 s = headAtom(mimeTokenStop); 1473 if(s == nil) 1474 return; 1475 h->md5 = mkMimeHdr(s, nil, nil); 1476 } 1477 1478 /* 1479 * language : 'content-language' ':' langs 1480 * langs : token 1481 * | langs commas token 1482 * commas : ',' 1483 * | commas ',' 1484 */ 1485 static void 1486 mimeLanguage(Header *h) 1487 { 1488 MimeHdr head, *last; 1489 char *s; 1490 1491 head.next = nil; 1492 last = &head; 1493 for(;;){ 1494 s = headAtom(mimeTokenStop); 1495 if(s == nil) 1496 break; 1497 last->next = mkMimeHdr(s, nil, nil); 1498 last = last->next; 1499 while(headChar(0) != ',') 1500 headChar(1); 1501 } 1502 h->language = head.next; 1503 } 1504 1505 /* 1506 * token : 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimeTokenStop> 1507 * atom : 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headAtomStop> 1508 * note this allows 8 bit characters, which occur in utf. 1509 */ 1510 static char* 1511 headAtom(char *disallowed) 1512 { 1513 char *s; 1514 int c, ns, as; 1515 1516 headSkipWhite(0); 1517 1518 s = emalloc(StrAlloc); 1519 as = StrAlloc; 1520 ns = 0; 1521 for(;;){ 1522 c = *headStr++; 1523 if(c <= ' ' || strchr(disallowed, c) != nil){ 1524 headStr--; 1525 break; 1526 } 1527 s[ns++] = c; 1528 if(ns >= as){ 1529 as += StrAlloc; 1530 s = erealloc(s, as); 1531 } 1532 } 1533 if(ns == 0){ 1534 free(s); 1535 return 0; 1536 } 1537 s[ns] = '\0'; 1538 return s; 1539 } 1540 1541 /* 1542 * sub-domain : atom | domain-lit 1543 */ 1544 static char * 1545 headSubDomain(void) 1546 { 1547 if(headChar(0) == '[') 1548 return headQuoted('[', ']'); 1549 return headAtom(headAtomStop); 1550 } 1551 1552 /* 1553 * word : atom | quoted-str 1554 */ 1555 static char * 1556 headWord(void) 1557 { 1558 if(headChar(0) == '"') 1559 return headQuoted('"', '"'); 1560 return headAtom(headAtomStop); 1561 } 1562 1563 /* 1564 * q is a quoted string. remove enclosing " and and \ escapes 1565 */ 1566 static void 1567 stripQuotes(char *q) 1568 { 1569 char *s; 1570 int c; 1571 1572 if(q == nil) 1573 return; 1574 s = q++; 1575 while(c = *q++){ 1576 if(c == '\\'){ 1577 c = *q++; 1578 if(!c) 1579 return; 1580 } 1581 *s++ = c; 1582 } 1583 s[-1] = '\0'; 1584 } 1585 1586 /* 1587 * quoted-str : '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"' 1588 * domain-lit : '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']' 1589 */ 1590 static char * 1591 headQuoted(int start, int stop) 1592 { 1593 char *s; 1594 int c, ns, as; 1595 1596 if(headChar(1) != start) 1597 return nil; 1598 s = emalloc(StrAlloc); 1599 as = StrAlloc; 1600 ns = 0; 1601 s[ns++] = start; 1602 for(;;){ 1603 c = *headStr; 1604 if(c == stop){ 1605 headStr++; 1606 break; 1607 } 1608 if(c == '\0'){ 1609 free(s); 1610 return nil; 1611 } 1612 if(c == '\r'){ 1613 headStr++; 1614 continue; 1615 } 1616 if(c == '\n'){ 1617 headStr++; 1618 while(*headStr == ' ' || *headStr == '\t' || *headStr == '\r' || *headStr == '\n') 1619 headStr++; 1620 c = ' '; 1621 }else if(c == '\\'){ 1622 headStr++; 1623 s[ns++] = c; 1624 c = *headStr; 1625 if(c == '\0'){ 1626 free(s); 1627 return nil; 1628 } 1629 headStr++; 1630 }else 1631 headStr++; 1632 s[ns++] = c; 1633 if(ns + 1 >= as){ /* leave room for \c or "0 */ 1634 as += StrAlloc; 1635 s = erealloc(s, as); 1636 } 1637 } 1638 s[ns++] = stop; 1639 s[ns] = '\0'; 1640 return s; 1641 } 1642 1643 /* 1644 * headText : contents of rest of header line 1645 */ 1646 static char * 1647 headText(void) 1648 { 1649 uchar *v; 1650 char *s; 1651 1652 v = headStr; 1653 headToEnd(); 1654 s = emalloc(headStr - v + 1); 1655 memmove(s, v, headStr - v); 1656 s[headStr - v] = '\0'; 1657 return s; 1658 } 1659 1660 /* 1661 * white space is ' ' '\t' or nested comments. 1662 * skip white space. 1663 * if com and a comment is seen, 1664 * return it's contents and stop processing white space. 1665 */ 1666 static char* 1667 headSkipWhite(int com) 1668 { 1669 char *s; 1670 int c, incom, as, ns; 1671 1672 s = nil; 1673 as = StrAlloc; 1674 ns = 0; 1675 if(com) 1676 s = emalloc(StrAlloc); 1677 incom = 0; 1678 for(; c = *headStr; headStr++){ 1679 switch(c){ 1680 case ' ': 1681 case '\t': 1682 case '\r': 1683 c = ' '; 1684 break; 1685 case '\n': 1686 c = headStr[1]; 1687 if(c != ' ' && c != '\t') 1688 goto breakout; 1689 c = ' '; 1690 break; 1691 case '\\': 1692 if(com && incom) 1693 s[ns++] = c; 1694 c = headStr[1]; 1695 if(c == '\0') 1696 goto breakout; 1697 headStr++; 1698 break; 1699 case '(': 1700 incom++; 1701 if(incom == 1) 1702 continue; 1703 break; 1704 case ')': 1705 incom--; 1706 if(com && !incom){ 1707 s[ns] = '\0'; 1708 return s; 1709 } 1710 break; 1711 default: 1712 if(!incom) 1713 goto breakout; 1714 break; 1715 } 1716 if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){ 1717 s[ns++] = c; 1718 if(ns + 1 >= as){ /* leave room for \c or 0 */ 1719 as += StrAlloc; 1720 s = erealloc(s, as); 1721 } 1722 } 1723 } 1724 breakout:; 1725 free(s); 1726 return nil; 1727 } 1728 1729 /* 1730 * return the next non-white character 1731 */ 1732 static int 1733 headChar(int eat) 1734 { 1735 int c; 1736 1737 headSkipWhite(0); 1738 c = *headStr; 1739 if(eat && c != '\0' && c != '\n') 1740 headStr++; 1741 return c; 1742 } 1743 1744 static void 1745 headToEnd(void) 1746 { 1747 uchar *s; 1748 int c; 1749 1750 for(;;){ 1751 s = headStr; 1752 c = *s++; 1753 while(c == '\r') 1754 c = *s++; 1755 if(c == '\n'){ 1756 c = *s++; 1757 if(c != ' ' && c != '\t') 1758 return; 1759 } 1760 if(c == '\0') 1761 return; 1762 headStr = s; 1763 } 1764 } 1765 1766 static void 1767 headSkip(void) 1768 { 1769 int c; 1770 1771 while(c = *headStr){ 1772 headStr++; 1773 if(c == '\n'){ 1774 c = *headStr; 1775 if(c == ' ' || c == '\t') 1776 continue; 1777 return; 1778 } 1779 } 1780 } 1781