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