1 #include <u.h> 2 #include <libc.h> 3 #include <auth.h> 4 #include <bio.h> 5 #include "imap4d.h" 6 7 /* 8 * these should be in libraries 9 */ 10 char *csquery(char *attr, char *val, char *rattr); 11 12 /* 13 * /lib/rfc/rfc2060 imap4rev1 14 * /lib/rfc/rfc2683 is implementation advice 15 * /lib/rfc/rfc2342 is namespace capability 16 * /lib/rfc/rfc2222 is security protocols 17 * /lib/rfc/rfc1731 is security protocols 18 * /lib/rfc/rfc2221 is LOGIN-REFERRALS 19 * /lib/rfc/rfc2193 is MAILBOX-REFERRALS 20 * /lib/rfc/rfc2177 is IDLE capability 21 * /lib/rfc/rfc2195 is CRAM-MD5 authentication 22 * /lib/rfc/rfc2088 is LITERAL+ capability 23 * /lib/rfc/rfc1760 is S/Key authentication 24 * 25 * outlook uses "Secure Password Authentication" aka ntlm authentication 26 * 27 * capabilities from nslocum 28 * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT 29 */ 30 31 typedef struct ParseCmd ParseCmd; 32 33 enum 34 { 35 UlongMax = 4294967295, 36 }; 37 38 struct ParseCmd 39 { 40 char *name; 41 void (*f)(char *tg, char *cmd); 42 }; 43 44 static void appendCmd(char *tg, char *cmd); 45 static void authenticateCmd(char *tg, char *cmd); 46 static void capabilityCmd(char *tg, char *cmd); 47 static void closeCmd(char *tg, char *cmd); 48 static void copyCmd(char *tg, char *cmd); 49 static void createCmd(char *tg, char *cmd); 50 static void deleteCmd(char *tg, char *cmd); 51 static void expungeCmd(char *tg, char *cmd); 52 static void fetchCmd(char *tg, char *cmd); 53 static void idleCmd(char *tg, char *cmd); 54 static void listCmd(char *tg, char *cmd); 55 static void loginCmd(char *tg, char *cmd); 56 static void logoutCmd(char *tg, char *cmd); 57 static void namespaceCmd(char *tg, char *cmd); 58 static void noopCmd(char *tg, char *cmd); 59 static void renameCmd(char *tg, char *cmd); 60 static void searchCmd(char *tg, char *cmd); 61 static void selectCmd(char *tg, char *cmd); 62 static void statusCmd(char *tg, char *cmd); 63 static void storeCmd(char *tg, char *cmd); 64 static void subscribeCmd(char *tg, char *cmd); 65 static void uidCmd(char *tg, char *cmd); 66 static void unsubscribeCmd(char *tg, char *cmd); 67 68 static void copyUCmd(char *tg, char *cmd, int uids); 69 static void fetchUCmd(char *tg, char *cmd, int uids); 70 static void searchUCmd(char *tg, char *cmd, int uids); 71 static void storeUCmd(char *tg, char *cmd, int uids); 72 73 static void imap4(int); 74 static void status(int expungeable, int uids); 75 static void cleaner(void); 76 static void check(void); 77 static int catcher(void*, char*); 78 79 static Search *searchKey(int first); 80 static Search *searchKeys(int first, Search *tail); 81 static char *astring(void); 82 static char *atomString(char *disallowed, char *initial); 83 static char *atom(void); 84 static void badsyn(void); 85 static void clearcmd(void); 86 static char *command(void); 87 static void crnl(void); 88 static Fetch *fetchAtt(char *s, Fetch *f); 89 static Fetch *fetchWhat(void); 90 static int flagList(void); 91 static int flags(void); 92 static int getc(void); 93 static char *listmbox(void); 94 static char *literal(void); 95 static ulong litlen(void); 96 static MsgSet *msgSet(int); 97 static void mustBe(int c); 98 static ulong number(int nonzero); 99 static int peekc(void); 100 static char *quoted(void); 101 static void sectText(Fetch *f, int mimeOk); 102 static ulong seqNo(void); 103 static Store *storeWhat(void); 104 static char *tag(void); 105 static ulong uidNo(void); 106 static void ungetc(void); 107 108 static ParseCmd SNonAuthed[] = 109 { 110 {"capability", capabilityCmd}, 111 {"logout", logoutCmd}, 112 {"x-exit", logoutCmd}, 113 {"noop", noopCmd}, 114 115 {"login", loginCmd}, 116 {"authenticate", authenticateCmd}, 117 118 nil 119 }; 120 121 static ParseCmd SAuthed[] = 122 { 123 {"capability", capabilityCmd}, 124 {"logout", logoutCmd}, 125 {"x-exit", logoutCmd}, 126 {"noop", noopCmd}, 127 128 {"append", appendCmd}, 129 {"create", createCmd}, 130 {"delete", deleteCmd}, 131 {"examine", selectCmd}, 132 {"select", selectCmd}, 133 {"idle", idleCmd}, 134 {"list", listCmd}, 135 {"lsub", listCmd}, 136 {"namespace", namespaceCmd}, 137 {"rename", renameCmd}, 138 {"status", statusCmd}, 139 {"subscribe", subscribeCmd}, 140 {"unsubscribe", unsubscribeCmd}, 141 142 nil 143 }; 144 145 static ParseCmd SSelected[] = 146 { 147 {"capability", capabilityCmd}, 148 {"logout", logoutCmd}, 149 {"x-exit", logoutCmd}, 150 {"noop", noopCmd}, 151 152 {"append", appendCmd}, 153 {"create", createCmd}, 154 {"delete", deleteCmd}, 155 {"examine", selectCmd}, 156 {"select", selectCmd}, 157 {"idle", idleCmd}, 158 {"list", listCmd}, 159 {"lsub", listCmd}, 160 {"namespace", namespaceCmd}, 161 {"rename", renameCmd}, 162 {"status", statusCmd}, 163 {"subscribe", subscribeCmd}, 164 {"unsubscribe", unsubscribeCmd}, 165 166 {"check", noopCmd}, 167 {"close", closeCmd}, 168 {"copy", copyCmd}, 169 {"expunge", expungeCmd}, 170 {"fetch", fetchCmd}, 171 {"search", searchCmd}, 172 {"store", storeCmd}, 173 {"uid", uidCmd}, 174 175 nil 176 }; 177 178 static char *atomStop = "(){%*\"\\"; 179 static Chalstate *chal; 180 static int chaled; 181 static ParseCmd *imapState; 182 static jmp_buf parseJmp; 183 static char *parseMsg; 184 static int allowPass; 185 static int allowCR; 186 static int exiting; 187 static QLock imaplock; 188 static int idlepid = -1; 189 190 Biobuf bout; 191 Biobuf bin; 192 char username[UserNameLen]; 193 char mboxDir[MboxNameLen]; 194 char *servername; 195 char *site; 196 char *remote; 197 Box *selected; 198 Bin *parseBin; 199 int debug; 200 201 void 202 main(int argc, char *argv[]) 203 { 204 char *s, *t; 205 int preauth, n; 206 207 Binit(&bin, 0, OREAD); 208 Binit(&bout, 1, OWRITE); 209 210 preauth = 0; 211 allowPass = 0; 212 allowCR = 0; 213 ARGBEGIN{ 214 case 'a': 215 preauth = 1; 216 break; 217 case 'd': 218 site = ARGF(); 219 break; 220 case 'c': 221 allowCR = 1; 222 break; 223 case 'p': 224 allowPass = 1; 225 break; 226 case 'r': 227 remote = ARGF(); 228 break; 229 case 's': 230 servername = ARGF(); 231 break; 232 case 'v': 233 debug = 1; 234 debuglog("imap4d debugging enabled\n"); 235 break; 236 default: 237 fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n"); 238 bye("usage"); 239 break; 240 }ARGEND 241 242 if(allowPass && allowCR){ 243 fprint(2, "%s: -c and -p are mutually exclusive\n", argv0); 244 bye("usage"); 245 } 246 247 if(preauth) 248 setupuser(nil); 249 250 if(servername == nil){ 251 servername = csquery("sys", sysname(), "dom"); 252 if(servername == nil) 253 servername = sysname(); 254 if(servername == nil){ 255 fprint(2, "ip/imap4d can't find server name: %r\n"); 256 bye("can't find system name"); 257 } 258 } 259 if(site == nil){ 260 t = getenv("site"); 261 if(t == nil) 262 site = servername; 263 else{ 264 n = strlen(t); 265 s = strchr(servername, '.'); 266 if(s == nil) 267 s = servername; 268 else 269 s++; 270 n += strlen(s) + 2; 271 site = emalloc(n); 272 snprint(site, n, "%s.%s", t, s); 273 } 274 } 275 276 rfork(RFNOTEG|RFREND); 277 278 atnotify(catcher, 1); 279 qlock(&imaplock); 280 atexit(cleaner); 281 imap4(preauth); 282 } 283 284 static void 285 imap4(int preauth) 286 { 287 char *volatile tg; 288 char *volatile cmd; 289 ParseCmd *st; 290 291 if(preauth){ 292 Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username); 293 imapState = SAuthed; 294 }else{ 295 Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername); 296 imapState = SNonAuthed; 297 } 298 if(Bflush(&bout) < 0) 299 writeErr(); 300 301 chaled = 0; 302 303 tg = nil; 304 cmd = nil; 305 if(setjmp(parseJmp)){ 306 if(tg == nil) 307 Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg); 308 else if(cmd == nil) 309 Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg); 310 else 311 Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg); 312 clearcmd(); 313 if(Bflush(&bout) < 0) 314 writeErr(); 315 binfree(&parseBin); 316 } 317 for(;;){ 318 if(mbLocked()) 319 bye("internal error: mailbox lock held"); 320 tg = nil; 321 cmd = nil; 322 tg = tag(); 323 mustBe(' '); 324 cmd = atom(); 325 326 /* 327 * note: outlook express is broken: it requires echoing the 328 * command as part of matching response 329 */ 330 for(st = imapState; st->name != nil; st++){ 331 if(cistrcmp(cmd, st->name) == 0){ 332 (*st->f)(tg, cmd); 333 break; 334 } 335 } 336 if(st->name == nil){ 337 clearcmd(); 338 Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd); 339 } 340 341 if(Bflush(&bout) < 0) 342 writeErr(); 343 binfree(&parseBin); 344 } 345 } 346 347 void 348 bye(char *fmt, ...) 349 { 350 va_list arg; 351 352 va_start(arg, fmt); 353 Bprint(&bout, "* bye "); 354 Bvprint(&bout, fmt, arg); 355 Bprint(&bout, "\r\n"); 356 Bflush(&bout); 357 exits("rob2"); 358 exits(0); 359 } 360 361 void 362 parseErr(char *msg) 363 { 364 parseMsg = msg; 365 longjmp(parseJmp, 1); 366 } 367 368 /* 369 * an error occured while writing to the client 370 */ 371 void 372 writeErr(void) 373 { 374 cleaner(); 375 _exits("connection closed"); 376 } 377 378 static int 379 catcher(void *v, char *msg) 380 { 381 USED(v); 382 if(strstr(msg, "closed pipe") != nil) 383 return 1; 384 return 0; 385 } 386 387 /* 388 * wipes out the idleCmd backgroung process if it is around. 389 * this can only be called if the current proc has qlocked imaplock. 390 * it must be the last piece of imap4d code executed. 391 */ 392 static void 393 cleaner(void) 394 { 395 int i; 396 397 if(idlepid < 0) 398 return; 399 exiting = 1; 400 close(0); 401 close(1); 402 close(2); 403 404 /* 405 * the other proc is either stuck in a read, a sleep, 406 * or is trying to lock imap4lock. 407 * get him out of it so he can exit cleanly 408 */ 409 qunlock(&imaplock); 410 for(i = 0; i < 4; i++) 411 postnote(PNGROUP, getpid(), "die"); 412 } 413 414 /* 415 * send any pending status updates to the client 416 * careful: shouldn't exit, because called by idle polling proc 417 * 418 * can't always send pending info 419 * in particular, can't send expunge info 420 * in response to a fetch, store, or search command. 421 * 422 * rfc2060 5.2: server must send mailbox size updates 423 * rfc2060 5.2: server may send flag updates 424 * rfc2060 5.5: servers prohibited from sending expunge while fetch, store, search in progress 425 * rfc2060 7: in selected state, server checks mailbox for new messages as part of every command 426 * sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox 427 * should also send appropriate untagged FETCH and EXPUNGE messages if another agent 428 * changes the state of any message flags or expunges any messages 429 * rfc2060 7.4.1 expunge server response must not be sent when no command is in progress, 430 * nor while responding to a fetch, stort, or search command (uid versions are ok) 431 * command only "in progress" after entirely parsed. 432 * 433 * strategy for third party deletion of messages or of a mailbox 434 * 435 * deletion of a selected mailbox => act like all message are expunged 436 * not strictly allowed by rfc2180, but close to method 3.2. 437 * 438 * renaming same as deletion 439 * 440 * copy 441 * reject iff a deleted message is in the request 442 * 443 * search, store, fetch operations on expunged messages 444 * ignore the expunged messages 445 * return tagged no if referenced 446 */ 447 static void 448 status(int expungeable, int uids) 449 { 450 int tell; 451 452 if(!selected) 453 return; 454 tell = 0; 455 if(expungeable) 456 tell = expungeMsgs(selected, 1); 457 if(selected->sendFlags) 458 sendFlags(selected, uids); 459 if(tell || selected->toldMax != selected->max){ 460 Bprint(&bout, "* %lud EXISTS\r\n", selected->max); 461 selected->toldMax = selected->max; 462 } 463 if(tell || selected->toldRecent != selected->recent){ 464 Bprint(&bout, "* %lud RECENT\r\n", selected->recent); 465 selected->toldRecent = selected->recent; 466 } 467 if(tell) 468 closeImp(selected, checkBox(selected, 1)); 469 } 470 471 /* 472 * careful: can't exit, because called by idle polling proc 473 */ 474 static void 475 check(void) 476 { 477 if(!selected) 478 return; 479 checkBox(selected, 0); 480 status(1, 0); 481 } 482 483 static void 484 appendCmd(char *tg, char *cmd) 485 { 486 char *mbox, head[128]; 487 ulong t, n, now; 488 int flags, ok; 489 490 mustBe(' '); 491 mbox = astring(); 492 mustBe(' '); 493 flags = 0; 494 if(peekc() == '('){ 495 flags = flagList(); 496 mustBe(' '); 497 } 498 now = time(nil); 499 if(peekc() == '"'){ 500 t = imap4DateTime(quoted()); 501 if(t == ~0) 502 parseErr("illegal date format"); 503 mustBe(' '); 504 if(t > now) 505 t = now; 506 }else 507 t = now; 508 n = litlen(); 509 510 mbox = mboxName(mbox); 511 if(mbox == nil || !okMbox(mbox)){ 512 check(); 513 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 514 return; 515 } 516 if(!cdExists(mboxDir, mbox)){ 517 check(); 518 Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd); 519 return; 520 } 521 522 snprint(head, sizeof(head), "From %s %s", username, ctime(t)); 523 ok = appendSave(mbox, flags, head, &bin, n); 524 crnl(); 525 check(); 526 if(ok) 527 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 528 else 529 Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd); 530 } 531 532 static void 533 authenticateCmd(char *tg, char *cmd) 534 { 535 char *s, *t; 536 537 mustBe(' '); 538 s = atom(); 539 crnl(); 540 auth_freechal(chal); 541 chal = nil; 542 if(cistrcmp(s, "cram-md5") == 0){ 543 t = cramauth(); 544 if(t == nil){ 545 Bprint(&bout, "%s OK %s\r\n", tg, cmd); 546 imapState = SAuthed; 547 }else 548 Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t); 549 }else 550 Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd); 551 } 552 553 static void 554 capabilityCmd(char *tg, char *cmd) 555 { 556 crnl(); 557 check(); 558 // nslocum's capabilities 559 // Bprint(&bout, "* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT\r\n"); 560 Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n"); 561 Bprint(&bout, "%s OK %s\r\n", tg, cmd); 562 } 563 564 static void 565 closeCmd(char *tg, char *cmd) 566 { 567 crnl(); 568 imapState = SAuthed; 569 closeBox(selected, 1); 570 selected = nil; 571 Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd); 572 } 573 574 /* 575 * note: message id's are before any pending expunges 576 */ 577 static void 578 copyCmd(char *tg, char *cmd) 579 { 580 copyUCmd(tg, cmd, 0); 581 } 582 583 static void 584 copyUCmd(char *tg, char *cmd, int uids) 585 { 586 MsgSet *ms; 587 char *uid, *mbox; 588 ulong max; 589 int ok; 590 591 mustBe(' '); 592 ms = msgSet(uids); 593 mustBe(' '); 594 mbox = astring(); 595 crnl(); 596 597 uid = ""; 598 if(uids) 599 uid = "uid "; 600 601 mbox = mboxName(mbox); 602 if(mbox == nil || !okMbox(mbox)){ 603 status(1, uids); 604 Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd); 605 return; 606 } 607 if(!cdExists(mboxDir, mbox)){ 608 check(); 609 Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd); 610 return; 611 } 612 613 max = selected->max; 614 checkBox(selected, 0); 615 ok = forMsgs(selected, ms, max, uids, copyCheck, nil); 616 if(ok) 617 ok = forMsgs(selected, ms, max, uids, copySave, mbox); 618 619 status(1, uids); 620 if(ok) 621 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 622 else 623 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 624 } 625 626 static void 627 createCmd(char *tg, char *cmd) 628 { 629 char *mbox, *m; 630 int fd, slash; 631 632 mustBe(' '); 633 mbox = astring(); 634 crnl(); 635 check(); 636 637 m = strchr(mbox, '\0'); 638 slash = m != mbox && m[-1] == '/'; 639 mbox = mboxName(mbox); 640 if(mbox == nil || !okMbox(mbox)){ 641 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 642 return; 643 } 644 if(cistrcmp(mbox, "inbox") == 0){ 645 Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd); 646 return; 647 } 648 if(access(mbox, AEXIST) >= 0){ 649 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd); 650 return; 651 } 652 653 fd = createBox(mbox, slash); 654 close(fd); 655 if(fd < 0) 656 Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox); 657 else 658 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd); 659 } 660 661 static void 662 deleteCmd(char *tg, char *cmd) 663 { 664 char *mbox, *imp; 665 666 mustBe(' '); 667 mbox = astring(); 668 crnl(); 669 check(); 670 671 mbox = mboxName(mbox); 672 if(mbox == nil || !okMbox(mbox)){ 673 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 674 return; 675 } 676 677 imp = impName(mbox); 678 if(cistrcmp(mbox, "inbox") == 0 679 || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp) 680 || cdRemove(mboxDir, mbox) < 0) 681 Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox); 682 else 683 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd); 684 } 685 686 static void 687 expungeCmd(char *tg, char *cmd) 688 { 689 int ok; 690 691 crnl(); 692 ok = deleteMsgs(selected); 693 check(); 694 if(ok) 695 Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd); 696 else 697 Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd); 698 } 699 700 static void 701 fetchCmd(char *tg, char *cmd) 702 { 703 fetchUCmd(tg, cmd, 0); 704 } 705 706 static void 707 fetchUCmd(char *tg, char *cmd, int uids) 708 { 709 Fetch *f; 710 MsgSet *ms; 711 MbLock *ml; 712 char *uid; 713 ulong max; 714 int ok; 715 716 mustBe(' '); 717 ms = msgSet(uids); 718 mustBe(' '); 719 f = fetchWhat(); 720 crnl(); 721 uid = ""; 722 if(uids) 723 uid = "uid "; 724 max = selected->max; 725 ml = checkBox(selected, 1); 726 if(ml != nil) 727 forMsgs(selected, ms, max, uids, fetchSeen, f); 728 closeImp(selected, ml); 729 ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f); 730 status(uids, uids); 731 if(ok) 732 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 733 else 734 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 735 } 736 737 static void 738 idleCmd(char *tg, char *cmd) 739 { 740 int c, pid; 741 742 crnl(); 743 Bprint(&bout, "+ idling, waiting for done\r\n"); 744 if(Bflush(&bout) < 0) 745 writeErr(); 746 747 if(idlepid < 0){ 748 pid = rfork(RFPROC|RFMEM|RFNOWAIT); 749 if(pid == 0){ 750 for(;;){ 751 qlock(&imaplock); 752 if(exiting) 753 break; 754 755 /* 756 * parent may have changed curDir, but it doesn't change our . 757 */ 758 resetCurDir(); 759 760 check(); 761 if(Bflush(&bout) < 0) 762 writeErr(); 763 qunlock(&imaplock); 764 sleep(15*1000); 765 enableForwarding(); 766 } 767 _exits("rob3"); 768 _exits(0); 769 } 770 idlepid = pid; 771 } 772 773 qunlock(&imaplock); 774 775 /* 776 * clear out the next line, which is supposed to contain (case-insensitive) 777 * done\n 778 * this is special code since it has to dance with the idle polling proc 779 * and handle exiting correctly. 780 */ 781 for(;;){ 782 c = getc(); 783 if(c < 0){ 784 qlock(&imaplock); 785 if(!exiting) 786 cleaner(); 787 _exits("rob4"); 788 _exits(0); 789 } 790 if(c == '\n') 791 break; 792 } 793 794 qlock(&imaplock); 795 if(exiting) 796 {_exits("rob5"); 797 _exits(0); 798 } 799 800 /* 801 * child may have changed curDir, but it doesn't change our . 802 */ 803 resetCurDir(); 804 805 check(); 806 Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd); 807 } 808 809 static void 810 listCmd(char *tg, char *cmd) 811 { 812 char *s, *t, *ss, *ref, *mbox; 813 int n; 814 815 mustBe(' '); 816 s = astring(); 817 mustBe(' '); 818 t = listmbox(); 819 crnl(); 820 check(); 821 ref = mutf7str(s); 822 mbox = mutf7str(t); 823 if(ref == nil || mbox == nil){ 824 Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd); 825 return; 826 } 827 828 /* 829 * special request for hierarchy delimiter and root name 830 * root name appears to be name up to and including any delimiter, 831 * or the empty string, if there is no delimiter. 832 * 833 * this must change if the # namespace convention is supported. 834 */ 835 if(*mbox == '\0'){ 836 s = strchr(ref, '/'); 837 if(s == nil) 838 ref = ""; 839 else 840 s[1] = '\0'; 841 Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref); 842 Bprint(&bout, "%s OK %s\r\n", tg, cmd); 843 return; 844 } 845 846 847 /* 848 * massage the listing name: 849 * clean up the components individually, 850 * then rip off componenets from the ref to 851 * take care of leading ..'s in the mbox. 852 * 853 * the cleanup can wipe out * followed by a .. 854 * tough luck if such a stupid pattern is given. 855 */ 856 cleanname(mbox); 857 if(strcmp(mbox, ".") == 0) 858 *mbox = '\0'; 859 if(mbox[0] == '/') 860 *ref = '\0'; 861 else if(*ref != '\0'){ 862 cleanname(ref); 863 if(strcmp(ref, ".") == 0) 864 *ref = '\0'; 865 }else 866 *ref = '\0'; 867 while(*ref && isdotdot(mbox)){ 868 s = strrchr(ref, '/'); 869 if(s == nil) 870 s = ref; 871 if(isdotdot(s)) 872 break; 873 *s = '\0'; 874 mbox += 2; 875 if(*mbox == '/') 876 mbox++; 877 } 878 if(*ref == '\0'){ 879 s = mbox; 880 ss = s; 881 }else{ 882 n = strlen(ref) + strlen(mbox) + 2; 883 t = binalloc(&parseBin, n, 0); 884 if(t == nil) 885 parseErr("out of memory"); 886 snprint(t, n, "%s/%s", ref, mbox); 887 s = t; 888 ss = s + strlen(ref); 889 } 890 891 /* 892 * only allow activity in /mail/box 893 */ 894 if(s[0] == '/' || isdotdot(s)){ 895 Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg); 896 return; 897 } 898 899 if(cistrcmp(cmd, "lsub") == 0) 900 lsubBoxes(cmd, s, ss); 901 else 902 listBoxes(cmd, s, ss); 903 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 904 } 905 906 static char* 907 passCR(char*u, char*p) 908 { 909 static char Ebadch[] = "can't get challenge"; 910 static char nchall[64]; 911 static char response[64]; 912 static Chalstate *ch = nil; 913 AuthInfo *ai; 914 915 again: 916 if (ch == nil){ 917 if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u))) 918 return Ebadch; 919 snprint(nchall, 64, " encrypt challenge: %s", ch->chal); 920 return nchall; 921 } else { 922 strncpy(response, p, 64); 923 ch->resp = response; 924 ch->nresp = strlen(response); 925 ai = auth_response(ch); 926 auth_freechal(ch); 927 ch = nil; 928 if (ai == nil) 929 goto again; 930 setupuser(ai); 931 return nil; 932 } 933 934 } 935 936 static void 937 loginCmd(char *tg, char *cmd) 938 { 939 char *s, *t; 940 AuthInfo *ai; 941 char*r; 942 mustBe(' '); 943 s = astring(); /* uid */ 944 mustBe(' '); 945 t = astring(); /* password */ 946 crnl(); 947 if(allowCR){ 948 if ((r = passCR(s, t)) == nil){ 949 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd); 950 imapState = SAuthed; 951 } else { 952 Bprint(&bout, "* NO [ALERT] %s\r\n", r); 953 Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd); 954 } 955 return; 956 } 957 else if(allowPass){ 958 if(ai = passLogin(s, t)){ 959 setupuser(ai); 960 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd); 961 imapState = SAuthed; 962 }else 963 Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd); 964 return; 965 } 966 Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd); 967 } 968 969 /* 970 * logout or x-exit, which doesn't expunge the mailbox 971 */ 972 static void 973 logoutCmd(char *tg, char *cmd) 974 { 975 crnl(); 976 977 if(cmd[0] != 'x' && selected){ 978 closeBox(selected, 1); 979 selected = nil; 980 } 981 Bprint(&bout, "* bye\r\n"); 982 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 983 exits("rob6"); 984 exits(0); 985 } 986 987 static void 988 namespaceCmd(char *tg, char *cmd) 989 { 990 crnl(); 991 check(); 992 993 /* 994 * personal, other users, shared namespaces 995 * send back nil or descriptions of (prefix heirarchy-delim) for each case 996 */ 997 Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n"); 998 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 999 } 1000 1001 static void 1002 noopCmd(char *tg, char *cmd) 1003 { 1004 crnl(); 1005 check(); 1006 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1007 enableForwarding(); 1008 } 1009 1010 /* 1011 * this is only a partial implementation 1012 * should copy files to other directories, 1013 * and copy & truncate inbox 1014 */ 1015 static void 1016 renameCmd(char *tg, char *cmd) 1017 { 1018 char *from, *to; 1019 int ok; 1020 1021 mustBe(' '); 1022 from = astring(); 1023 mustBe(' '); 1024 to = astring(); 1025 crnl(); 1026 check(); 1027 1028 to = mboxName(to); 1029 if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){ 1030 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd); 1031 return; 1032 } 1033 if(access(to, AEXIST) >= 0){ 1034 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd); 1035 return; 1036 } 1037 from = mboxName(from); 1038 if(from == nil || !okMbox(from)){ 1039 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd); 1040 return; 1041 } 1042 if(cistrcmp(from, "inbox") == 0) 1043 ok = copyBox(from, to, 0); 1044 else 1045 ok = moveBox(from, to); 1046 1047 if(ok) 1048 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1049 else 1050 Bprint(&bout, "%s NO %s failed\r\n", tg, cmd); 1051 } 1052 1053 static void 1054 searchCmd(char *tg, char *cmd) 1055 { 1056 searchUCmd(tg, cmd, 0); 1057 } 1058 1059 static void 1060 searchUCmd(char *tg, char *cmd, int uids) 1061 { 1062 Search rock; 1063 Msg *m; 1064 char *uid; 1065 ulong id; 1066 1067 mustBe(' '); 1068 rock.next = nil; 1069 searchKeys(1, &rock); 1070 crnl(); 1071 uid = ""; 1072 if(uids) 1073 uid = "uid "; 1074 if(rock.next != nil && rock.next->key == SKCharset){ 1075 if(cistrstr(rock.next->s, "utf-8") != 0 1076 && cistrcmp(rock.next->s, "us-ascii") != 0){ 1077 Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd); 1078 checkBox(selected, 0); 1079 status(uids, uids); 1080 return; 1081 } 1082 rock.next = rock.next->next; 1083 } 1084 Bprint(&bout, "* search"); 1085 for(m = selected->msgs; m != nil; m = m->next) 1086 m->matched = searchMsg(m, rock.next); 1087 for(m = selected->msgs; m != nil; m = m->next){ 1088 if(m->matched){ 1089 if(uids) 1090 id = m->uid; 1091 else 1092 id = m->seq; 1093 Bprint(&bout, " %lud", id); 1094 } 1095 } 1096 Bprint(&bout, "\r\n"); 1097 checkBox(selected, 0); 1098 status(uids, uids); 1099 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 1100 } 1101 1102 static void 1103 selectCmd(char *tg, char *cmd) 1104 { 1105 Msg *m; 1106 char *s, *mbox; 1107 1108 mustBe(' '); 1109 mbox = astring(); 1110 crnl(); 1111 1112 if(selected){ 1113 imapState = SAuthed; 1114 closeBox(selected, 1); 1115 selected = nil; 1116 } 1117 1118 mbox = mboxName(mbox); 1119 if(mbox == nil || !okMbox(mbox)){ 1120 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1121 return; 1122 } 1123 1124 selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0); 1125 if(selected == nil){ 1126 Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); 1127 return; 1128 } 1129 1130 imapState = SSelected; 1131 1132 Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n"); 1133 Bprint(&bout, "* %lud EXISTS\r\n", selected->max); 1134 selected->toldMax = selected->max; 1135 Bprint(&bout, "* %lud RECENT\r\n", selected->recent); 1136 selected->toldRecent = selected->recent; 1137 for(m = selected->msgs; m != nil; m = m->next){ 1138 if(!m->expunged && (m->flags & MSeen) != MSeen){ 1139 Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq); 1140 break; 1141 } 1142 } 1143 Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n"); 1144 Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext); 1145 Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity); 1146 s = "READ-ONLY"; 1147 if(selected->writable) 1148 s = "READ-WRITE"; 1149 Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox); 1150 } 1151 1152 static NamedInt statusItems[] = 1153 { 1154 {"MESSAGES", SMessages}, 1155 {"RECENT", SRecent}, 1156 {"UIDNEXT", SUidNext}, 1157 {"UIDVALIDITY", SUidValidity}, 1158 {"UNSEEN", SUnseen}, 1159 {nil, 0} 1160 }; 1161 1162 static void 1163 statusCmd(char *tg, char *cmd) 1164 { 1165 Box *box; 1166 Msg *m; 1167 char *s, *mbox; 1168 ulong v; 1169 int si, i; 1170 1171 mustBe(' '); 1172 mbox = astring(); 1173 mustBe(' '); 1174 mustBe('('); 1175 si = 0; 1176 for(;;){ 1177 s = atom(); 1178 i = mapInt(statusItems, s); 1179 if(i == 0) 1180 parseErr("illegal status item"); 1181 si |= i; 1182 if(peekc() == ')') 1183 break; 1184 mustBe(' '); 1185 } 1186 mustBe(')'); 1187 crnl(); 1188 1189 mbox = mboxName(mbox); 1190 if(mbox == nil || !okMbox(mbox)){ 1191 check(); 1192 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1193 return; 1194 } 1195 1196 box = openBox(mbox, "status", 1); 1197 if(box == nil){ 1198 check(); 1199 Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); 1200 return; 1201 } 1202 1203 Bprint(&bout, "* STATUS ("); 1204 s = ""; 1205 for(i = 0; statusItems[i].name != nil; i++){ 1206 if(si & statusItems[i].v){ 1207 v = 0; 1208 switch(statusItems[i].v){ 1209 case SMessages: 1210 v = box->max; 1211 break; 1212 case SRecent: 1213 v = box->recent; 1214 break; 1215 case SUidNext: 1216 v = box->uidnext; 1217 break; 1218 case SUidValidity: 1219 v = box->uidvalidity; 1220 break; 1221 case SUnseen: 1222 v = 0; 1223 for(m = box->msgs; m != nil; m = m->next) 1224 if((m->flags & MSeen) != MSeen) 1225 v++; 1226 break; 1227 default: 1228 Bprint(&bout, ")"); 1229 bye("internal error: status item not implemented"); 1230 break; 1231 } 1232 Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v); 1233 s = " "; 1234 } 1235 } 1236 Bprint(&bout, ")\r\n"); 1237 closeBox(box, 1); 1238 1239 check(); 1240 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1241 } 1242 1243 static void 1244 storeCmd(char *tg, char *cmd) 1245 { 1246 storeUCmd(tg, cmd, 0); 1247 } 1248 1249 static void 1250 storeUCmd(char *tg, char *cmd, int uids) 1251 { 1252 Store *st; 1253 MsgSet *ms; 1254 MbLock *ml; 1255 char *uid; 1256 ulong max; 1257 int ok; 1258 1259 mustBe(' '); 1260 ms = msgSet(uids); 1261 mustBe(' '); 1262 st = storeWhat(); 1263 crnl(); 1264 uid = ""; 1265 if(uids) 1266 uid = "uid "; 1267 max = selected->max; 1268 ml = checkBox(selected, 1); 1269 ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st); 1270 closeImp(selected, ml); 1271 status(uids, uids); 1272 if(ok) 1273 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 1274 else 1275 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 1276 } 1277 1278 /* 1279 * minimal implementation of subscribe 1280 * all folders are automatically subscribed, 1281 * and can't be unsubscribed 1282 */ 1283 static void 1284 subscribeCmd(char *tg, char *cmd) 1285 { 1286 Box *box; 1287 char *mbox; 1288 int ok; 1289 1290 mustBe(' '); 1291 mbox = astring(); 1292 crnl(); 1293 check(); 1294 mbox = mboxName(mbox); 1295 ok = 0; 1296 if(mbox != nil && okMbox(mbox)){ 1297 box = openBox(mbox, "subscribe", 0); 1298 if(box != nil){ 1299 ok = subscribe(mbox, 's'); 1300 closeBox(box, 1); 1301 } 1302 } 1303 if(!ok) 1304 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1305 else 1306 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1307 } 1308 1309 static void 1310 uidCmd(char *tg, char *cmd) 1311 { 1312 char *sub; 1313 1314 mustBe(' '); 1315 sub = atom(); 1316 if(cistrcmp(sub, "copy") == 0) 1317 copyUCmd(tg, sub, 1); 1318 else if(cistrcmp(sub, "fetch") == 0) 1319 fetchUCmd(tg, sub, 1); 1320 else if(cistrcmp(sub, "search") == 0) 1321 searchUCmd(tg, sub, 1); 1322 else if(cistrcmp(sub, "store") == 0) 1323 storeUCmd(tg, sub, 1); 1324 else{ 1325 clearcmd(); 1326 Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub); 1327 } 1328 } 1329 1330 static void 1331 unsubscribeCmd(char *tg, char *cmd) 1332 { 1333 char *mbox; 1334 1335 mustBe(' '); 1336 mbox = astring(); 1337 crnl(); 1338 check(); 1339 mbox = mboxName(mbox); 1340 if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u')) 1341 Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd); 1342 else 1343 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1344 } 1345 1346 static void 1347 badsyn(void) 1348 { 1349 parseErr("bad syntax"); 1350 } 1351 1352 static void 1353 clearcmd(void) 1354 { 1355 int c; 1356 1357 for(;;){ 1358 c = getc(); 1359 if(c < 0) 1360 bye("end of input"); 1361 if(c == '\n') 1362 return; 1363 } 1364 } 1365 1366 static void 1367 crnl(void) 1368 { 1369 int c; 1370 1371 c = getc(); 1372 if(c == '\n') 1373 return; 1374 if(c != '\r' || getc() != '\n') 1375 badsyn(); 1376 } 1377 1378 static void 1379 mustBe(int c) 1380 { 1381 if(getc() != c){ 1382 ungetc(); 1383 badsyn(); 1384 } 1385 } 1386 1387 /* 1388 * flaglist : '(' ')' | '(' flags ')' 1389 */ 1390 static int 1391 flagList(void) 1392 { 1393 int f; 1394 1395 mustBe('('); 1396 f = 0; 1397 if(peekc() != ')') 1398 f = flags(); 1399 1400 mustBe(')'); 1401 return f; 1402 } 1403 1404 /* 1405 * flags : flag | flags ' ' flag 1406 * flag : '\' atom | atom 1407 */ 1408 static int 1409 flags(void) 1410 { 1411 int ff, flags; 1412 char *s; 1413 int c; 1414 1415 flags = 0; 1416 for(;;){ 1417 c = peekc(); 1418 if(c == '\\'){ 1419 mustBe('\\'); 1420 s = atomString(atomStop, "\\"); 1421 }else if(strchr(atomStop, c) != nil) 1422 s = atom(); 1423 else 1424 break; 1425 ff = mapFlag(s); 1426 if(ff == 0) 1427 parseErr("flag not supported"); 1428 flags |= ff; 1429 if(peekc() != ' ') 1430 break; 1431 mustBe(' '); 1432 } 1433 if(flags == 0) 1434 parseErr("no flags given"); 1435 return flags; 1436 } 1437 1438 /* 1439 * storeWhat : osign 'FLAGS' ' ' storeflags 1440 * | osign 'FLAGS.SILENT' ' ' storeflags 1441 * osign : 1442 * | '+' | '-' 1443 * storeflags : flagList | flags 1444 */ 1445 static Store* 1446 storeWhat(void) 1447 { 1448 int f; 1449 char *s; 1450 int c, w; 1451 1452 c = peekc(); 1453 if(c == '+' || c == '-') 1454 mustBe(c); 1455 else 1456 c = 0; 1457 s = atom(); 1458 w = 0; 1459 if(cistrcmp(s, "flags") == 0) 1460 w = STFlags; 1461 else if(cistrcmp(s, "flags.silent") == 0) 1462 w = STFlagsSilent; 1463 else 1464 parseErr("illegal store attribute"); 1465 mustBe(' '); 1466 if(peekc() == '(') 1467 f = flagList(); 1468 else 1469 f = flags(); 1470 return mkStore(c, w, f); 1471 } 1472 1473 /* 1474 * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')' 1475 * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt 1476 */ 1477 static char *fetchAtom = "(){}%*\"\\[]"; 1478 static Fetch* 1479 fetchWhat(void) 1480 { 1481 Fetch *f; 1482 char *s; 1483 1484 if(peekc() == '('){ 1485 getc(); 1486 f = nil; 1487 for(;;){ 1488 s = atomString(fetchAtom, ""); 1489 f = fetchAtt(s, f); 1490 if(peekc() == ')') 1491 break; 1492 mustBe(' '); 1493 } 1494 getc(); 1495 return revFetch(f); 1496 } 1497 1498 s = atomString(fetchAtom, ""); 1499 if(cistrcmp(s, "all") == 0) 1500 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil)))); 1501 else if(cistrcmp(s, "fast") == 0) 1502 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil))); 1503 else if(cistrcmp(s, "full") == 0) 1504 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil))))); 1505 else 1506 f = fetchAtt(s, nil); 1507 return f; 1508 } 1509 1510 /* 1511 * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE" 1512 * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT" 1513 * | "BODYSTRUCTURE" 1514 * | "UID" 1515 * | "BODY" 1516 * | "BODY" bodysubs 1517 * | "BODY.PEEK" bodysubs 1518 * bodysubs : sect 1519 * | sect '<' number '.' nz-number '>' 1520 * sect : '[' sectSpec ']' 1521 * sectSpec : sectMsgText 1522 * | sectPart 1523 * | sectPart '.' sectText 1524 * sectPart : nz-number 1525 * | sectPart '.' nz-number 1526 */ 1527 static Fetch* 1528 fetchAtt(char *s, Fetch *f) 1529 { 1530 NList *sect; 1531 int c; 1532 1533 if(cistrcmp(s, "envelope") == 0) 1534 return mkFetch(FEnvelope, f); 1535 if(cistrcmp(s, "flags") == 0) 1536 return mkFetch(FFlags, f); 1537 if(cistrcmp(s, "internaldate") == 0) 1538 return mkFetch(FInternalDate, f); 1539 if(cistrcmp(s, "RFC822") == 0) 1540 return mkFetch(FRfc822, f); 1541 if(cistrcmp(s, "RFC822.header") == 0) 1542 return mkFetch(FRfc822Head, f); 1543 if(cistrcmp(s, "RFC822.size") == 0) 1544 return mkFetch(FRfc822Size, f); 1545 if(cistrcmp(s, "RFC822.text") == 0) 1546 return mkFetch(FRfc822Text, f); 1547 if(cistrcmp(s, "bodystructure") == 0) 1548 return mkFetch(FBodyStruct, f); 1549 if(cistrcmp(s, "uid") == 0) 1550 return mkFetch(FUid, f); 1551 1552 if(cistrcmp(s, "body") == 0){ 1553 if(peekc() != '[') 1554 return mkFetch(FBody, f); 1555 f = mkFetch(FBodySect, f); 1556 }else if(cistrcmp(s, "body.peek") == 0) 1557 f = mkFetch(FBodyPeek, f); 1558 else 1559 parseErr("illegal fetch attribute"); 1560 1561 mustBe('['); 1562 c = peekc(); 1563 if(c >= '1' && c <= '9'){ 1564 sect = mkNList(number(1), nil); 1565 while(peekc() == '.'){ 1566 getc(); 1567 c = peekc(); 1568 if(c >= '1' && c <= '9'){ 1569 sect = mkNList(number(1), sect); 1570 }else{ 1571 break; 1572 } 1573 } 1574 f->sect = revNList(sect); 1575 } 1576 if(peekc() != ']') 1577 sectText(f, f->sect != nil); 1578 mustBe(']'); 1579 1580 if(peekc() != '<') 1581 return f; 1582 1583 f->partial = 1; 1584 mustBe('<'); 1585 f->start = number(0); 1586 mustBe('.'); 1587 f->size = number(1); 1588 mustBe('>'); 1589 return f; 1590 } 1591 1592 /* 1593 * sectText : sectMsgText | "MIME" 1594 * sectMsgText : "HEADER" 1595 * | "TEXT" 1596 * | "HEADER.FIELDS" ' ' hdrList 1597 * | "HEADER.FIELDS.NOT" ' ' hdrList 1598 * hdrList : '(' hdrs ')' 1599 * hdrs: : astring 1600 * | hdrs ' ' astring 1601 */ 1602 static void 1603 sectText(Fetch *f, int mimeOk) 1604 { 1605 SList *h; 1606 char *s; 1607 1608 s = atomString(fetchAtom, ""); 1609 if(cistrcmp(s, "header") == 0){ 1610 f->part = FPHead; 1611 return; 1612 } 1613 if(cistrcmp(s, "text") == 0){ 1614 f->part = FPText; 1615 return; 1616 } 1617 if(mimeOk && cistrcmp(s, "mime") == 0){ 1618 f->part = FPMime; 1619 return; 1620 } 1621 if(cistrcmp(s, "header.fields") == 0) 1622 f->part = FPHeadFields; 1623 else if(cistrcmp(s, "header.fields.not") == 0) 1624 f->part = FPHeadFieldsNot; 1625 else 1626 parseErr("illegal fetch section text"); 1627 mustBe(' '); 1628 mustBe('('); 1629 h = nil; 1630 for(;;){ 1631 h = mkSList(astring(), h); 1632 if(peekc() == ')') 1633 break; 1634 mustBe(' '); 1635 } 1636 mustBe(')'); 1637 f->hdrs = revSList(h); 1638 } 1639 1640 /* 1641 * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys 1642 * searchkeys : searchkey | searchkeys ' ' searchkey 1643 * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT" 1644 * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT" 1645 * | astrkey ' ' astring 1646 * | datekey ' ' date 1647 * | "KEYWORD" ' ' flag | "UNKEYWORD" flag 1648 * | "LARGER" ' ' number | "SMALLER" ' ' number 1649 * | "HEADER" astring ' ' astring 1650 * | set | "UID" ' ' set 1651 * | "NOT" ' ' searchkey 1652 * | "OR" ' ' searchkey ' ' searchkey 1653 * | '(' searchkeys ')' 1654 * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO" 1655 * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE" 1656 */ 1657 static NamedInt searchMap[] = 1658 { 1659 {"ALL", SKAll}, 1660 {"ANSWERED", SKAnswered}, 1661 {"DELETED", SKDeleted}, 1662 {"FLAGGED", SKFlagged}, 1663 {"NEW", SKNew}, 1664 {"OLD", SKOld}, 1665 {"RECENT", SKRecent}, 1666 {"SEEN", SKSeen}, 1667 {"UNANSWERED", SKUnanswered}, 1668 {"UNDELETED", SKUndeleted}, 1669 {"UNFLAGGED", SKUnflagged}, 1670 {"DRAFT", SKDraft}, 1671 {"UNDRAFT", SKUndraft}, 1672 {"UNSEEN", SKUnseen}, 1673 {nil, 0} 1674 }; 1675 1676 static NamedInt searchMapStr[] = 1677 { 1678 {"CHARSET", SKCharset}, 1679 {"BCC", SKBcc}, 1680 {"BODY", SKBody}, 1681 {"CC", SKCc}, 1682 {"FROM", SKFrom}, 1683 {"SUBJECT", SKSubject}, 1684 {"TEXT", SKText}, 1685 {"TO", SKTo}, 1686 {nil, 0} 1687 }; 1688 1689 static NamedInt searchMapDate[] = 1690 { 1691 {"BEFORE", SKBefore}, 1692 {"ON", SKOn}, 1693 {"SINCE", SKSince}, 1694 {"SENTBEFORE", SKSentBefore}, 1695 {"SENTON", SKSentOn}, 1696 {"SENTSINCE", SKSentSince}, 1697 {nil, 0} 1698 }; 1699 1700 static NamedInt searchMapFlag[] = 1701 { 1702 {"KEYWORD", SKKeyword}, 1703 {"UNKEYWORD", SKUnkeyword}, 1704 {nil, 0} 1705 }; 1706 1707 static NamedInt searchMapNum[] = 1708 { 1709 {"SMALLER", SKSmaller}, 1710 {"LARGER", SKLarger}, 1711 {nil, 0} 1712 }; 1713 1714 static Search* 1715 searchKeys(int first, Search *tail) 1716 { 1717 Search *s; 1718 1719 for(;;){ 1720 if(peekc() == '('){ 1721 getc(); 1722 tail = searchKeys(0, tail); 1723 mustBe(')'); 1724 }else{ 1725 s = searchKey(first); 1726 tail->next = s; 1727 tail = s; 1728 } 1729 first = 0; 1730 if(peekc() != ' ') 1731 break; 1732 getc(); 1733 } 1734 return tail; 1735 } 1736 1737 static Search* 1738 searchKey(int first) 1739 { 1740 Search *sr, rock; 1741 Tm tm; 1742 char *a; 1743 int i, c; 1744 1745 sr = binalloc(&parseBin, sizeof(Search), 1); 1746 if(sr == nil) 1747 parseErr("out of memory"); 1748 1749 c = peekc(); 1750 if(c >= '0' && c <= '9'){ 1751 sr->key = SKSet; 1752 sr->set = msgSet(0); 1753 return sr; 1754 } 1755 1756 a = atom(); 1757 if(i = mapInt(searchMap, a)) 1758 sr->key = i; 1759 else if(i = mapInt(searchMapStr, a)){ 1760 if(!first && i == SKCharset) 1761 parseErr("illegal search key"); 1762 sr->key = i; 1763 mustBe(' '); 1764 sr->s = astring(); 1765 }else if(i = mapInt(searchMapDate, a)){ 1766 sr->key = i; 1767 mustBe(' '); 1768 c = peekc(); 1769 if(c == '"') 1770 getc(); 1771 a = atom(); 1772 if(!imap4Date(&tm, a)) 1773 parseErr("bad date format"); 1774 sr->year = tm.year; 1775 sr->mon = tm.mon; 1776 sr->mday = tm.mday; 1777 if(c == '"') 1778 mustBe('"'); 1779 }else if(i = mapInt(searchMapFlag, a)){ 1780 sr->key = i; 1781 mustBe(' '); 1782 c = peekc(); 1783 if(c == '\\'){ 1784 mustBe('\\'); 1785 a = atomString(atomStop, "\\"); 1786 }else 1787 a = atom(); 1788 i = mapFlag(a); 1789 if(i == 0) 1790 parseErr("flag not supported"); 1791 sr->num = i; 1792 }else if(i = mapInt(searchMapNum, a)){ 1793 sr->key = i; 1794 mustBe(' '); 1795 sr->num = number(0); 1796 }else if(cistrcmp(a, "HEADER") == 0){ 1797 sr->key = SKHeader; 1798 mustBe(' '); 1799 sr->hdr = astring(); 1800 mustBe(' '); 1801 sr->s = astring(); 1802 }else if(cistrcmp(a, "UID") == 0){ 1803 sr->key = SKUid; 1804 mustBe(' '); 1805 sr->set = msgSet(0); 1806 }else if(cistrcmp(a, "NOT") == 0){ 1807 sr->key = SKNot; 1808 mustBe(' '); 1809 rock.next = nil; 1810 searchKeys(0, &rock); 1811 sr->left = rock.next; 1812 }else if(cistrcmp(a, "OR") == 0){ 1813 sr->key = SKOr; 1814 mustBe(' '); 1815 rock.next = nil; 1816 searchKeys(0, &rock); 1817 sr->left = rock.next; 1818 mustBe(' '); 1819 rock.next = nil; 1820 searchKeys(0, &rock); 1821 sr->right = rock.next; 1822 }else 1823 parseErr("illegal search key"); 1824 return sr; 1825 } 1826 1827 /* 1828 * set : seqno 1829 * | seqno ':' seqno 1830 * | set ',' set 1831 * seqno: nz-number 1832 * | '*' 1833 * 1834 */ 1835 static MsgSet* 1836 msgSet(int uids) 1837 { 1838 MsgSet head, *last, *ms; 1839 ulong from, to; 1840 1841 last = &head; 1842 head.next = nil; 1843 for(;;){ 1844 from = uids ? uidNo() : seqNo(); 1845 to = from; 1846 if(peekc() == ':'){ 1847 getc(); 1848 to = uids ? uidNo() : seqNo(); 1849 } 1850 ms = binalloc(&parseBin, sizeof(MsgSet), 0); 1851 if(ms == nil) 1852 parseErr("out of memory"); 1853 ms->from = from; 1854 ms->to = to; 1855 ms->next = nil; 1856 last->next = ms; 1857 last = ms; 1858 if(peekc() != ',') 1859 break; 1860 getc(); 1861 } 1862 return head.next; 1863 } 1864 1865 static ulong 1866 seqNo(void) 1867 { 1868 if(peekc() == '*'){ 1869 getc(); 1870 return ~0UL; 1871 } 1872 return number(1); 1873 } 1874 1875 static ulong 1876 uidNo(void) 1877 { 1878 if(peekc() == '*'){ 1879 getc(); 1880 return ~0UL; 1881 } 1882 return number(0); 1883 } 1884 1885 /* 1886 * 7 bit, non-ctl chars, no (){%*"\ 1887 * NIL is special case for nstring or parenlist 1888 */ 1889 static char * 1890 atom(void) 1891 { 1892 return atomString(atomStop, ""); 1893 } 1894 1895 /* 1896 * like an atom, but no + 1897 */ 1898 static char * 1899 tag(void) 1900 { 1901 return atomString("+(){%*\"\\", ""); 1902 } 1903 1904 /* 1905 * string or atom allowing %* 1906 */ 1907 static char * 1908 listmbox(void) 1909 { 1910 int c; 1911 1912 c = peekc(); 1913 if(c == '{') 1914 return literal(); 1915 if(c == '"') 1916 return quoted(); 1917 return atomString("(){\"\\", ""); 1918 } 1919 1920 /* 1921 * string or atom 1922 */ 1923 static char * 1924 astring(void) 1925 { 1926 int c; 1927 1928 c = peekc(); 1929 if(c == '{') 1930 return literal(); 1931 if(c == '"') 1932 return quoted(); 1933 return atom(); 1934 } 1935 1936 /* 1937 * 7 bit, non-ctl chars, none from exception list 1938 */ 1939 static char * 1940 atomString(char *disallowed, char *initial) 1941 { 1942 char *s; 1943 int c, ns, as; 1944 1945 ns = strlen(initial); 1946 s = binalloc(&parseBin, ns + StrAlloc, 0); 1947 if(s == nil) 1948 parseErr("out of memory"); 1949 strcpy(s, initial); 1950 as = ns + StrAlloc; 1951 for(;;){ 1952 c = getc(); 1953 if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){ 1954 ungetc(); 1955 break; 1956 } 1957 s[ns++] = c; 1958 if(ns >= as){ 1959 s = bingrow(&parseBin, s, as, as + StrAlloc, 0); 1960 if(s == nil) 1961 parseErr("out of memory"); 1962 as += StrAlloc; 1963 } 1964 } 1965 if(ns == 0) 1966 badsyn(); 1967 s[ns] = '\0'; 1968 return s; 1969 } 1970 1971 /* 1972 * quoted: '"' chars* '"' 1973 * chars: 1-128 except \r and \n 1974 */ 1975 static char * 1976 quoted(void) 1977 { 1978 char *s; 1979 int c, ns, as; 1980 1981 mustBe('"'); 1982 s = binalloc(&parseBin, StrAlloc, 0); 1983 if(s == nil) 1984 parseErr("out of memory"); 1985 as = StrAlloc; 1986 ns = 0; 1987 for(;;){ 1988 c = getc(); 1989 if(c == '"') 1990 break; 1991 if(c < 1 || c > 0x7f || c == '\r' || c == '\n') 1992 badsyn(); 1993 if(c == '\\'){ 1994 c = getc(); 1995 if(c != '\\' && c != '"') 1996 badsyn(); 1997 } 1998 s[ns++] = c; 1999 if(ns >= as){ 2000 s = bingrow(&parseBin, s, as, as + StrAlloc, 0); 2001 if(s == nil) 2002 parseErr("out of memory"); 2003 as += StrAlloc; 2004 } 2005 } 2006 s[ns] = '\0'; 2007 return s; 2008 } 2009 2010 /* 2011 * litlen: {number}\r\n 2012 */ 2013 static ulong 2014 litlen(void) 2015 { 2016 ulong v; 2017 2018 mustBe('{'); 2019 v = number(0); 2020 mustBe('}'); 2021 crnl(); 2022 return v; 2023 } 2024 2025 /* 2026 * literal: litlen data<0:litlen> 2027 */ 2028 static char * 2029 literal(void) 2030 { 2031 char *s; 2032 ulong v; 2033 2034 v = litlen(); 2035 s = binalloc(&parseBin, v+1, 0); 2036 if(s == nil) 2037 parseErr("out of memory"); 2038 Bprint(&bout, "+ Ready for literal data\r\n"); 2039 if(Bflush(&bout) < 0) 2040 writeErr(); 2041 if(v != 0 && Bread(&bin, s, v) != v) 2042 badsyn(); 2043 s[v] = '\0'; 2044 return s; 2045 } 2046 2047 /* 2048 * digits; number is 32 bits 2049 */ 2050 static ulong 2051 number(int nonzero) 2052 { 2053 ulong v; 2054 int c, first; 2055 2056 v = 0; 2057 first = 1; 2058 for(;;){ 2059 c = getc(); 2060 if(c < '0' || c > '9'){ 2061 ungetc(); 2062 if(first) 2063 badsyn(); 2064 break; 2065 } 2066 if(nonzero && first && c == '0') 2067 badsyn(); 2068 c -= '0'; 2069 first = 0; 2070 if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10) 2071 parseErr("number out of range\r\n"); 2072 v = v * 10 + c; 2073 } 2074 return v; 2075 } 2076 2077 static int 2078 getc(void) 2079 { 2080 return Bgetc(&bin); 2081 } 2082 2083 static void 2084 ungetc(void) 2085 { 2086 Bungetc(&bin); 2087 } 2088 2089 static int 2090 peekc(void) 2091 { 2092 int c; 2093 2094 c = Bgetc(&bin); 2095 Bungetc(&bin); 2096 return c; 2097 } 2098 2099