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(cistrcmp(mbox, "inbox") == 0) 608 mbox = "mbox"; 609 if(!cdExists(mboxDir, mbox)){ 610 check(); 611 Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd); 612 return; 613 } 614 615 max = selected->max; 616 checkBox(selected, 0); 617 ok = forMsgs(selected, ms, max, uids, copyCheck, nil); 618 if(ok) 619 ok = forMsgs(selected, ms, max, uids, copySave, mbox); 620 621 status(1, uids); 622 if(ok) 623 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 624 else 625 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 626 } 627 628 static void 629 createCmd(char *tg, char *cmd) 630 { 631 char *mbox, *m; 632 int fd, slash; 633 634 mustBe(' '); 635 mbox = astring(); 636 crnl(); 637 check(); 638 639 m = strchr(mbox, '\0'); 640 slash = m != mbox && m[-1] == '/'; 641 mbox = mboxName(mbox); 642 if(mbox == nil || !okMbox(mbox)){ 643 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 644 return; 645 } 646 if(cistrcmp(mbox, "inbox") == 0){ 647 Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd); 648 return; 649 } 650 if(access(mbox, AEXIST) >= 0){ 651 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd); 652 return; 653 } 654 655 fd = createBox(mbox, slash); 656 close(fd); 657 if(fd < 0) 658 Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox); 659 else 660 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd); 661 } 662 663 static void 664 deleteCmd(char *tg, char *cmd) 665 { 666 char *mbox, *imp; 667 668 mustBe(' '); 669 mbox = astring(); 670 crnl(); 671 check(); 672 673 mbox = mboxName(mbox); 674 if(mbox == nil || !okMbox(mbox)){ 675 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 676 return; 677 } 678 679 imp = impName(mbox); 680 if(cistrcmp(mbox, "inbox") == 0 681 || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp) 682 || cdRemove(mboxDir, mbox) < 0) 683 Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox); 684 else 685 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd); 686 } 687 688 static void 689 expungeCmd(char *tg, char *cmd) 690 { 691 int ok; 692 693 crnl(); 694 ok = deleteMsgs(selected); 695 check(); 696 if(ok) 697 Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd); 698 else 699 Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd); 700 } 701 702 static void 703 fetchCmd(char *tg, char *cmd) 704 { 705 fetchUCmd(tg, cmd, 0); 706 } 707 708 static void 709 fetchUCmd(char *tg, char *cmd, int uids) 710 { 711 Fetch *f; 712 MsgSet *ms; 713 MbLock *ml; 714 char *uid; 715 ulong max; 716 int ok; 717 718 mustBe(' '); 719 ms = msgSet(uids); 720 mustBe(' '); 721 f = fetchWhat(); 722 crnl(); 723 uid = ""; 724 if(uids) 725 uid = "uid "; 726 max = selected->max; 727 ml = checkBox(selected, 1); 728 if(ml != nil) 729 forMsgs(selected, ms, max, uids, fetchSeen, f); 730 closeImp(selected, ml); 731 ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f); 732 status(uids, uids); 733 if(ok) 734 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 735 else 736 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 737 } 738 739 static void 740 idleCmd(char *tg, char *cmd) 741 { 742 int c, pid; 743 744 crnl(); 745 Bprint(&bout, "+ idling, waiting for done\r\n"); 746 if(Bflush(&bout) < 0) 747 writeErr(); 748 749 if(idlepid < 0){ 750 pid = rfork(RFPROC|RFMEM|RFNOWAIT); 751 if(pid == 0){ 752 for(;;){ 753 qlock(&imaplock); 754 if(exiting) 755 break; 756 757 /* 758 * parent may have changed curDir, but it doesn't change our . 759 */ 760 resetCurDir(); 761 762 check(); 763 if(Bflush(&bout) < 0) 764 writeErr(); 765 qunlock(&imaplock); 766 sleep(15*1000); 767 enableForwarding(); 768 } 769 _exits("rob3"); 770 _exits(0); 771 } 772 idlepid = pid; 773 } 774 775 qunlock(&imaplock); 776 777 /* 778 * clear out the next line, which is supposed to contain (case-insensitive) 779 * done\n 780 * this is special code since it has to dance with the idle polling proc 781 * and handle exiting correctly. 782 */ 783 for(;;){ 784 c = getc(); 785 if(c < 0){ 786 qlock(&imaplock); 787 if(!exiting) 788 cleaner(); 789 _exits("rob4"); 790 _exits(0); 791 } 792 if(c == '\n') 793 break; 794 } 795 796 qlock(&imaplock); 797 if(exiting) 798 {_exits("rob5"); 799 _exits(0); 800 } 801 802 /* 803 * child may have changed curDir, but it doesn't change our . 804 */ 805 resetCurDir(); 806 807 check(); 808 Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd); 809 } 810 811 static void 812 listCmd(char *tg, char *cmd) 813 { 814 char *s, *t, *ss, *ref, *mbox; 815 int n; 816 817 mustBe(' '); 818 s = astring(); 819 mustBe(' '); 820 t = listmbox(); 821 crnl(); 822 check(); 823 ref = mutf7str(s); 824 mbox = mutf7str(t); 825 if(ref == nil || mbox == nil){ 826 Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd); 827 return; 828 } 829 830 /* 831 * special request for hierarchy delimiter and root name 832 * root name appears to be name up to and including any delimiter, 833 * or the empty string, if there is no delimiter. 834 * 835 * this must change if the # namespace convention is supported. 836 */ 837 if(*mbox == '\0'){ 838 s = strchr(ref, '/'); 839 if(s == nil) 840 ref = ""; 841 else 842 s[1] = '\0'; 843 Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref); 844 Bprint(&bout, "%s OK %s\r\n", tg, cmd); 845 return; 846 } 847 848 849 /* 850 * massage the listing name: 851 * clean up the components individually, 852 * then rip off componenets from the ref to 853 * take care of leading ..'s in the mbox. 854 * 855 * the cleanup can wipe out * followed by a .. 856 * tough luck if such a stupid pattern is given. 857 */ 858 cleanname(mbox); 859 if(strcmp(mbox, ".") == 0) 860 *mbox = '\0'; 861 if(mbox[0] == '/') 862 *ref = '\0'; 863 else if(*ref != '\0'){ 864 cleanname(ref); 865 if(strcmp(ref, ".") == 0) 866 *ref = '\0'; 867 }else 868 *ref = '\0'; 869 while(*ref && isdotdot(mbox)){ 870 s = strrchr(ref, '/'); 871 if(s == nil) 872 s = ref; 873 if(isdotdot(s)) 874 break; 875 *s = '\0'; 876 mbox += 2; 877 if(*mbox == '/') 878 mbox++; 879 } 880 if(*ref == '\0'){ 881 s = mbox; 882 ss = s; 883 }else{ 884 n = strlen(ref) + strlen(mbox) + 2; 885 t = binalloc(&parseBin, n, 0); 886 if(t == nil) 887 parseErr("out of memory"); 888 snprint(t, n, "%s/%s", ref, mbox); 889 s = t; 890 ss = s + strlen(ref); 891 } 892 893 /* 894 * only allow activity in /mail/box 895 */ 896 if(s[0] == '/' || isdotdot(s)){ 897 Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg); 898 return; 899 } 900 901 if(cistrcmp(cmd, "lsub") == 0) 902 lsubBoxes(cmd, s, ss); 903 else 904 listBoxes(cmd, s, ss); 905 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 906 } 907 908 static char* 909 passCR(char*u, char*p) 910 { 911 static char Ebadch[] = "can't get challenge"; 912 static char nchall[64]; 913 static char response[64]; 914 static Chalstate *ch = nil; 915 AuthInfo *ai; 916 917 again: 918 if (ch == nil){ 919 if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u))) 920 return Ebadch; 921 snprint(nchall, 64, " encrypt challenge: %s", ch->chal); 922 return nchall; 923 } else { 924 strncpy(response, p, 64); 925 ch->resp = response; 926 ch->nresp = strlen(response); 927 ai = auth_response(ch); 928 auth_freechal(ch); 929 ch = nil; 930 if (ai == nil) 931 goto again; 932 setupuser(ai); 933 return nil; 934 } 935 936 } 937 938 static void 939 loginCmd(char *tg, char *cmd) 940 { 941 char *s, *t; 942 AuthInfo *ai; 943 char*r; 944 mustBe(' '); 945 s = astring(); /* uid */ 946 mustBe(' '); 947 t = astring(); /* password */ 948 crnl(); 949 if(allowCR){ 950 if ((r = passCR(s, t)) == nil){ 951 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd); 952 imapState = SAuthed; 953 } else { 954 Bprint(&bout, "* NO [ALERT] %s\r\n", r); 955 Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd); 956 } 957 return; 958 } 959 else if(allowPass){ 960 if(ai = passLogin(s, t)){ 961 setupuser(ai); 962 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd); 963 imapState = SAuthed; 964 }else 965 Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd); 966 return; 967 } 968 Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd); 969 } 970 971 /* 972 * logout or x-exit, which doesn't expunge the mailbox 973 */ 974 static void 975 logoutCmd(char *tg, char *cmd) 976 { 977 crnl(); 978 979 if(cmd[0] != 'x' && selected){ 980 closeBox(selected, 1); 981 selected = nil; 982 } 983 Bprint(&bout, "* bye\r\n"); 984 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 985 exits("rob6"); 986 exits(0); 987 } 988 989 static void 990 namespaceCmd(char *tg, char *cmd) 991 { 992 crnl(); 993 check(); 994 995 /* 996 * personal, other users, shared namespaces 997 * send back nil or descriptions of (prefix heirarchy-delim) for each case 998 */ 999 Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n"); 1000 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1001 } 1002 1003 static void 1004 noopCmd(char *tg, char *cmd) 1005 { 1006 crnl(); 1007 check(); 1008 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1009 enableForwarding(); 1010 } 1011 1012 /* 1013 * this is only a partial implementation 1014 * should copy files to other directories, 1015 * and copy & truncate inbox 1016 */ 1017 static void 1018 renameCmd(char *tg, char *cmd) 1019 { 1020 char *from, *to; 1021 int ok; 1022 1023 mustBe(' '); 1024 from = astring(); 1025 mustBe(' '); 1026 to = astring(); 1027 crnl(); 1028 check(); 1029 1030 to = mboxName(to); 1031 if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){ 1032 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd); 1033 return; 1034 } 1035 if(access(to, AEXIST) >= 0){ 1036 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd); 1037 return; 1038 } 1039 from = mboxName(from); 1040 if(from == nil || !okMbox(from)){ 1041 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd); 1042 return; 1043 } 1044 if(cistrcmp(from, "inbox") == 0) 1045 ok = copyBox(from, to, 0); 1046 else 1047 ok = moveBox(from, to); 1048 1049 if(ok) 1050 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1051 else 1052 Bprint(&bout, "%s NO %s failed\r\n", tg, cmd); 1053 } 1054 1055 static void 1056 searchCmd(char *tg, char *cmd) 1057 { 1058 searchUCmd(tg, cmd, 0); 1059 } 1060 1061 static void 1062 searchUCmd(char *tg, char *cmd, int uids) 1063 { 1064 Search rock; 1065 Msg *m; 1066 char *uid; 1067 ulong id; 1068 1069 mustBe(' '); 1070 rock.next = nil; 1071 searchKeys(1, &rock); 1072 crnl(); 1073 uid = ""; 1074 if(uids) 1075 uid = "uid "; 1076 if(rock.next != nil && rock.next->key == SKCharset){ 1077 if(cistrstr(rock.next->s, "utf-8") != 0 1078 && cistrcmp(rock.next->s, "us-ascii") != 0){ 1079 Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd); 1080 checkBox(selected, 0); 1081 status(uids, uids); 1082 return; 1083 } 1084 rock.next = rock.next->next; 1085 } 1086 Bprint(&bout, "* search"); 1087 for(m = selected->msgs; m != nil; m = m->next) 1088 m->matched = searchMsg(m, rock.next); 1089 for(m = selected->msgs; m != nil; m = m->next){ 1090 if(m->matched){ 1091 if(uids) 1092 id = m->uid; 1093 else 1094 id = m->seq; 1095 Bprint(&bout, " %lud", id); 1096 } 1097 } 1098 Bprint(&bout, "\r\n"); 1099 checkBox(selected, 0); 1100 status(uids, uids); 1101 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 1102 } 1103 1104 static void 1105 selectCmd(char *tg, char *cmd) 1106 { 1107 Msg *m; 1108 char *s, *mbox; 1109 1110 mustBe(' '); 1111 mbox = astring(); 1112 crnl(); 1113 1114 if(selected){ 1115 imapState = SAuthed; 1116 closeBox(selected, 1); 1117 selected = nil; 1118 } 1119 1120 mbox = mboxName(mbox); 1121 if(mbox == nil || !okMbox(mbox)){ 1122 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1123 return; 1124 } 1125 1126 selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0); 1127 if(selected == nil){ 1128 Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); 1129 return; 1130 } 1131 1132 imapState = SSelected; 1133 1134 Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n"); 1135 Bprint(&bout, "* %lud EXISTS\r\n", selected->max); 1136 selected->toldMax = selected->max; 1137 Bprint(&bout, "* %lud RECENT\r\n", selected->recent); 1138 selected->toldRecent = selected->recent; 1139 for(m = selected->msgs; m != nil; m = m->next){ 1140 if(!m->expunged && (m->flags & MSeen) != MSeen){ 1141 Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq); 1142 break; 1143 } 1144 } 1145 Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n"); 1146 Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext); 1147 Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity); 1148 s = "READ-ONLY"; 1149 if(selected->writable) 1150 s = "READ-WRITE"; 1151 Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox); 1152 } 1153 1154 static NamedInt statusItems[] = 1155 { 1156 {"MESSAGES", SMessages}, 1157 {"RECENT", SRecent}, 1158 {"UIDNEXT", SUidNext}, 1159 {"UIDVALIDITY", SUidValidity}, 1160 {"UNSEEN", SUnseen}, 1161 {nil, 0} 1162 }; 1163 1164 static void 1165 statusCmd(char *tg, char *cmd) 1166 { 1167 Box *box; 1168 Msg *m; 1169 char *s, *mbox; 1170 ulong v; 1171 int si, i; 1172 1173 mustBe(' '); 1174 mbox = astring(); 1175 mustBe(' '); 1176 mustBe('('); 1177 si = 0; 1178 for(;;){ 1179 s = atom(); 1180 i = mapInt(statusItems, s); 1181 if(i == 0) 1182 parseErr("illegal status item"); 1183 si |= i; 1184 if(peekc() == ')') 1185 break; 1186 mustBe(' '); 1187 } 1188 mustBe(')'); 1189 crnl(); 1190 1191 mbox = mboxName(mbox); 1192 if(mbox == nil || !okMbox(mbox)){ 1193 check(); 1194 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1195 return; 1196 } 1197 1198 box = openBox(mbox, "status", 1); 1199 if(box == nil){ 1200 check(); 1201 Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); 1202 return; 1203 } 1204 1205 Bprint(&bout, "* STATUS %s (", mbox); 1206 s = ""; 1207 for(i = 0; statusItems[i].name != nil; i++){ 1208 if(si & statusItems[i].v){ 1209 v = 0; 1210 switch(statusItems[i].v){ 1211 case SMessages: 1212 v = box->max; 1213 break; 1214 case SRecent: 1215 v = box->recent; 1216 break; 1217 case SUidNext: 1218 v = box->uidnext; 1219 break; 1220 case SUidValidity: 1221 v = box->uidvalidity; 1222 break; 1223 case SUnseen: 1224 v = 0; 1225 for(m = box->msgs; m != nil; m = m->next) 1226 if((m->flags & MSeen) != MSeen) 1227 v++; 1228 break; 1229 default: 1230 Bprint(&bout, ")"); 1231 bye("internal error: status item not implemented"); 1232 break; 1233 } 1234 Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v); 1235 s = " "; 1236 } 1237 } 1238 Bprint(&bout, ")\r\n"); 1239 closeBox(box, 1); 1240 1241 check(); 1242 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1243 } 1244 1245 static void 1246 storeCmd(char *tg, char *cmd) 1247 { 1248 storeUCmd(tg, cmd, 0); 1249 } 1250 1251 static void 1252 storeUCmd(char *tg, char *cmd, int uids) 1253 { 1254 Store *st; 1255 MsgSet *ms; 1256 MbLock *ml; 1257 char *uid; 1258 ulong max; 1259 int ok; 1260 1261 mustBe(' '); 1262 ms = msgSet(uids); 1263 mustBe(' '); 1264 st = storeWhat(); 1265 crnl(); 1266 uid = ""; 1267 if(uids) 1268 uid = "uid "; 1269 max = selected->max; 1270 ml = checkBox(selected, 1); 1271 ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st); 1272 closeImp(selected, ml); 1273 status(uids, uids); 1274 if(ok) 1275 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); 1276 else 1277 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd); 1278 } 1279 1280 /* 1281 * minimal implementation of subscribe 1282 * all folders are automatically subscribed, 1283 * and can't be unsubscribed 1284 */ 1285 static void 1286 subscribeCmd(char *tg, char *cmd) 1287 { 1288 Box *box; 1289 char *mbox; 1290 int ok; 1291 1292 mustBe(' '); 1293 mbox = astring(); 1294 crnl(); 1295 check(); 1296 mbox = mboxName(mbox); 1297 ok = 0; 1298 if(mbox != nil && okMbox(mbox)){ 1299 box = openBox(mbox, "subscribe", 0); 1300 if(box != nil){ 1301 ok = subscribe(mbox, 's'); 1302 closeBox(box, 1); 1303 } 1304 } 1305 if(!ok) 1306 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); 1307 else 1308 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1309 } 1310 1311 static void 1312 uidCmd(char *tg, char *cmd) 1313 { 1314 char *sub; 1315 1316 mustBe(' '); 1317 sub = atom(); 1318 if(cistrcmp(sub, "copy") == 0) 1319 copyUCmd(tg, sub, 1); 1320 else if(cistrcmp(sub, "fetch") == 0) 1321 fetchUCmd(tg, sub, 1); 1322 else if(cistrcmp(sub, "search") == 0) 1323 searchUCmd(tg, sub, 1); 1324 else if(cistrcmp(sub, "store") == 0) 1325 storeUCmd(tg, sub, 1); 1326 else{ 1327 clearcmd(); 1328 Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub); 1329 } 1330 } 1331 1332 static void 1333 unsubscribeCmd(char *tg, char *cmd) 1334 { 1335 char *mbox; 1336 1337 mustBe(' '); 1338 mbox = astring(); 1339 crnl(); 1340 check(); 1341 mbox = mboxName(mbox); 1342 if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u')) 1343 Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd); 1344 else 1345 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); 1346 } 1347 1348 static void 1349 badsyn(void) 1350 { 1351 parseErr("bad syntax"); 1352 } 1353 1354 static void 1355 clearcmd(void) 1356 { 1357 int c; 1358 1359 for(;;){ 1360 c = getc(); 1361 if(c < 0) 1362 bye("end of input"); 1363 if(c == '\n') 1364 return; 1365 } 1366 } 1367 1368 static void 1369 crnl(void) 1370 { 1371 int c; 1372 1373 c = getc(); 1374 if(c == '\n') 1375 return; 1376 if(c != '\r' || getc() != '\n') 1377 badsyn(); 1378 } 1379 1380 static void 1381 mustBe(int c) 1382 { 1383 if(getc() != c){ 1384 ungetc(); 1385 badsyn(); 1386 } 1387 } 1388 1389 /* 1390 * flaglist : '(' ')' | '(' flags ')' 1391 */ 1392 static int 1393 flagList(void) 1394 { 1395 int f; 1396 1397 mustBe('('); 1398 f = 0; 1399 if(peekc() != ')') 1400 f = flags(); 1401 1402 mustBe(')'); 1403 return f; 1404 } 1405 1406 /* 1407 * flags : flag | flags ' ' flag 1408 * flag : '\' atom | atom 1409 */ 1410 static int 1411 flags(void) 1412 { 1413 int ff, flags; 1414 char *s; 1415 int c; 1416 1417 flags = 0; 1418 for(;;){ 1419 c = peekc(); 1420 if(c == '\\'){ 1421 mustBe('\\'); 1422 s = atomString(atomStop, "\\"); 1423 }else if(strchr(atomStop, c) != nil) 1424 s = atom(); 1425 else 1426 break; 1427 ff = mapFlag(s); 1428 if(ff == 0) 1429 parseErr("flag not supported"); 1430 flags |= ff; 1431 if(peekc() != ' ') 1432 break; 1433 mustBe(' '); 1434 } 1435 if(flags == 0) 1436 parseErr("no flags given"); 1437 return flags; 1438 } 1439 1440 /* 1441 * storeWhat : osign 'FLAGS' ' ' storeflags 1442 * | osign 'FLAGS.SILENT' ' ' storeflags 1443 * osign : 1444 * | '+' | '-' 1445 * storeflags : flagList | flags 1446 */ 1447 static Store* 1448 storeWhat(void) 1449 { 1450 int f; 1451 char *s; 1452 int c, w; 1453 1454 c = peekc(); 1455 if(c == '+' || c == '-') 1456 mustBe(c); 1457 else 1458 c = 0; 1459 s = atom(); 1460 w = 0; 1461 if(cistrcmp(s, "flags") == 0) 1462 w = STFlags; 1463 else if(cistrcmp(s, "flags.silent") == 0) 1464 w = STFlagsSilent; 1465 else 1466 parseErr("illegal store attribute"); 1467 mustBe(' '); 1468 if(peekc() == '(') 1469 f = flagList(); 1470 else 1471 f = flags(); 1472 return mkStore(c, w, f); 1473 } 1474 1475 /* 1476 * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')' 1477 * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt 1478 */ 1479 static char *fetchAtom = "(){}%*\"\\[]"; 1480 static Fetch* 1481 fetchWhat(void) 1482 { 1483 Fetch *f; 1484 char *s; 1485 1486 if(peekc() == '('){ 1487 getc(); 1488 f = nil; 1489 for(;;){ 1490 s = atomString(fetchAtom, ""); 1491 f = fetchAtt(s, f); 1492 if(peekc() == ')') 1493 break; 1494 mustBe(' '); 1495 } 1496 getc(); 1497 return revFetch(f); 1498 } 1499 1500 s = atomString(fetchAtom, ""); 1501 if(cistrcmp(s, "all") == 0) 1502 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil)))); 1503 else if(cistrcmp(s, "fast") == 0) 1504 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil))); 1505 else if(cistrcmp(s, "full") == 0) 1506 f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil))))); 1507 else 1508 f = fetchAtt(s, nil); 1509 return f; 1510 } 1511 1512 /* 1513 * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE" 1514 * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT" 1515 * | "BODYSTRUCTURE" 1516 * | "UID" 1517 * | "BODY" 1518 * | "BODY" bodysubs 1519 * | "BODY.PEEK" bodysubs 1520 * bodysubs : sect 1521 * | sect '<' number '.' nz-number '>' 1522 * sect : '[' sectSpec ']' 1523 * sectSpec : sectMsgText 1524 * | sectPart 1525 * | sectPart '.' sectText 1526 * sectPart : nz-number 1527 * | sectPart '.' nz-number 1528 */ 1529 static Fetch* 1530 fetchAtt(char *s, Fetch *f) 1531 { 1532 NList *sect; 1533 int c; 1534 1535 if(cistrcmp(s, "envelope") == 0) 1536 return mkFetch(FEnvelope, f); 1537 if(cistrcmp(s, "flags") == 0) 1538 return mkFetch(FFlags, f); 1539 if(cistrcmp(s, "internaldate") == 0) 1540 return mkFetch(FInternalDate, f); 1541 if(cistrcmp(s, "RFC822") == 0) 1542 return mkFetch(FRfc822, f); 1543 if(cistrcmp(s, "RFC822.header") == 0) 1544 return mkFetch(FRfc822Head, f); 1545 if(cistrcmp(s, "RFC822.size") == 0) 1546 return mkFetch(FRfc822Size, f); 1547 if(cistrcmp(s, "RFC822.text") == 0) 1548 return mkFetch(FRfc822Text, f); 1549 if(cistrcmp(s, "bodystructure") == 0) 1550 return mkFetch(FBodyStruct, f); 1551 if(cistrcmp(s, "uid") == 0) 1552 return mkFetch(FUid, f); 1553 1554 if(cistrcmp(s, "body") == 0){ 1555 if(peekc() != '[') 1556 return mkFetch(FBody, f); 1557 f = mkFetch(FBodySect, f); 1558 }else if(cistrcmp(s, "body.peek") == 0) 1559 f = mkFetch(FBodyPeek, f); 1560 else 1561 parseErr("illegal fetch attribute"); 1562 1563 mustBe('['); 1564 c = peekc(); 1565 if(c >= '1' && c <= '9'){ 1566 sect = mkNList(number(1), nil); 1567 while(peekc() == '.'){ 1568 getc(); 1569 c = peekc(); 1570 if(c >= '1' && c <= '9'){ 1571 sect = mkNList(number(1), sect); 1572 }else{ 1573 break; 1574 } 1575 } 1576 f->sect = revNList(sect); 1577 } 1578 if(peekc() != ']') 1579 sectText(f, f->sect != nil); 1580 mustBe(']'); 1581 1582 if(peekc() != '<') 1583 return f; 1584 1585 f->partial = 1; 1586 mustBe('<'); 1587 f->start = number(0); 1588 mustBe('.'); 1589 f->size = number(1); 1590 mustBe('>'); 1591 return f; 1592 } 1593 1594 /* 1595 * sectText : sectMsgText | "MIME" 1596 * sectMsgText : "HEADER" 1597 * | "TEXT" 1598 * | "HEADER.FIELDS" ' ' hdrList 1599 * | "HEADER.FIELDS.NOT" ' ' hdrList 1600 * hdrList : '(' hdrs ')' 1601 * hdrs: : astring 1602 * | hdrs ' ' astring 1603 */ 1604 static void 1605 sectText(Fetch *f, int mimeOk) 1606 { 1607 SList *h; 1608 char *s; 1609 1610 s = atomString(fetchAtom, ""); 1611 if(cistrcmp(s, "header") == 0){ 1612 f->part = FPHead; 1613 return; 1614 } 1615 if(cistrcmp(s, "text") == 0){ 1616 f->part = FPText; 1617 return; 1618 } 1619 if(mimeOk && cistrcmp(s, "mime") == 0){ 1620 f->part = FPMime; 1621 return; 1622 } 1623 if(cistrcmp(s, "header.fields") == 0) 1624 f->part = FPHeadFields; 1625 else if(cistrcmp(s, "header.fields.not") == 0) 1626 f->part = FPHeadFieldsNot; 1627 else 1628 parseErr("illegal fetch section text"); 1629 mustBe(' '); 1630 mustBe('('); 1631 h = nil; 1632 for(;;){ 1633 h = mkSList(astring(), h); 1634 if(peekc() == ')') 1635 break; 1636 mustBe(' '); 1637 } 1638 mustBe(')'); 1639 f->hdrs = revSList(h); 1640 } 1641 1642 /* 1643 * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys 1644 * searchkeys : searchkey | searchkeys ' ' searchkey 1645 * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT" 1646 * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT" 1647 * | astrkey ' ' astring 1648 * | datekey ' ' date 1649 * | "KEYWORD" ' ' flag | "UNKEYWORD" flag 1650 * | "LARGER" ' ' number | "SMALLER" ' ' number 1651 * | "HEADER" astring ' ' astring 1652 * | set | "UID" ' ' set 1653 * | "NOT" ' ' searchkey 1654 * | "OR" ' ' searchkey ' ' searchkey 1655 * | '(' searchkeys ')' 1656 * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO" 1657 * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE" 1658 */ 1659 static NamedInt searchMap[] = 1660 { 1661 {"ALL", SKAll}, 1662 {"ANSWERED", SKAnswered}, 1663 {"DELETED", SKDeleted}, 1664 {"FLAGGED", SKFlagged}, 1665 {"NEW", SKNew}, 1666 {"OLD", SKOld}, 1667 {"RECENT", SKRecent}, 1668 {"SEEN", SKSeen}, 1669 {"UNANSWERED", SKUnanswered}, 1670 {"UNDELETED", SKUndeleted}, 1671 {"UNFLAGGED", SKUnflagged}, 1672 {"DRAFT", SKDraft}, 1673 {"UNDRAFT", SKUndraft}, 1674 {"UNSEEN", SKUnseen}, 1675 {nil, 0} 1676 }; 1677 1678 static NamedInt searchMapStr[] = 1679 { 1680 {"CHARSET", SKCharset}, 1681 {"BCC", SKBcc}, 1682 {"BODY", SKBody}, 1683 {"CC", SKCc}, 1684 {"FROM", SKFrom}, 1685 {"SUBJECT", SKSubject}, 1686 {"TEXT", SKText}, 1687 {"TO", SKTo}, 1688 {nil, 0} 1689 }; 1690 1691 static NamedInt searchMapDate[] = 1692 { 1693 {"BEFORE", SKBefore}, 1694 {"ON", SKOn}, 1695 {"SINCE", SKSince}, 1696 {"SENTBEFORE", SKSentBefore}, 1697 {"SENTON", SKSentOn}, 1698 {"SENTSINCE", SKSentSince}, 1699 {nil, 0} 1700 }; 1701 1702 static NamedInt searchMapFlag[] = 1703 { 1704 {"KEYWORD", SKKeyword}, 1705 {"UNKEYWORD", SKUnkeyword}, 1706 {nil, 0} 1707 }; 1708 1709 static NamedInt searchMapNum[] = 1710 { 1711 {"SMALLER", SKSmaller}, 1712 {"LARGER", SKLarger}, 1713 {nil, 0} 1714 }; 1715 1716 static Search* 1717 searchKeys(int first, Search *tail) 1718 { 1719 Search *s; 1720 1721 for(;;){ 1722 if(peekc() == '('){ 1723 getc(); 1724 tail = searchKeys(0, tail); 1725 mustBe(')'); 1726 }else{ 1727 s = searchKey(first); 1728 tail->next = s; 1729 tail = s; 1730 } 1731 first = 0; 1732 if(peekc() != ' ') 1733 break; 1734 getc(); 1735 } 1736 return tail; 1737 } 1738 1739 static Search* 1740 searchKey(int first) 1741 { 1742 Search *sr, rock; 1743 Tm tm; 1744 char *a; 1745 int i, c; 1746 1747 sr = binalloc(&parseBin, sizeof(Search), 1); 1748 if(sr == nil) 1749 parseErr("out of memory"); 1750 1751 c = peekc(); 1752 if(c >= '0' && c <= '9'){ 1753 sr->key = SKSet; 1754 sr->set = msgSet(0); 1755 return sr; 1756 } 1757 1758 a = atom(); 1759 if(i = mapInt(searchMap, a)) 1760 sr->key = i; 1761 else if(i = mapInt(searchMapStr, a)){ 1762 if(!first && i == SKCharset) 1763 parseErr("illegal search key"); 1764 sr->key = i; 1765 mustBe(' '); 1766 sr->s = astring(); 1767 }else if(i = mapInt(searchMapDate, a)){ 1768 sr->key = i; 1769 mustBe(' '); 1770 c = peekc(); 1771 if(c == '"') 1772 getc(); 1773 a = atom(); 1774 if(!imap4Date(&tm, a)) 1775 parseErr("bad date format"); 1776 sr->year = tm.year; 1777 sr->mon = tm.mon; 1778 sr->mday = tm.mday; 1779 if(c == '"') 1780 mustBe('"'); 1781 }else if(i = mapInt(searchMapFlag, a)){ 1782 sr->key = i; 1783 mustBe(' '); 1784 c = peekc(); 1785 if(c == '\\'){ 1786 mustBe('\\'); 1787 a = atomString(atomStop, "\\"); 1788 }else 1789 a = atom(); 1790 i = mapFlag(a); 1791 if(i == 0) 1792 parseErr("flag not supported"); 1793 sr->num = i; 1794 }else if(i = mapInt(searchMapNum, a)){ 1795 sr->key = i; 1796 mustBe(' '); 1797 sr->num = number(0); 1798 }else if(cistrcmp(a, "HEADER") == 0){ 1799 sr->key = SKHeader; 1800 mustBe(' '); 1801 sr->hdr = astring(); 1802 mustBe(' '); 1803 sr->s = astring(); 1804 }else if(cistrcmp(a, "UID") == 0){ 1805 sr->key = SKUid; 1806 mustBe(' '); 1807 sr->set = msgSet(0); 1808 }else if(cistrcmp(a, "NOT") == 0){ 1809 sr->key = SKNot; 1810 mustBe(' '); 1811 rock.next = nil; 1812 searchKeys(0, &rock); 1813 sr->left = rock.next; 1814 }else if(cistrcmp(a, "OR") == 0){ 1815 sr->key = SKOr; 1816 mustBe(' '); 1817 rock.next = nil; 1818 searchKeys(0, &rock); 1819 sr->left = rock.next; 1820 mustBe(' '); 1821 rock.next = nil; 1822 searchKeys(0, &rock); 1823 sr->right = rock.next; 1824 }else 1825 parseErr("illegal search key"); 1826 return sr; 1827 } 1828 1829 /* 1830 * set : seqno 1831 * | seqno ':' seqno 1832 * | set ',' set 1833 * seqno: nz-number 1834 * | '*' 1835 * 1836 */ 1837 static MsgSet* 1838 msgSet(int uids) 1839 { 1840 MsgSet head, *last, *ms; 1841 ulong from, to; 1842 1843 last = &head; 1844 head.next = nil; 1845 for(;;){ 1846 from = uids ? uidNo() : seqNo(); 1847 to = from; 1848 if(peekc() == ':'){ 1849 getc(); 1850 to = uids ? uidNo() : seqNo(); 1851 } 1852 ms = binalloc(&parseBin, sizeof(MsgSet), 0); 1853 if(ms == nil) 1854 parseErr("out of memory"); 1855 ms->from = from; 1856 ms->to = to; 1857 ms->next = nil; 1858 last->next = ms; 1859 last = ms; 1860 if(peekc() != ',') 1861 break; 1862 getc(); 1863 } 1864 return head.next; 1865 } 1866 1867 static ulong 1868 seqNo(void) 1869 { 1870 if(peekc() == '*'){ 1871 getc(); 1872 return ~0UL; 1873 } 1874 return number(1); 1875 } 1876 1877 static ulong 1878 uidNo(void) 1879 { 1880 if(peekc() == '*'){ 1881 getc(); 1882 return ~0UL; 1883 } 1884 return number(0); 1885 } 1886 1887 /* 1888 * 7 bit, non-ctl chars, no (){%*"\ 1889 * NIL is special case for nstring or parenlist 1890 */ 1891 static char * 1892 atom(void) 1893 { 1894 return atomString(atomStop, ""); 1895 } 1896 1897 /* 1898 * like an atom, but no + 1899 */ 1900 static char * 1901 tag(void) 1902 { 1903 return atomString("+(){%*\"\\", ""); 1904 } 1905 1906 /* 1907 * string or atom allowing %* 1908 */ 1909 static char * 1910 listmbox(void) 1911 { 1912 int c; 1913 1914 c = peekc(); 1915 if(c == '{') 1916 return literal(); 1917 if(c == '"') 1918 return quoted(); 1919 return atomString("(){\"\\", ""); 1920 } 1921 1922 /* 1923 * string or atom 1924 */ 1925 static char * 1926 astring(void) 1927 { 1928 int c; 1929 1930 c = peekc(); 1931 if(c == '{') 1932 return literal(); 1933 if(c == '"') 1934 return quoted(); 1935 return atom(); 1936 } 1937 1938 /* 1939 * 7 bit, non-ctl chars, none from exception list 1940 */ 1941 static char * 1942 atomString(char *disallowed, char *initial) 1943 { 1944 char *s; 1945 int c, ns, as; 1946 1947 ns = strlen(initial); 1948 s = binalloc(&parseBin, ns + StrAlloc, 0); 1949 if(s == nil) 1950 parseErr("out of memory"); 1951 strcpy(s, initial); 1952 as = ns + StrAlloc; 1953 for(;;){ 1954 c = getc(); 1955 if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){ 1956 ungetc(); 1957 break; 1958 } 1959 s[ns++] = c; 1960 if(ns >= as){ 1961 s = bingrow(&parseBin, s, as, as + StrAlloc, 0); 1962 if(s == nil) 1963 parseErr("out of memory"); 1964 as += StrAlloc; 1965 } 1966 } 1967 if(ns == 0) 1968 badsyn(); 1969 s[ns] = '\0'; 1970 return s; 1971 } 1972 1973 /* 1974 * quoted: '"' chars* '"' 1975 * chars: 1-128 except \r and \n 1976 */ 1977 static char * 1978 quoted(void) 1979 { 1980 char *s; 1981 int c, ns, as; 1982 1983 mustBe('"'); 1984 s = binalloc(&parseBin, StrAlloc, 0); 1985 if(s == nil) 1986 parseErr("out of memory"); 1987 as = StrAlloc; 1988 ns = 0; 1989 for(;;){ 1990 c = getc(); 1991 if(c == '"') 1992 break; 1993 if(c < 1 || c > 0x7f || c == '\r' || c == '\n') 1994 badsyn(); 1995 if(c == '\\'){ 1996 c = getc(); 1997 if(c != '\\' && c != '"') 1998 badsyn(); 1999 } 2000 s[ns++] = c; 2001 if(ns >= as){ 2002 s = bingrow(&parseBin, s, as, as + StrAlloc, 0); 2003 if(s == nil) 2004 parseErr("out of memory"); 2005 as += StrAlloc; 2006 } 2007 } 2008 s[ns] = '\0'; 2009 return s; 2010 } 2011 2012 /* 2013 * litlen: {number}\r\n 2014 */ 2015 static ulong 2016 litlen(void) 2017 { 2018 ulong v; 2019 2020 mustBe('{'); 2021 v = number(0); 2022 mustBe('}'); 2023 crnl(); 2024 return v; 2025 } 2026 2027 /* 2028 * literal: litlen data<0:litlen> 2029 */ 2030 static char * 2031 literal(void) 2032 { 2033 char *s; 2034 ulong v; 2035 2036 v = litlen(); 2037 s = binalloc(&parseBin, v+1, 0); 2038 if(s == nil) 2039 parseErr("out of memory"); 2040 Bprint(&bout, "+ Ready for literal data\r\n"); 2041 if(Bflush(&bout) < 0) 2042 writeErr(); 2043 if(v != 0 && Bread(&bin, s, v) != v) 2044 badsyn(); 2045 s[v] = '\0'; 2046 return s; 2047 } 2048 2049 /* 2050 * digits; number is 32 bits 2051 */ 2052 static ulong 2053 number(int nonzero) 2054 { 2055 ulong v; 2056 int c, first; 2057 2058 v = 0; 2059 first = 1; 2060 for(;;){ 2061 c = getc(); 2062 if(c < '0' || c > '9'){ 2063 ungetc(); 2064 if(first) 2065 badsyn(); 2066 break; 2067 } 2068 if(nonzero && first && c == '0') 2069 badsyn(); 2070 c -= '0'; 2071 first = 0; 2072 if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10) 2073 parseErr("number out of range\r\n"); 2074 v = v * 10 + c; 2075 } 2076 return v; 2077 } 2078 2079 static int 2080 getc(void) 2081 { 2082 return Bgetc(&bin); 2083 } 2084 2085 static void 2086 ungetc(void) 2087 { 2088 Bungetc(&bin); 2089 } 2090 2091 static int 2092 peekc(void) 2093 { 2094 int c; 2095 2096 c = Bgetc(&bin); 2097 Bungetc(&bin); 2098 return c; 2099 } 2100 2101