imap.c (32794B)
1 /* 2 * Locking here is not quite right. 3 * Calling qlock(&z->lk) can block the proc, 4 * and when it comes back, boxes and msgs might have been freed 5 * (if the refresh proc was holding the lock and in the middle of a 6 * redial). I've tried to be careful about not assuming boxes continue 7 * to exist across imap commands, but maybe this isn't really tenable. 8 * Maybe instead we should ref count the boxes and messages. 9 */ 10 11 #include "a.h" 12 #include <libsec.h> 13 14 struct Imap 15 { 16 int connected; 17 int autoreconnect; 18 int ticks; /* until boom! */ 19 char* server; 20 char* root; 21 char* user; 22 int mode; 23 int fd; 24 Biobuf b; 25 Ioproc* io; 26 QLock lk; 27 QLock rlk; 28 Rendez r; 29 30 Box* inbox; 31 Box* box; 32 Box* nextbox; 33 34 /* SEARCH results */ 35 uint *uid; 36 uint nuid; 37 38 uint reply; 39 }; 40 41 static struct { 42 char *name; 43 int flag; 44 } flagstab[] = 45 { 46 "Junk", FlagJunk, 47 "NonJunk", FlagNonJunk, 48 "\\Answered", FlagReplied, 49 "\\Flagged", FlagFlagged, 50 "\\Deleted", FlagDeleted, 51 "\\Draft", FlagDraft, 52 "\\Recent", FlagRecent, 53 "\\Seen", FlagSeen, 54 "\\NoInferiors", FlagNoInferiors, 55 "\\NoSelect", FlagNoSelect, 56 "\\Marked", FlagMarked, 57 "\\UnMarked", FlagUnMarked 58 }; 59 60 int chattyimap; 61 62 static char *tag = "#"; 63 64 static void checkbox(Imap*, Box*); 65 static char* copyaddrs(Sx*); 66 static void freeup(UserPasswd*); 67 static int getbox(Imap*, Box*); 68 static int getboxes(Imap*); 69 static char* gsub(char*, char*, char*); 70 static int imapcmd(Imap*, Box*, char*, ...); 71 static Sx* imapcmdsxlit(Imap*, Box*, char*, ...); 72 static Sx* imapcmdsx(Imap*, Box*, char*, ...); 73 static Sx* imapcmdsx0(Imap*, char*, ...); 74 static Sx* imapvcmdsx(Imap*, Box*, char*, va_list, int); 75 static Sx* imapvcmdsx0(Imap*, char*, va_list, int); 76 static int imapdial(char*, int); 77 static int imaplogin(Imap*); 78 static int imapquote(Fmt*); 79 static int imapreconnect(Imap*); 80 static void imaprefreshthread(void*); 81 static void imaptimerproc(void*); 82 static Sx* imapwaitsx(Imap*); 83 static int isatom(Sx *v, char *name); 84 static int islist(Sx *v); 85 static int isnil(Sx *v); 86 static int isnumber(Sx *sx); 87 static int isstring(Sx *sx); 88 static int ioimapdial(Ioproc*, char*, int); 89 static char* nstring(Sx*); 90 static void unexpected(Imap*, Sx*); 91 static Sx* zBrdsx(Imap*); 92 93 /* 94 * Imap connection maintenance and login. 95 */ 96 97 Imap* 98 imapconnect(char *server, int mode, char *root, char *user) 99 { 100 Imap *z; 101 102 fmtinstall('H', encodefmt); 103 fmtinstall('Z', imapquote); 104 105 z = emalloc(sizeof *z); 106 z->server = estrdup(server); 107 z->mode = mode; 108 z->user = user; 109 if(root) 110 if(root[0] != 0 && root[strlen(root)-1] != '/') 111 z->root = smprint("%s/", root); 112 else 113 z->root = root; 114 else 115 z->root = ""; 116 z->fd = -1; 117 z->autoreconnect = 0; 118 z->io = ioproc(); 119 120 qlock(&z->lk); 121 if(imapreconnect(z) < 0){ 122 free(z); 123 return nil; 124 } 125 126 z->r.l = &z->rlk; 127 z->autoreconnect = 1; 128 qunlock(&z->lk); 129 130 proccreate(imaptimerproc, z, STACK); 131 mailthread(imaprefreshthread, z); 132 133 return z; 134 } 135 136 void 137 imaphangup(Imap *z, int ticks) 138 { 139 z->ticks = ticks; 140 if(ticks == 0){ 141 close(z->fd); 142 z->fd = -1; 143 } 144 } 145 146 static int 147 imapreconnect(Imap *z) 148 { 149 Sx *sx; 150 151 z->autoreconnect = 0; 152 z->box = nil; 153 z->inbox = nil; 154 155 if(z->fd >= 0){ 156 close(z->fd); 157 z->fd = -1; 158 } 159 160 if(chattyimap) 161 fprint(2, "dial %s...\n", z->server); 162 if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0) 163 return -1; 164 z->connected = 1; 165 Binit(&z->b, z->fd, OREAD); 166 if((sx = zBrdsx(z)) == nil){ 167 werrstr("no greeting"); 168 goto err; 169 } 170 if(chattyimap) 171 fprint(2, "<I %#$\n", sx); 172 if(sx->nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){ 173 freesx(sx); 174 goto preauth; 175 } 176 if(!oksx(sx)){ 177 werrstr("bad greeting - %#$", sx); 178 goto err; 179 } 180 freesx(sx); 181 sx = nil; 182 if(imaplogin(z) < 0) 183 goto err; 184 preauth: 185 if(getboxes(z) < 0 || getbox(z, z->inbox) < 0) 186 goto err; 187 z->autoreconnect = 1; 188 return 0; 189 190 err: 191 if(z->fd >= 0){ 192 close(z->fd); 193 z->fd = -1; 194 } 195 if(sx) 196 freesx(sx); 197 z->autoreconnect = 1; 198 z->connected = 0; 199 return -1; 200 } 201 202 static int 203 imaplogin(Imap *z) 204 { 205 Sx *sx; 206 UserPasswd *up; 207 208 if(z->user != nil) 209 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q user=%q", z->server, z->user); 210 else 211 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server); 212 if(up == nil){ 213 werrstr("getuserpasswd - %r"); 214 return -1; 215 } 216 217 sx = imapcmdsx(z, nil, "LOGIN %#Z %#Z", up->user, up->passwd); 218 freeup(up); 219 if(sx == nil) 220 return -1; 221 if(!oksx(sx)){ 222 freesx(sx); 223 werrstr("login rejected - %#$", sx); 224 return -1; 225 } 226 return 0; 227 } 228 229 static int 230 getboxes(Imap *z) 231 { 232 int i; 233 Box **r, **w, **e; 234 235 for(i=0; i<nboxes; i++){ 236 boxes[i]->mark = 1; 237 boxes[i]->exists = 0; 238 boxes[i]->maxseen = 0; 239 } 240 if(imapcmd(z, nil, "LIST %Z *", z->root) < 0) 241 return -1; 242 if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0) 243 return -1; 244 if(z->nextbox && z->nextbox->mark) 245 z->nextbox = nil; 246 for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){ 247 if((*r)->mark) 248 {fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname); 249 boxfree(*r); 250 } 251 else 252 *w++ = *r; 253 } 254 nboxes = w - boxes; 255 return 0; 256 } 257 258 static int 259 getbox(Imap *z, Box *b) 260 { 261 int i; 262 Msg **r, **w, **e; 263 264 if(b == nil) 265 return 0; 266 267 for(i=0; i<b->nmsg; i++) 268 b->msg[i]->imapid = 0; 269 if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0) 270 return -1; 271 for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; r<e; r++){ 272 if((*r)->imapid == 0) 273 msgfree(*r); 274 else{ 275 (*r)->ix = w-b->msg; 276 *w++ = *r; 277 } 278 } 279 b->nmsg = w - b->msg; 280 b->imapinit = 1; 281 checkbox(z, b); 282 return 0; 283 } 284 285 static void 286 freeup(UserPasswd *up) 287 { 288 memset(up->user, 0, strlen(up->user)); 289 memset(up->passwd, 0, strlen(up->passwd)); 290 free(up); 291 } 292 293 static void 294 imaptimerproc(void *v) 295 { 296 Imap *z; 297 298 z = v; 299 for(;;){ 300 sleep(60*1000); 301 qlock(z->r.l); 302 rwakeup(&z->r); 303 qunlock(z->r.l); 304 } 305 } 306 307 static void 308 checkbox(Imap *z, Box *b) 309 { 310 if(imapcmd(z, b, "NOOP") >= 0){ 311 if(!b->imapinit) 312 getbox(z, b); 313 if(!b->imapinit) 314 return; 315 if(b==z->box && b->exists > b->maxseen){ 316 imapcmd(z, b, "UID FETCH %d:* FULL", 317 b->uidnext); 318 } 319 } 320 } 321 322 static void 323 imaprefreshthread(void *v) 324 { 325 Imap *z; 326 327 z = v; 328 for(;;){ 329 qlock(z->r.l); 330 rsleep(&z->r); 331 qunlock(z->r.l); 332 333 qlock(&z->lk); 334 if(z->inbox) 335 checkbox(z, z->inbox); 336 qunlock(&z->lk); 337 } 338 } 339 340 /* 341 * Run a single command and return the Sx. Does NOT redial. 342 */ 343 static Sx* 344 imapvcmdsx0(Imap *z, char *fmt, va_list arg, int dotag) 345 { 346 char *s; 347 Fmt f; 348 int len; 349 Sx *sx; 350 351 if(canqlock(&z->lk)) 352 abort(); 353 354 if(z->fd < 0 || !z->connected) 355 return nil; 356 357 fmtstrinit(&f); 358 if(dotag) 359 fmtprint(&f, "%s ", tag); 360 fmtvprint(&f, fmt, arg); 361 fmtprint(&f, "\r\n"); 362 s = fmtstrflush(&f); 363 len = strlen(s); 364 s[len-2] = 0; 365 if(chattyimap) 366 fprint(2, "I> %s\n", s); 367 s[len-2] = '\r'; 368 if(iowrite(z->io, z->fd, s, len) < 0){ 369 z->connected = 0; 370 free(s); 371 return nil; 372 } 373 sx = imapwaitsx(z); 374 free(s); 375 return sx; 376 } 377 378 static Sx* 379 imapcmdsx0(Imap *z, char *fmt, ...) 380 { 381 va_list arg; 382 Sx *sx; 383 384 va_start(arg, fmt); 385 sx = imapvcmdsx0(z, fmt, arg, 1); 386 va_end(arg); 387 return sx; 388 } 389 390 /* 391 * Run a single command on box b. Does redial. 392 */ 393 static Sx* 394 imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg, int dotag) 395 { 396 int tries; 397 Sx *sx; 398 399 tries = 0; 400 z->nextbox = b; 401 402 if(z->fd < 0 || !z->connected){ 403 reconnect: 404 if(!z->autoreconnect) 405 return nil; 406 if(imapreconnect(z) < 0) 407 return nil; 408 if(b && z->nextbox == nil) /* box disappeared on reconnect */ 409 return nil; 410 } 411 412 if(b && b != z->box){ 413 if(z->box) 414 z->box->imapinit = 0; 415 z->box = b; 416 if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){ 417 z->box = nil; 418 if(tries++ == 0 && (z->fd < 0 || !z->connected)) 419 goto reconnect; 420 return nil; 421 } 422 freesx(sx); 423 } 424 425 if((sx=imapvcmdsx0(z, fmt, arg, dotag)) == nil){ 426 if(tries++ == 0 && (z->fd < 0 || !z->connected)) 427 goto reconnect; 428 return nil; 429 } 430 return sx; 431 } 432 433 static int 434 imapcmd(Imap *z, Box *b, char *fmt, ...) 435 { 436 Sx *sx; 437 va_list arg; 438 439 va_start(arg, fmt); 440 sx = imapvcmdsx(z, b, fmt, arg, 1); 441 va_end(arg); 442 if(sx == nil) 443 return -1; 444 if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){ 445 werrstr("%$", sx); 446 freesx(sx); 447 return -1; 448 } 449 freesx(sx); 450 return 0; 451 } 452 453 static Sx* 454 imapcmdsx(Imap *z, Box *b, char *fmt, ...) 455 { 456 Sx *sx; 457 va_list arg; 458 459 va_start(arg, fmt); 460 sx = imapvcmdsx(z, b, fmt, arg, 1); 461 va_end(arg); 462 return sx; 463 } 464 465 static Sx* 466 imapcmdsxlit(Imap *z, Box *b, char *fmt, ...) 467 { 468 Sx *sx; 469 va_list arg; 470 471 va_start(arg, fmt); 472 sx = imapvcmdsx(z, b, fmt, arg, 0); 473 va_end(arg); 474 return sx; 475 } 476 477 static Sx* 478 imapwaitsx(Imap *z) 479 { 480 Sx *sx; 481 482 while((sx = zBrdsx(z)) != nil){ 483 if(chattyimap) 484 fprint(2, "<| %#$\n", sx); 485 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0) 486 return sx; 487 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, "+") == 0){ 488 z->reply = 1; 489 return sx; 490 } 491 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0) 492 unexpected(z, sx); 493 if(sx->type == SxList && sx->nsx == 0){ 494 freesx(sx); 495 break; 496 } 497 freesx(sx); 498 } 499 z->connected = 0; 500 return nil; 501 } 502 503 /* 504 * Imap interface to mail file system. 505 */ 506 507 static void 508 _bodyname(char *buf, char *ebuf, Part *p, char *extra) 509 { 510 if(buf >= ebuf){ 511 fprint(2, "***** BUFFER TOO SMALL\n"); 512 return; 513 } 514 *buf = 0; 515 if(p->parent){ 516 _bodyname(buf, ebuf, p->parent, ""); 517 buf += strlen(buf); 518 seprint(buf, ebuf, ".%d", p->pix+1); 519 } 520 buf += strlen(buf); 521 seprint(buf, ebuf, "%s", extra); 522 } 523 524 static char* 525 bodyname(Part *p, char *extra) 526 { 527 static char buf[256]; 528 memset(buf, 0, sizeof buf); /* can't see why this is necessary, but it is */ 529 _bodyname(buf, buf+sizeof buf, p, extra); 530 return buf+1; /* buf[0] == '.' */ 531 } 532 533 static void 534 fetch1(Imap *z, Part *p, char *s) 535 { 536 qlock(&z->lk); 537 imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]", 538 p->msg->imapuid, bodyname(p, s)); 539 qunlock(&z->lk); 540 } 541 542 void 543 imapfetchrawheader(Imap *z, Part *p) 544 { 545 fetch1(z, p, ".HEADER"); 546 } 547 548 void 549 imapfetchrawmime(Imap *z, Part *p) 550 { 551 fetch1(z, p, ".MIME"); 552 } 553 554 void 555 imapfetchrawbody(Imap *z, Part *p) 556 { 557 fetch1(z, p, ".TEXT"); 558 } 559 560 void 561 imapfetchraw(Imap *z, Part *p) 562 { 563 fetch1(z, p, ""); 564 } 565 566 static int 567 imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after) 568 { 569 int i, r; 570 char *cmd; 571 Fmt fmt; 572 573 if(nm == 0) 574 return 0; 575 576 fmtstrinit(&fmt); 577 fmtprint(&fmt, "%s ", before); 578 for(i=0; i<nm; i++){ 579 if(i > 0) 580 fmtrune(&fmt, ','); 581 fmtprint(&fmt, "%ud", m[i]->imapuid); 582 } 583 fmtprint(&fmt, " %s", after); 584 cmd = fmtstrflush(&fmt); 585 586 r = 0; 587 if(imapcmd(z, box, "%s", cmd) < 0) 588 r = -1; 589 free(cmd); 590 return r; 591 } 592 593 int 594 imapcopylist(Imap *z, char *nbox, Msg **m, uint nm) 595 { 596 int rv; 597 char *name, *p; 598 599 if(nm == 0) 600 return 0; 601 602 qlock(&z->lk); 603 if(strcmp(nbox, "mbox") == 0) 604 name = estrdup("INBOX"); 605 else{ 606 p = esmprint("%s%s", z->root, nbox); 607 name = esmprint("%Z", p); 608 free(p); 609 } 610 rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name); 611 free(name); 612 qunlock(&z->lk); 613 return rv; 614 } 615 616 int 617 imapremovelist(Imap *z, Msg **m, uint nm) 618 { 619 int rv; 620 621 if(nm == 0) 622 return 0; 623 624 qlock(&z->lk); 625 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)"); 626 /* careful - box might be gone; use z->box instead */ 627 if(rv == 0 && z->box) 628 rv = imapcmd(z, z->box, "EXPUNGE"); 629 qunlock(&z->lk); 630 return rv; 631 } 632 633 int 634 imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm) 635 { 636 char *mod, *s, *sep; 637 int i, rv; 638 Fmt fmt; 639 640 if(op > 0) 641 mod = "+"; 642 else if(op == 0) 643 mod = ""; 644 else 645 mod = "-"; 646 647 fmtstrinit(&fmt); 648 fmtprint(&fmt, "%sFLAGS (", mod); 649 sep = ""; 650 for(i=0; i<nelem(flagstab); i++){ 651 if(flagstab[i].flag & flag){ 652 fmtprint(&fmt, "%s%s", sep, flagstab[i].name); 653 sep = " "; 654 } 655 } 656 fmtprint(&fmt, ")"); 657 s = fmtstrflush(&fmt); 658 659 qlock(&z->lk); 660 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s); 661 qunlock(&z->lk); 662 free(s); 663 return rv; 664 } 665 666 int 667 imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm) 668 { 669 uint *uid; 670 int i, nuid; 671 Msg **m; 672 int nm; 673 Sx *sx; 674 675 qlock(&z->lk); 676 sx = imapcmdsx(z, b, "UID SEARCH CHARSET UTF-8 TEXT {%d}", strlen(search)); 677 freesx(sx); 678 if(!z->reply){ 679 qunlock(&z->lk); 680 return -1; 681 } 682 if((sx = imapcmdsxlit(z, b, "%s", search)) == nil){ 683 qunlock(&z->lk); 684 return -1; 685 } 686 if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){ 687 werrstr("%$", sx); 688 freesx(sx); 689 qunlock(&z->lk); 690 return -1; 691 } 692 freesx(sx); 693 694 uid = z->uid; 695 nuid = z->nuid; 696 z->uid = nil; 697 z->nuid = 0; 698 z->reply = 0; 699 qunlock(&z->lk); 700 701 m = emalloc(nuid*sizeof m[0]); 702 nm = 0; 703 for(i=0; i<nuid; i++) 704 if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil) 705 nm++; 706 *mm = m; 707 free(uid); 708 return nm; 709 } 710 711 void 712 imapcheckbox(Imap *z, Box *b) 713 { 714 if(b == nil) 715 return; 716 qlock(&z->lk); 717 checkbox(z, b); 718 qunlock(&z->lk); 719 } 720 721 /* 722 * Imap utility routines 723 */ 724 static long 725 _ioimapdial(va_list *arg) 726 { 727 char *server; 728 int mode; 729 730 server = va_arg(*arg, char*); 731 mode = va_arg(*arg, int); 732 return imapdial(server, mode); 733 } 734 static int 735 ioimapdial(Ioproc *io, char *server, int mode) 736 { 737 return iocall(io, _ioimapdial, server, mode); 738 } 739 740 static long 741 _ioBrdsx(va_list *arg) 742 { 743 Biobuf *b; 744 Sx **sx; 745 746 b = va_arg(*arg, Biobuf*); 747 sx = va_arg(*arg, Sx**); 748 *sx = Brdsx(b); 749 if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){ 750 freesx(*sx); 751 *sx = nil; 752 } 753 return 0; 754 } 755 static Sx* 756 ioBrdsx(Ioproc *io, Biobuf *b) 757 { 758 Sx *sx; 759 760 iocall(io, _ioBrdsx, b, &sx); 761 return sx; 762 } 763 764 static Sx* 765 zBrdsx(Imap *z) 766 { 767 if(z->ticks && --z->ticks==0){ 768 close(z->fd); 769 z->fd = -1; 770 return nil; 771 } 772 return ioBrdsx(z->io, &z->b); 773 } 774 775 static int 776 imapdial(char *server, int mode) 777 { 778 int p[2]; 779 int fd[3]; 780 char *tmp; 781 char *fpath; 782 783 switch(mode){ 784 default: 785 case Unencrypted: 786 return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil); 787 788 case Starttls: 789 werrstr("starttls not supported"); 790 return -1; 791 792 case Tls: 793 if(pipe(p) < 0) 794 return -1; 795 fd[0] = dup(p[0], -1); 796 fd[1] = dup(p[0], -1); 797 fd[2] = dup(2, -1); 798 #ifdef PLAN9PORT 799 tmp = esmprint("%s:993", server); 800 fpath = searchpath("stunnel3"); 801 if (!fpath) { 802 werrstr("stunnel not found. it is required for tls support."); 803 return -1; 804 } 805 if(threadspawnl(fd, fpath, "stunnel", "-c", "-r", tmp, nil) < 0) { 806 #else 807 tmp = esmprint("tcp!%s!993", server); 808 if(threadspawnl(fd, "/bin/tlsclient", "tlsclient", tmp, nil) < 0){ 809 #endif 810 free(tmp); 811 close(p[0]); 812 close(p[1]); 813 close(fd[0]); 814 close(fd[1]); 815 close(fd[2]); 816 return -1; 817 } 818 free(tmp); 819 close(p[0]); 820 return p[1]; 821 822 case Cmd: 823 if(pipe(p) < 0) 824 return -1; 825 fd[0] = dup(p[0], -1); 826 fd[1] = dup(p[0], -1); 827 fd[2] = dup(2, -1); 828 /* could do better - use get9root for rc(1) path */ 829 if(threadspawnl(fd, PLAN9_TARGET "/bin/rc", "rc", "-c", server, nil) < 0){ 830 close(p[0]); 831 close(p[1]); 832 close(fd[0]); 833 close(fd[1]); 834 close(fd[2]); 835 return -1; 836 } 837 close(p[0]); 838 return p[1]; 839 } 840 } 841 842 enum 843 { 844 Qok = 0, 845 Qquote, 846 Qbackslash 847 }; 848 849 static int 850 needtoquote(Rune r) 851 { 852 if(r >= Runeself) 853 return Qquote; 854 if(r <= ' ') 855 return Qquote; 856 if(r=='\\' || r=='"') 857 return Qbackslash; 858 return Qok; 859 } 860 861 static int 862 imapquote(Fmt *f) 863 { 864 char *s, *t; 865 int w, quotes; 866 Rune r; 867 868 s = va_arg(f->args, char*); 869 if(s == nil || *s == '\0') 870 return fmtstrcpy(f, "\"\""); 871 872 quotes = 0; 873 if(f->flags&FmtSharp) 874 quotes = 1; 875 for(t=s; *t; t+=w){ 876 w = chartorune(&r, t); 877 quotes |= needtoquote(r); 878 } 879 if(quotes == 0) 880 return fmtstrcpy(f, s); 881 882 fmtrune(f, '"'); 883 for(t=s; *t; t+=w){ 884 w = chartorune(&r, t); 885 if(needtoquote(r) == Qbackslash) 886 fmtrune(f, '\\'); 887 fmtrune(f, r); 888 } 889 return fmtrune(f, '"'); 890 } 891 892 static int 893 fmttype(char c) 894 { 895 switch(c){ 896 case 'A': 897 return SxAtom; 898 case 'L': 899 return SxList; 900 case 'N': 901 return SxNumber; 902 case 'S': 903 return SxString; 904 default: 905 return -1; 906 } 907 } 908 909 /* 910 * Check S expression against format string. 911 */ 912 static int 913 sxmatch(Sx *sx, char *fmt) 914 { 915 int i; 916 917 for(i=0; fmt[i]; i++){ 918 if(fmt[i] == '*') 919 fmt--; /* like i-- but better */ 920 if(i == sx->nsx && fmt[i+1] == '*') 921 return 1; 922 if(i >= sx->nsx) 923 return 0; 924 if(sx->sx[i] == nil) 925 return 0; 926 if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){ 927 if(fmt[i] == 'L'){ 928 free(sx->sx[i]->data); 929 sx->sx[i]->data = nil; 930 sx->sx[i]->type = SxList; 931 sx->sx[i]->sx = nil; 932 sx->sx[i]->nsx = 0; 933 } 934 else if(fmt[i] == 'S'){ 935 free(sx->sx[i]->data); 936 sx->sx[i]->data = nil; 937 sx->sx[i]->type = SxString; 938 } 939 } 940 if(sx->sx[i]->type == SxAtom && fmt[i]=='S') 941 sx->sx[i]->type = SxString; 942 if(sx->sx[i]->type != fmttype(fmt[i])){ 943 fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]); 944 return 0; 945 } 946 } 947 if(i != sx->nsx) 948 return 0; 949 return 1; 950 } 951 952 /* 953 * Check string against format string. 954 */ 955 static int 956 stringmatch(char *fmt, char *s) 957 { 958 for(; *fmt && *s; fmt++, s++){ 959 switch(*fmt){ 960 case '0': 961 if(*s == ' ') 962 break; 963 /* fall through */ 964 case '1': 965 if(*s < '0' || *s > '9') 966 return 0; 967 break; 968 case 'A': 969 if(*s < 'A' || *s > 'Z') 970 return 0; 971 break; 972 case 'a': 973 if(*s < 'a' || *s > 'z') 974 return 0; 975 break; 976 case '+': 977 if(*s != '-' && *s != '+') 978 return 0; 979 break; 980 default: 981 if(*s != *fmt) 982 return 0; 983 break; 984 } 985 } 986 if(*fmt || *s) 987 return 0; 988 return 1; 989 } 990 991 /* 992 * Parse simple S expressions and IMAP elements. 993 */ 994 static int 995 isatom(Sx *v, char *name) 996 { 997 int n; 998 999 if(v == nil || v->type != SxAtom) 1000 return 0; 1001 n = strlen(name); 1002 if(cistrncmp(v->data, name, n) == 0) 1003 if(v->data[n] == 0 || (n>0 && v->data[n-1] == '[')) 1004 return 1; 1005 return 0; 1006 } 1007 1008 static int 1009 isstring(Sx *sx) 1010 { 1011 if(sx->type == SxAtom) 1012 sx->type = SxString; 1013 return sx->type == SxString; 1014 } 1015 1016 static int 1017 isnumber(Sx *sx) 1018 { 1019 return sx->type == SxNumber; 1020 } 1021 1022 static int 1023 isnil(Sx *v) 1024 { 1025 return v == nil || 1026 (v->type==SxList && v->nsx == 0) || 1027 (v->type==SxAtom && strcmp(v->data, "NIL") == 0); 1028 } 1029 1030 static int 1031 islist(Sx *v) 1032 { 1033 return isnil(v) || v->type==SxList; 1034 } 1035 1036 static uint 1037 parseflags(Sx *v) 1038 { 1039 int f, i, j; 1040 1041 if(v->type != SxList){ 1042 warn("malformed flags: %$", v); 1043 return 0; 1044 } 1045 f = 0; 1046 for(i=0; i<v->nsx; i++){ 1047 if(v->sx[i]->type != SxAtom) 1048 continue; 1049 for(j=0; j<nelem(flagstab); j++) 1050 if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0) 1051 f |= flagstab[j].flag; 1052 } 1053 return f; 1054 } 1055 1056 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; 1057 static int 1058 parsemon(char *s) 1059 { 1060 int i; 1061 1062 for(i=0; months[i]; i+=3) 1063 if(memcmp(s, months+i, 3) == 0) 1064 return i/3; 1065 return -1; 1066 } 1067 1068 static uint 1069 parsedate(Sx *v) 1070 { 1071 Tm tm; 1072 uint t; 1073 int delta; 1074 char *p; 1075 1076 if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){ 1077 bad: 1078 warn("bad date: %$", v); 1079 return 0; 1080 } 1081 1082 /* cannot use atoi because 09 is malformed octal! */ 1083 memset(&tm, 0, sizeof tm); 1084 p = v->data; 1085 tm.mday = strtol(p, 0, 10); 1086 tm.mon = parsemon(p+3); 1087 if(tm.mon == -1) 1088 goto bad; 1089 tm.year = strtol(p+7, 0, 10) - 1900; 1090 tm.hour = strtol(p+12, 0, 10); 1091 tm.min = strtol(p+15, 0, 10); 1092 tm.sec = strtol(p+18, 0, 10); 1093 strcpy(tm.zone, "GMT"); 1094 1095 t = tm2sec(&tm); 1096 delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60; 1097 if(p[21] == '-') 1098 delta = -delta; 1099 1100 t -= delta; 1101 return t; 1102 } 1103 1104 static uint 1105 parsenumber(Sx *v) 1106 { 1107 if(v->type != SxNumber) 1108 return 0; 1109 return v->number; 1110 } 1111 1112 static void 1113 hash(DigestState *ds, char *tag, char *val) 1114 { 1115 if(val == nil) 1116 val = ""; 1117 md5((uchar*)tag, strlen(tag)+1, nil, ds); 1118 md5((uchar*)val, strlen(val)+1, nil, ds); 1119 } 1120 1121 static Hdr* 1122 parseenvelope(Sx *v) 1123 { 1124 Hdr *hdr; 1125 uchar digest[16]; 1126 DigestState ds; 1127 1128 if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){ 1129 warn("bad envelope: %$", v); 1130 return nil; 1131 } 1132 1133 hdr = emalloc(sizeof *hdr); 1134 hdr->date = nstring(v->sx[0]); 1135 hdr->subject = unrfc2047(nstring(v->sx[1])); 1136 hdr->from = copyaddrs(v->sx[2]); 1137 hdr->sender = copyaddrs(v->sx[3]); 1138 hdr->replyto = copyaddrs(v->sx[4]); 1139 hdr->to = copyaddrs(v->sx[5]); 1140 hdr->cc = copyaddrs(v->sx[6]); 1141 hdr->bcc = copyaddrs(v->sx[7]); 1142 hdr->inreplyto = unrfc2047(nstring(v->sx[8])); 1143 hdr->messageid = unrfc2047(nstring(v->sx[9])); 1144 1145 memset(&ds, 0, sizeof ds); 1146 hash(&ds, "date", hdr->date); 1147 hash(&ds, "subject", hdr->subject); 1148 hash(&ds, "from", hdr->from); 1149 hash(&ds, "sender", hdr->sender); 1150 hash(&ds, "replyto", hdr->replyto); 1151 hash(&ds, "to", hdr->to); 1152 hash(&ds, "cc", hdr->cc); 1153 hash(&ds, "bcc", hdr->bcc); 1154 hash(&ds, "inreplyto", hdr->inreplyto); 1155 hash(&ds, "messageid", hdr->messageid); 1156 md5(0, 0, digest, &ds); 1157 hdr->digest = esmprint("%.16H", digest); 1158 1159 return hdr; 1160 } 1161 1162 static void 1163 strlwr(char *s) 1164 { 1165 char *t; 1166 1167 if(s == nil) 1168 return; 1169 for(t=s; *t; t++) 1170 if('A' <= *t && *t <= 'Z') 1171 *t += 'a' - 'A'; 1172 } 1173 1174 static void 1175 nocr(char *s) 1176 { 1177 char *r, *w; 1178 1179 if(s == nil) 1180 return; 1181 for(r=w=s; *r; r++) 1182 if(*r != '\r') 1183 *w++ = *r; 1184 *w = 0; 1185 } 1186 1187 /* 1188 * substitute all occurrences of a with b in s. 1189 */ 1190 static char* 1191 gsub(char *s, char *a, char *b) 1192 { 1193 char *p, *t, *w, *last; 1194 int n; 1195 1196 n = 0; 1197 for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a)) 1198 n++; 1199 if(n == 0) 1200 return s; 1201 t = emalloc(strlen(s)+n*strlen(b)+1); 1202 w = t; 1203 for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){ 1204 memmove(w, last, p-last); 1205 w += p-last; 1206 memmove(w, b, strlen(b)); 1207 w += strlen(b); 1208 } 1209 strcpy(w, last); 1210 free(s); 1211 return t; 1212 } 1213 1214 /* 1215 * Table-driven IMAP "unexpected response" parser. 1216 * All the interesting data is in the unexpected responses. 1217 */ 1218 static void xlist(Imap*, Sx*); 1219 static void xrecent(Imap*, Sx*); 1220 static void xexists(Imap*, Sx*); 1221 static void xok(Imap*, Sx*); 1222 static void xflags(Imap*, Sx*); 1223 static void xfetch(Imap*, Sx*); 1224 static void xexpunge(Imap*, Sx*); 1225 static void xbye(Imap*, Sx*); 1226 static void xsearch(Imap*, Sx*); 1227 1228 static struct { 1229 int num; 1230 char *name; 1231 char *fmt; 1232 void (*fn)(Imap*, Sx*); 1233 } unextab[] = { 1234 0, "BYE", nil, xbye, 1235 0, "FLAGS", "AAL", xflags, 1236 0, "LIST", "AALSS", xlist, 1237 0, "OK", nil, xok, 1238 0, "SEARCH", "AAN*", xsearch, 1239 1240 1, "EXISTS", "ANA", xexists, 1241 1, "EXPUNGE", "ANA", xexpunge, 1242 1, "FETCH", "ANAL", xfetch, 1243 1, "RECENT", "ANA", xrecent 1244 }; 1245 1246 static void 1247 unexpected(Imap *z, Sx *sx) 1248 { 1249 int i, num; 1250 char *name; 1251 1252 if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){ 1253 num = 1; 1254 name = sx->sx[2]->data; 1255 }else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){ 1256 num = 0; 1257 name = sx->sx[1]->data; 1258 }else 1259 return; 1260 1261 for(i=0; i<nelem(unextab); i++){ 1262 if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){ 1263 if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){ 1264 warn("malformed %s: %$", name, sx); 1265 continue; 1266 } 1267 unextab[i].fn(z, sx); 1268 } 1269 } 1270 } 1271 1272 static int 1273 alldollars(char *s) 1274 { 1275 for(; *s; s++) 1276 if(*s != '$') 1277 return 0; 1278 return 1; 1279 } 1280 1281 static void 1282 xlist(Imap *z, Sx *sx) 1283 { 1284 int inbox; 1285 char *s, *t; 1286 Box *box; 1287 1288 s = estrdup(sx->sx[4]->data); 1289 if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){ 1290 s = gsub(s, "/", "_"); 1291 s = gsub(s, sx->sx[3]->data, "/"); 1292 } 1293 1294 /* 1295 * INBOX is the special imap name for the main mailbox. 1296 * All other mailbox names have the root prefix removed, if applicable. 1297 */ 1298 inbox = 0; 1299 if(cistrcmp(s, "INBOX") == 0){ 1300 inbox = 1; 1301 free(s); 1302 s = estrdup("mbox"); 1303 } else if(z->root && strstr(s, z->root) == s) { 1304 t = estrdup(s+strlen(z->root)); 1305 free(s); 1306 s = t; 1307 } 1308 1309 /* 1310 * Plan 9 calls the main mailbox mbox. 1311 * Rename any existing mbox by appending a $. 1312 */ 1313 if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){ 1314 t = emalloc(strlen(s)+2); 1315 strcpy(t, s); 1316 strcat(t, "$"); 1317 free(s); 1318 s = t; 1319 } 1320 1321 box = boxcreate(s); 1322 if(box == nil) 1323 return; 1324 box->imapname = estrdup(sx->sx[4]->data); 1325 if(inbox) 1326 z->inbox = box; 1327 box->mark = 0; 1328 box->flags = parseflags(sx->sx[2]); 1329 } 1330 1331 static void 1332 xrecent(Imap *z, Sx *sx) 1333 { 1334 if(z->box) 1335 z->box->recent = sx->sx[1]->number; 1336 } 1337 1338 static void 1339 xexists(Imap *z, Sx *sx) 1340 { 1341 if(z->box){ 1342 z->box->exists = sx->sx[1]->number; 1343 if(z->box->exists < z->box->maxseen) 1344 z->box->maxseen = z->box->exists; 1345 } 1346 } 1347 1348 static void 1349 xflags(Imap *z, Sx *sx) 1350 { 1351 USED(z); 1352 USED(sx); 1353 /* 1354 * This response contains in sx->sx[2] the list of flags 1355 * that can be validly attached to messages in z->box. 1356 * We don't have any use for this list, since we 1357 * use only the standard flags. 1358 */ 1359 } 1360 1361 static void 1362 xbye(Imap *z, Sx *sx) 1363 { 1364 USED(sx); 1365 close(z->fd); 1366 z->fd = -1; 1367 z->connected = 0; 1368 } 1369 1370 static void 1371 xexpunge(Imap *z, Sx *sx) 1372 { 1373 int i, n; 1374 Box *b; 1375 1376 if((b=z->box) == nil) 1377 return; 1378 n = sx->sx[1]->number; 1379 for(i=0; i<b->nmsg; i++){ 1380 if(b->msg[i]->imapid == n){ 1381 msgplumb(b->msg[i], 1); 1382 msgfree(b->msg[i]); 1383 b->nmsg--; 1384 memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]); 1385 i--; 1386 b->maxseen--; 1387 b->exists--; 1388 continue; 1389 } 1390 if(b->msg[i]->imapid > n) 1391 b->msg[i]->imapid--; 1392 b->msg[i]->ix = i; 1393 } 1394 } 1395 1396 static void 1397 xsearch(Imap *z, Sx *sx) 1398 { 1399 int i; 1400 1401 free(z->uid); 1402 z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]); 1403 z->nuid = sx->nsx-2; 1404 for(i=0; i<z->nuid; i++) 1405 z->uid[i] = sx->sx[i+2]->number; 1406 } 1407 1408 /* 1409 * Table-driven FETCH message info parser. 1410 */ 1411 static void xmsgflags(Msg*, Sx*, Sx*); 1412 static void xmsgdate(Msg*, Sx*, Sx*); 1413 static void xmsgrfc822size(Msg*, Sx*, Sx*); 1414 static void xmsgenvelope(Msg*, Sx*, Sx*); 1415 static void xmsgbody(Msg*, Sx*, Sx*); 1416 static void xmsgbodydata(Msg*, Sx*, Sx*); 1417 1418 static struct { 1419 char *name; 1420 void (*fn)(Msg*, Sx*, Sx*); 1421 } msgtab[] = { 1422 "FLAGS", xmsgflags, 1423 "INTERNALDATE", xmsgdate, 1424 "RFC822.SIZE", xmsgrfc822size, 1425 "ENVELOPE", xmsgenvelope, 1426 "BODY", xmsgbody, 1427 "BODY[", xmsgbodydata 1428 }; 1429 1430 static void 1431 xfetch(Imap *z, Sx *sx) 1432 { 1433 int i, j, n, uid; 1434 Msg *msg; 1435 1436 if(z->box == nil){ 1437 warn("FETCH but no open box: %$", sx); 1438 return; 1439 } 1440 1441 /* * 152 FETCH (UID 185 FLAGS () ...) */ 1442 if(sx->sx[3]->nsx%2){ 1443 warn("malformed FETCH: %$", sx); 1444 return; 1445 } 1446 1447 n = sx->sx[1]->number; 1448 sx = sx->sx[3]; 1449 for(i=0; i<sx->nsx; i+=2){ 1450 if(isatom(sx->sx[i], "UID")){ 1451 if(sx->sx[i+1]->type == SxNumber){ 1452 uid = sx->sx[i+1]->number; 1453 goto haveuid; 1454 } 1455 } 1456 } 1457 /* This happens: too bad. 1458 warn("FETCH without UID: %$", sx); 1459 */ 1460 return; 1461 1462 haveuid: 1463 msg = msgbyimapuid(z->box, uid, 1); 1464 if(msg->imapid && msg->imapid != n) 1465 warn("msg id mismatch: want %d have %d", msg->id, n); 1466 msg->imapid = n; 1467 for(i=0; i<sx->nsx; i+=2){ 1468 for(j=0; j<nelem(msgtab); j++) 1469 if(isatom(sx->sx[i], msgtab[j].name)) 1470 msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]); 1471 } 1472 msgplumb(msg, 0); 1473 } 1474 1475 static void 1476 xmsgflags(Msg *msg, Sx *k, Sx *v) 1477 { 1478 USED(k); 1479 msg->flags = parseflags(v); 1480 } 1481 1482 static void 1483 xmsgdate(Msg *msg, Sx *k, Sx *v) 1484 { 1485 USED(k); 1486 msg->date = parsedate(v); 1487 } 1488 1489 static void 1490 xmsgrfc822size(Msg *msg, Sx *k, Sx *v) 1491 { 1492 USED(k); 1493 msg->size = parsenumber(v); 1494 } 1495 1496 static char* 1497 nstring(Sx *v) 1498 { 1499 char *p; 1500 1501 if(isnil(v)) 1502 return estrdup(""); 1503 p = v->data; 1504 v->data = nil; 1505 return p; 1506 } 1507 1508 static char* 1509 copyaddrs(Sx *v) 1510 { 1511 char *s, *sep; 1512 char *name, *email, *host, *mbox; 1513 int i; 1514 Fmt fmt; 1515 1516 if(v->nsx == 0) 1517 return nil; 1518 1519 fmtstrinit(&fmt); 1520 sep = ""; 1521 for(i=0; i<v->nsx; i++){ 1522 if(!sxmatch(v->sx[i], "SSSS")) 1523 warn("bad address: %$", v->sx[i]); 1524 name = unrfc2047(nstring(v->sx[i]->sx[0])); 1525 /* ignore sx[1] - route */ 1526 mbox = unrfc2047(nstring(v->sx[i]->sx[2])); 1527 host = unrfc2047(nstring(v->sx[i]->sx[3])); 1528 if(mbox == nil || host == nil){ /* rfc822 group syntax */ 1529 free(name); 1530 free(mbox); 1531 free(host); 1532 continue; 1533 } 1534 email = esmprint("%s@%s", mbox, host); 1535 free(mbox); 1536 free(host); 1537 fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : ""); 1538 free(name); 1539 free(email); 1540 sep = " "; 1541 } 1542 s = fmtstrflush(&fmt); 1543 if(s == nil) 1544 sysfatal("out of memory"); 1545 return s; 1546 } 1547 1548 static void 1549 xmsgenvelope(Msg *msg, Sx *k, Sx *v) 1550 { 1551 USED(k); 1552 hdrfree(msg->part[0]->hdr); 1553 msg->part[0]->hdr = parseenvelope(v); 1554 } 1555 1556 static struct { 1557 char *name; 1558 int offset; 1559 } paramtab[] = { 1560 "charset", offsetof(Part, charset), 1561 "name", offsetof(Part, filename) 1562 }; 1563 1564 static void 1565 parseparams(Part *part, Sx *v) 1566 { 1567 int i, j; 1568 char *s, *t, **p; 1569 1570 if(isnil(v)) 1571 return; 1572 if(v->nsx%2){ 1573 warn("bad message params: %$", v); 1574 return; 1575 } 1576 for(i=0; i<v->nsx; i+=2){ 1577 s = nstring(v->sx[i]); 1578 t = nstring(v->sx[i+1]); 1579 for(j=0; j<nelem(paramtab); j++){ 1580 if(cistrcmp(paramtab[j].name, s) == 0){ 1581 p = (char**)((char*)part+paramtab[j].offset); 1582 free(*p); 1583 *p = t; 1584 t = nil; 1585 break; 1586 } 1587 } 1588 free(s); 1589 free(t); 1590 } 1591 } 1592 1593 static void 1594 parsestructure(Part *part, Sx *v) 1595 { 1596 int i; 1597 char *s, *t; 1598 1599 if(isnil(v)) 1600 return; 1601 if(v->type != SxList){ 1602 bad: 1603 warn("bad structure: %$", v); 1604 return; 1605 } 1606 if(islist(v->sx[0])){ 1607 /* multipart */ 1608 for(i=0; i<v->nsx && islist(v->sx[i]); i++) 1609 parsestructure(partcreate(part->msg, part), v->sx[i]); 1610 free(part->type); 1611 if(i != v->nsx-1 || !isstring(v->sx[i])){ 1612 warn("bad multipart structure: %$", v); 1613 part->type = estrdup("multipart/mixed"); 1614 return; 1615 } 1616 s = nstring(v->sx[i]); 1617 strlwr(s); 1618 part->type = esmprint("multipart/%s", s); 1619 free(s); 1620 return; 1621 } 1622 /* single part */ 1623 if(!isstring(v->sx[0]) || v->nsx < 2) 1624 goto bad; 1625 s = nstring(v->sx[0]); 1626 t = nstring(v->sx[1]); 1627 strlwr(s); 1628 strlwr(t); 1629 free(part->type); 1630 part->type = esmprint("%s/%s", s, t); 1631 if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3]) 1632 || !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6])) 1633 goto bad; 1634 parseparams(part, v->sx[2]); 1635 part->idstr = nstring(v->sx[3]); 1636 part->desc = nstring(v->sx[4]); 1637 part->encoding = nstring(v->sx[5]); 1638 part->size = v->sx[6]->number; 1639 if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){ 1640 if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9])) 1641 goto bad; 1642 part->hdr = parseenvelope(v->sx[7]); 1643 parsestructure(partcreate(part->msg, part), v->sx[8]); 1644 part->lines = v->sx[9]->number; 1645 } 1646 if(strcmp(s, "text") == 0){ 1647 if(v->nsx < 8 || !isnumber(v->sx[7])) 1648 goto bad; 1649 part->lines = v->sx[7]->number; 1650 } 1651 } 1652 1653 static void 1654 xmsgbody(Msg *msg, Sx *k, Sx *v) 1655 { 1656 USED(k); 1657 if(v->type != SxList){ 1658 warn("bad body: %$", v); 1659 return; 1660 } 1661 /* 1662 * To follow the structure exactly we should 1663 * be doing this to partcreate(msg, msg->part[0]), 1664 * and we should leave msg->part[0] with type message/rfc822, 1665 * but the extra layer is redundant - what else would be in a mailbox? 1666 */ 1667 parsestructure(msg->part[0], v); 1668 if(msg->box->maxseen < msg->imapid) 1669 msg->box->maxseen = msg->imapid; 1670 if(msg->imapuid >= msg->box->uidnext) 1671 msg->box->uidnext = msg->imapuid+1; 1672 } 1673 1674 static void 1675 xmsgbodydata(Msg *msg, Sx *k, Sx *v) 1676 { 1677 int i; 1678 char *name, *p; 1679 Part *part; 1680 1681 name = k->data; 1682 name += 5; /* body[ */ 1683 p = strchr(name, ']'); 1684 if(p) 1685 *p = 0; 1686 1687 /* now name is something like 1 or 3.2.MIME - walk down parts from root */ 1688 part = msg->part[0]; 1689 1690 1691 while('1' <= name[0] && name[0] <= '9'){ 1692 i = strtol(name, &p, 10); 1693 if(*p == '.') 1694 p++; 1695 else if(*p != 0){ 1696 warn("bad body name: %$", k); 1697 return; 1698 } 1699 if((part = subpart(part, i-1)) == nil){ 1700 warn("unknown body part: %$", k); 1701 return; 1702 } 1703 name = p; 1704 } 1705 1706 1707 if(cistrcmp(name, "") == 0){ 1708 free(part->raw); 1709 part->raw = nstring(v); 1710 nocr(part->raw); 1711 }else if(cistrcmp(name, "HEADER") == 0){ 1712 free(part->rawheader); 1713 part->rawheader = nstring(v); 1714 nocr(part->rawheader); 1715 }else if(cistrcmp(name, "MIME") == 0){ 1716 free(part->mimeheader); 1717 part->mimeheader = nstring(v); 1718 nocr(part->mimeheader); 1719 }else if(cistrcmp(name, "TEXT") == 0){ 1720 free(part->rawbody); 1721 part->rawbody = nstring(v); 1722 nocr(part->rawbody); 1723 } 1724 } 1725 1726 /* 1727 * Table-driven OK info parser. 1728 */ 1729 static void xokuidvalidity(Imap*, Sx*); 1730 static void xokpermflags(Imap*, Sx*); 1731 static void xokunseen(Imap*, Sx*); 1732 static void xokreadwrite(Imap*, Sx*); 1733 static void xokreadonly(Imap*, Sx*); 1734 1735 struct { 1736 char *name; 1737 char fmt; 1738 void (*fn)(Imap*, Sx*); 1739 } oktab[] = { 1740 "UIDVALIDITY", 'N', xokuidvalidity, 1741 "PERMANENTFLAGS", 'L', xokpermflags, 1742 "UNSEEN", 'N', xokunseen, 1743 "READ-WRITE", 0, xokreadwrite, 1744 "READ-ONLY", 0, xokreadonly 1745 }; 1746 1747 static void 1748 xok(Imap *z, Sx *sx) 1749 { 1750 int i; 1751 char *name; 1752 Sx *arg; 1753 1754 if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){ 1755 if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']') 1756 arg = nil; 1757 else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']') 1758 arg = sx->sx[3]; 1759 else{ 1760 warn("cannot parse OK: %$", sx); 1761 return; 1762 } 1763 name = sx->sx[2]->data+1; 1764 for(i=0; i<nelem(oktab); i++){ 1765 if(cistrcmp(name, oktab[i].name) == 0){ 1766 if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){ 1767 warn("malformed %s: %$", name, arg); 1768 continue; 1769 } 1770 oktab[i].fn(z, arg); 1771 } 1772 } 1773 } 1774 } 1775 1776 static void 1777 xokuidvalidity(Imap *z, Sx *sx) 1778 { 1779 int i; 1780 Box *b; 1781 1782 if((b=z->box) == nil) 1783 return; 1784 if(b->validity != sx->number){ 1785 b->validity = sx->number; 1786 b->uidnext = 1; 1787 for(i=0; i<b->nmsg; i++) 1788 msgfree(b->msg[i]); 1789 free(b->msg); 1790 b->msg = nil; 1791 b->nmsg = 0; 1792 } 1793 } 1794 1795 static void 1796 xokpermflags(Imap *z, Sx *sx) 1797 { 1798 USED(z); 1799 USED(sx); 1800 /* z->permflags = parseflags(sx); */ 1801 } 1802 1803 static void 1804 xokunseen(Imap *z, Sx *sx) 1805 { 1806 USED(z); 1807 USED(sx); 1808 /* z->unseen = sx->number; */ 1809 } 1810 1811 static void 1812 xokreadwrite(Imap *z, Sx *sx) 1813 { 1814 USED(z); 1815 USED(sx); 1816 /* z->boxmode = ORDWR; */ 1817 } 1818 1819 static void 1820 xokreadonly(Imap *z, Sx *sx) 1821 { 1822 USED(z); 1823 USED(sx); 1824 /* z->boxmode = OREAD; */ 1825 }