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