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