1 #include "common.h" 2 #include <ctype.h> 3 #include <plumb.h> 4 5 typedef struct Message Message; 6 typedef struct Ctype Ctype; 7 typedef struct Cmd Cmd; 8 9 char root[Pathlen]; 10 char mbname[Elemlen]; 11 int rootlen; 12 int didopen; 13 char *user; 14 char wd[2048]; 15 String *mbpath; 16 int natural; 17 int doflush; 18 19 int interrupted; 20 21 struct Message { 22 Message *next; 23 Message *prev; 24 Message *cmd; 25 Message *child; 26 Message *parent; 27 String *path; 28 int id; 29 int len; 30 int fileno; // number of directory 31 String *info; 32 char *from; 33 char *to; 34 char *cc; 35 char *replyto; 36 char *date; 37 char *subject; 38 char *type; 39 char *disposition; 40 char *filename; 41 char deleted; 42 char stored; 43 }; 44 45 Message top; 46 47 struct Ctype { 48 char *type; 49 char *ext; 50 int display; 51 char *plumbdest; 52 Ctype *next; 53 }; 54 55 Ctype ctype[] = { 56 { "text/plain", "txt", 1, 0 }, 57 { "text/html", "htm", 1, 0 }, 58 { "text/html", "html", 1, 0 }, 59 { "text/tab-separated-values", "tsv", 1, 0 }, 60 { "text/richtext", "rtx", 1, 0 }, 61 { "text/rtf", "rtf", 1, 0 }, 62 { "text", "txt", 1, 0 }, 63 { "message/rfc822", "msg", 0, 0 }, 64 { "image/bmp", "bmp", 0, "image" }, 65 { "image/jpeg", "jpg", 0, "image" }, 66 { "image/gif", "gif", 0, "image" }, 67 { "image/png", "png", 0, "image" }, 68 { "application/pdf", "pdf", 0, "postscript" }, 69 { "application/postscript", "ps", 0, "postscript" }, 70 { "application/", 0, 0, 0 }, 71 { "image/", 0, 0, 0 }, 72 { "multipart/", "mul", 0, 0 }, 73 74 }; 75 76 Message* acmd(Cmd*, Message*); 77 Message* bcmd(Cmd*, Message*); 78 Message* dcmd(Cmd*, Message*); 79 Message* eqcmd(Cmd*, Message*); 80 Message* hcmd(Cmd*, Message*); 81 Message* Hcmd(Cmd*, Message*); 82 Message* helpcmd(Cmd*, Message*); 83 Message* icmd(Cmd*, Message*); 84 Message* pcmd(Cmd*, Message*); 85 Message* qcmd(Cmd*, Message*); 86 Message* rcmd(Cmd*, Message*); 87 Message* scmd(Cmd*, Message*); 88 Message* ucmd(Cmd*, Message*); 89 Message* wcmd(Cmd*, Message*); 90 Message* xcmd(Cmd*, Message*); 91 Message* ycmd(Cmd*, Message*); 92 Message* pipecmd(Cmd*, Message*); 93 Message* rpipecmd(Cmd*, Message*); 94 Message* bangcmd(Cmd*, Message*); 95 Message* Pcmd(Cmd*, Message*); 96 Message* mcmd(Cmd*, Message*); 97 Message* fcmd(Cmd*, Message*); 98 Message* quotecmd(Cmd*, Message*); 99 100 struct { 101 char *cmd; 102 int args; 103 Message* (*f)(Cmd*, Message*); 104 char *help; 105 } cmdtab[] = { 106 { "a", 1, acmd, "a reply to sender and recipients" }, 107 { "A", 1, acmd, "A reply to sender and recipients with copy" }, 108 { "b", 0, bcmd, "b print the next 10 headers" }, 109 { "d", 0, dcmd, "d mark for deletion" }, 110 { "f", 0, fcmd, "f file message by from address" }, 111 { "h", 0, hcmd, "h print elided message summary (,h for all)" }, 112 { "help", 0, helpcmd, "help print this info" }, 113 { "H", 0, Hcmd, "H print message's MIME structure " }, 114 { "i", 0, icmd, "i incorporate new mail" }, 115 { "m", 1, mcmd, "m addr forward mail" }, 116 { "M", 1, mcmd, "M addr forward mail with message" }, 117 { "p", 0, pcmd, "p print the processed message" }, 118 { "P", 0, Pcmd, "P print the raw message" }, 119 { "\"", 0, quotecmd, "\" print a quoted version of msg" }, 120 { "q", 0, qcmd, "q exit and remove all deleted mail" }, 121 { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" }, 122 { "rf", 1, rcmd, "rf [addr]file message and reply" }, 123 { "R", 1, rcmd, "R [addr] reply including copy of message" }, 124 { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" }, 125 { "s", 1, scmd, "s file append raw message to file" }, 126 { "u", 0, ucmd, "u remove deletion mark" }, 127 { "w", 1, wcmd, "w file store message contents as file" }, 128 { "x", 0, xcmd, "x exit without flushing deleted messages" }, 129 { "y", 0, ycmd, "y synchronize with mail box" }, 130 { "=", 1, eqcmd, "= print current message number" }, 131 { "|", 1, pipecmd, "|cmd pipe message body to a command" }, 132 { "||", 1, rpipecmd, "||cmd pipe raw message to a command" }, 133 { "!", 1, bangcmd, "!cmd run a command" }, 134 { nil, 0, nil, nil }, 135 }; 136 137 enum 138 { 139 NARG= 32, 140 }; 141 142 struct Cmd { 143 Message *msgs; 144 Message *(*f)(Cmd*, Message*); 145 int an; 146 char *av[NARG]; 147 int delete; 148 }; 149 150 Biobuf out; 151 int startedfs; 152 int reverse; 153 int longestfrom = 12; 154 155 String* file2string(String*, char*); 156 int dir2message(Message*, int); 157 int filelen(String*, char*); 158 String* extendpath(String*, char*); 159 void snprintheader(char*, int, Message*); 160 void cracktime(char*, char*, int); 161 int cistrncmp(char*, char*, int); 162 int cistrcmp(char*, char*); 163 Reprog* parsesearch(char**); 164 char* parseaddr(char**, Message*, Message*, Message*, Message**); 165 char* parsecmd(char*, Cmd*, Message*, Message*); 166 char* readline(char*, char*, int); 167 void messagecount(Message*); 168 void system(char*, char**, int); 169 void mkid(String*, Message*); 170 int switchmb(char*, char*); 171 void closemb(void); 172 int lineize(char*, char**, int); 173 int rawsearch(Message*, Reprog*); 174 Message* dosingleton(Message*, char*); 175 String* rooted(String*); 176 int plumb(Message*, Ctype*); 177 String* addrecolon(char*); 178 void exitfs(char*); 179 Message* flushdeleted(Message*); 180 181 void 182 usage(void) 183 { 184 fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0); 185 fprint(2, " %s -c dir\n", argv0); 186 exits("usage"); 187 } 188 189 void 190 catchnote(void*, char *note) 191 { 192 if(strstr(note, "interrupt") != nil){ 193 interrupted = 1; 194 noted(NCONT); 195 } 196 noted(NDFLT); 197 } 198 199 char * 200 plural(int n) 201 { 202 if (n == 1) 203 return ""; 204 205 return "s"; 206 } 207 208 void 209 main(int argc, char **argv) 210 { 211 Message *cur, *m, *x; 212 char cmdline[4*1024]; 213 Cmd cmd; 214 Ctype *cp; 215 int n, cflag; 216 char *av[4]; 217 String *prompt; 218 char *err, *file, *singleton; 219 220 quotefmtinstall(); 221 Binit(&out, 1, OWRITE); 222 223 file = nil; 224 singleton = nil; 225 reverse = 1; 226 cflag = 0; 227 ARGBEGIN { 228 case 'c': 229 cflag = 1; 230 break; 231 case 'f': 232 file = EARGF(usage()); 233 break; 234 case 's': 235 singleton = EARGF(usage()); 236 break; 237 case 'r': 238 reverse = 0; 239 break; 240 case 'n': 241 natural = 1; 242 reverse = 0; 243 break; 244 default: 245 usage(); 246 break; 247 } ARGEND; 248 249 user = getlog(); 250 if(user == nil || *user == 0) 251 sysfatal("can't read user name"); 252 253 if(cflag){ 254 if(argc > 0) 255 creatembox(user, argv[0]); 256 else 257 creatembox(user, nil); 258 exits(0); 259 } 260 261 if(argc) 262 usage(); 263 264 if(access("/mail/fs/ctl", 0) < 0){ 265 startedfs = 1; 266 av[0] = "fs"; 267 av[1] = "-p"; 268 av[2] = 0; 269 system("/bin/upas/fs", av, -1); 270 } 271 272 switchmb(file, singleton); 273 274 top.path = s_copy(root); 275 276 for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++) 277 cp->next = cp+1; 278 279 if(singleton != nil){ 280 cur = dosingleton(&top, singleton); 281 if(cur == nil){ 282 Bprint(&out, "no message\n"); 283 exitfs(0); 284 } 285 pcmd(nil, cur); 286 } else { 287 cur = ⊤ 288 n = dir2message(&top, reverse); 289 if(n < 0) 290 sysfatal("can't read %s", s_to_c(top.path)); 291 Bprint(&out, "%d message%s\n", n, plural(n)); 292 } 293 294 295 notify(catchnote); 296 prompt = s_new(); 297 for(;;){ 298 s_reset(prompt); 299 if(cur == &top) 300 s_append(prompt, ": "); 301 else { 302 mkid(prompt, cur); 303 s_append(prompt, ": "); 304 } 305 306 // leave space at the end of cmd line in case parsecmd needs to 307 // add a space after a '|' or '!' 308 if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil) 309 break; 310 err = parsecmd(cmdline, &cmd, top.child, cur); 311 if(err != nil){ 312 Bprint(&out, "!%s\n", err); 313 continue; 314 } 315 if(singleton != nil && cmd.f == icmd){ 316 Bprint(&out, "!illegal command\n"); 317 continue; 318 } 319 interrupted = 0; 320 if(cmd.msgs == nil || cmd.msgs == &top){ 321 x = (*cmd.f)(&cmd, &top); 322 if(x != nil) 323 cur = x; 324 } else for(m = cmd.msgs; m != nil; m = m->cmd){ 325 x = m; 326 if(cmd.delete){ 327 dcmd(&cmd, x); 328 329 // dp acts differently than all other commands 330 // since its an old lesk idiom that people love. 331 // it deletes the current message, moves the current 332 // pointer ahead one and prints. 333 if(cmd.f == pcmd){ 334 if(x->next == nil){ 335 Bprint(&out, "!address\n"); 336 cur = x; 337 break; 338 } else 339 x = x->next; 340 } 341 } 342 x = (*cmd.f)(&cmd, x); 343 if(x != nil) 344 cur = x; 345 if(interrupted) 346 break; 347 if(singleton != nil && (cmd.delete || cmd.f == dcmd)) 348 qcmd(nil, nil); 349 } 350 if(doflush) 351 cur = flushdeleted(cur); 352 } 353 qcmd(nil, nil); 354 } 355 356 // 357 // read the message info 358 // 359 Message* 360 file2message(Message *parent, char *name) 361 { 362 Message *m; 363 String *path; 364 char *f[10]; 365 366 m = mallocz(sizeof(Message), 1); 367 if(m == nil) 368 return nil; 369 m->path = path = extendpath(parent->path, name); 370 m->fileno = atoi(name); 371 m->info = file2string(path, "info"); 372 lineize(s_to_c(m->info), f, nelem(f)); 373 m->from = f[0]; 374 m->to = f[1]; 375 m->cc = f[2]; 376 m->replyto = f[3]; 377 m->date = f[4]; 378 m->subject = f[5]; 379 m->type = f[6]; 380 m->disposition = f[7]; 381 m->filename = f[8]; 382 m->len = filelen(path, "raw"); 383 if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0) 384 dir2message(m, 0); 385 m->parent = parent; 386 387 return m; 388 } 389 390 void 391 freemessage(Message *m) 392 { 393 Message *nm, *next; 394 395 for(nm = m->child; nm != nil; nm = next){ 396 next = nm->next; 397 freemessage(nm); 398 } 399 s_free(m->path); 400 s_free(m->info); 401 free(m); 402 } 403 404 // 405 // read a directory into a list of messages 406 // 407 int 408 dir2message(Message *parent, int reverse) 409 { 410 int i, n, fd, highest, newmsgs; 411 Dir *d; 412 Message *first, *last, *m; 413 414 fd = open(s_to_c(parent->path), OREAD); 415 if(fd < 0) 416 return -1; 417 418 // count current entries 419 first = parent->child; 420 highest = newmsgs = 0; 421 for(last = parent->child; last != nil && last->next != nil; last = last->next) 422 if(last->fileno > highest) 423 highest = last->fileno; 424 if(last != nil) 425 if(last->fileno > highest) 426 highest = last->fileno; 427 428 n = dirreadall(fd, &d); 429 for(i = 0; i < n; i++){ 430 if((d[i].qid.type & QTDIR) == 0) 431 continue; 432 if(atoi(d[i].name) <= highest) 433 continue; 434 m = file2message(parent, d[i].name); 435 if(m == nil) 436 break; 437 newmsgs++; 438 if(reverse){ 439 m->next = first; 440 if(first != nil) 441 first->prev = m; 442 first = m; 443 } else { 444 if(first == nil) 445 first = m; 446 else 447 last->next = m; 448 m->prev = last; 449 last = m; 450 } 451 } 452 free(d); 453 close(fd); 454 parent->child = first; 455 456 // renumber and file longest from 457 i = 1; 458 longestfrom = 12; 459 for(m = first; m != nil; m = m->next){ 460 m->id = natural ? m->fileno : i++; 461 n = strlen(m->from); 462 if(n > longestfrom) 463 longestfrom = n; 464 } 465 466 return newmsgs; 467 } 468 469 // 470 // point directly to a message 471 // 472 Message* 473 dosingleton(Message *parent, char *path) 474 { 475 char *p, *np; 476 Message *m; 477 478 // walk down to message and read it 479 if(strlen(path) < rootlen) 480 return nil; 481 if(path[rootlen] != '/') 482 return nil; 483 p = path+rootlen+1; 484 np = strchr(p, '/'); 485 if(np != nil) 486 *np = 0; 487 m = file2message(parent, p); 488 if(m == nil) 489 return nil; 490 parent->child = m; 491 m->id = 1; 492 493 // walk down to requested component 494 while(np != nil){ 495 *np = '/'; 496 np = strchr(np+1, '/'); 497 if(np != nil) 498 *np = 0; 499 for(m = m->child; m != nil; m = m->next) 500 if(strcmp(path, s_to_c(m->path)) == 0) 501 return m; 502 if(m == nil) 503 return nil; 504 } 505 return m; 506 } 507 508 // 509 // read a file into a string 510 // 511 String* 512 file2string(String *dir, char *file) 513 { 514 String *s; 515 int fd, n, m; 516 517 s = extendpath(dir, file); 518 fd = open(s_to_c(s), OREAD); 519 s_grow(s, 512); /* avoid multiple reads on info files */ 520 s_reset(s); 521 if(fd < 0) 522 return s; 523 524 for(;;){ 525 n = s->end - s->ptr; 526 if(n == 0){ 527 s_grow(s, 128); 528 continue; 529 } 530 m = read(fd, s->ptr, n); 531 if(m <= 0) 532 break; 533 s->ptr += m; 534 if(m < n) 535 break; 536 } 537 s_terminate(s); 538 close(fd); 539 540 return s; 541 } 542 543 // 544 // get the length of a file 545 // 546 int 547 filelen(String *dir, char *file) 548 { 549 String *path; 550 Dir *d; 551 int rv; 552 553 path = extendpath(dir, file); 554 d = dirstat(s_to_c(path)); 555 if(d == nil){ 556 s_free(path); 557 return -1; 558 } 559 s_free(path); 560 rv = d->length; 561 free(d); 562 return rv; 563 } 564 565 // 566 // walk the path name an element 567 // 568 String* 569 extendpath(String *dir, char *name) 570 { 571 String *path; 572 573 if(strcmp(s_to_c(dir), ".") == 0) 574 path = s_new(); 575 else { 576 path = s_copy(s_to_c(dir)); 577 s_append(path, "/"); 578 } 579 s_append(path, name); 580 return path; 581 } 582 583 int 584 cistrncmp(char *a, char *b, int n) 585 { 586 while(n-- > 0){ 587 if(tolower(*a++) != tolower(*b++)) 588 return -1; 589 } 590 return 0; 591 } 592 593 int 594 cistrcmp(char *a, char *b) 595 { 596 for(;;){ 597 if(tolower(*a) != tolower(*b++)) 598 return -1; 599 if(*a++ == 0) 600 break; 601 } 602 return 0; 603 } 604 605 char* 606 nosecs(char *t) 607 { 608 char *p; 609 610 p = strchr(t, ':'); 611 if(p == nil) 612 return t; 613 p = strchr(p+1, ':'); 614 if(p != nil) 615 *p = 0; 616 return t; 617 } 618 619 char *months[12] = 620 { 621 "jan", "feb", "mar", "apr", "may", "jun", 622 "jul", "aug", "sep", "oct", "nov", "dec" 623 }; 624 625 int 626 month(char *m) 627 { 628 int i; 629 630 for(i = 0; i < 12; i++) 631 if(cistrcmp(m, months[i]) == 0) 632 return i+1; 633 return 1; 634 } 635 636 enum 637 { 638 Yearsecs= 365*24*60*60 639 }; 640 641 void 642 cracktime(char *d, char *out, int len) 643 { 644 char in[64]; 645 char *f[6]; 646 int n; 647 Tm tm; 648 long now, then; 649 char *dtime; 650 651 *out = 0; 652 if(d == nil) 653 return; 654 strncpy(in, d, sizeof(in)); 655 in[sizeof(in)-1] = 0; 656 n = getfields(in, f, 6, 1, " \t\r\n"); 657 if(n != 6){ 658 // unknown style 659 snprint(out, 16, "%10.10s", d); 660 return; 661 } 662 now = time(0); 663 memset(&tm, 0, sizeof tm); 664 if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){ 665 // 822 style 666 tm.year = atoi(f[3])-1900; 667 tm.mon = month(f[2]); 668 tm.mday = atoi(f[1]); 669 dtime = nosecs(f[4]); 670 then = tm2sec(&tm); 671 } else if(strchr(f[3], ':') != nil){ 672 // unix style 673 tm.year = atoi(f[5])-1900; 674 tm.mon = month(f[1]); 675 tm.mday = atoi(f[2]); 676 dtime = nosecs(f[3]); 677 then = tm2sec(&tm); 678 } else { 679 then = now; 680 tm = *localtime(now); 681 dtime = ""; 682 } 683 684 if(now - then < Yearsecs/2) 685 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime); 686 else 687 snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900); 688 } 689 690 Ctype* 691 findctype(Message *m) 692 { 693 char *p; 694 char ftype[128]; 695 int n, pfd[2]; 696 Ctype *a, *cp; 697 static Ctype nulltype = { "", 0, 0, 0 }; 698 static Ctype bintype = { "application/octet-stream", "bin", 0, 0 }; 699 700 for(cp = ctype; cp; cp = cp->next) 701 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0) 702 return cp; 703 704 /* use file(1) for any unknown mimetypes 705 * 706 * if (strcmp(m->type, bintype.type) != 0) 707 * return &nulltype; 708 */ 709 if(pipe(pfd) < 0) 710 return &bintype; 711 712 *ftype = 0; 713 switch(fork()){ 714 case -1: 715 break; 716 case 0: 717 close(pfd[1]); 718 close(0); 719 dup(pfd[0], 0); 720 close(1); 721 dup(pfd[0], 1); 722 execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil); 723 exits(0); 724 default: 725 close(pfd[0]); 726 n = read(pfd[1], ftype, sizeof(ftype)); 727 if(n > 0) 728 ftype[n] = 0; 729 close(pfd[1]); 730 waitpid(); 731 break; 732 } 733 734 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil) 735 return &bintype; 736 *p++ = 0; 737 738 a = mallocz(sizeof(Ctype), 1); 739 a->type = strdup(ftype); 740 a->ext = strdup(p); 741 a->display = 0; 742 a->plumbdest = strdup(ftype); 743 for(cp = ctype; cp->next; cp = cp->next) 744 continue; 745 cp->next = a; 746 a->next = nil; 747 return a; 748 } 749 750 void 751 mkid(String *s, Message *m) 752 { 753 char buf[32]; 754 755 if(m->parent != &top){ 756 mkid(s, m->parent); 757 s_append(s, "."); 758 } 759 sprint(buf, "%d", m->id); 760 s_append(s, buf); 761 } 762 763 void 764 snprintheader(char *buf, int len, Message *m) 765 { 766 char timebuf[32]; 767 String *id; 768 char *p, *q; 769 770 // create id 771 id = s_new(); 772 mkid(id, m); 773 774 if(*m->from == 0){ 775 // no from 776 snprint(buf, len, "%-3s %s %6d %s", 777 s_to_c(id), 778 m->type, 779 m->len, 780 m->filename); 781 } else if(*m->subject){ 782 q = p = strdup(m->subject); 783 while(*p == ' ') 784 p++; 785 if(strlen(p) > 50) 786 p[50] = 0; 787 cracktime(m->date, timebuf, sizeof(timebuf)); 788 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s", 789 s_to_c(id), 790 m->child ? 'H' : ' ', 791 m->deleted ? 'd' : ' ', 792 m->stored ? 's' : ' ', 793 m->len, 794 timebuf, 795 longestfrom, longestfrom, m->from, 796 p); 797 free(q); 798 } else { 799 cracktime(m->date, timebuf, sizeof(timebuf)); 800 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s", 801 s_to_c(id), 802 m->child ? 'H' : ' ', 803 m->deleted ? 'd' : ' ', 804 m->stored ? 's' : ' ', 805 m->len, 806 timebuf, 807 m->from); 808 } 809 s_free(id); 810 } 811 812 char *spaces = " "; 813 814 void 815 snprintHeader(char *buf, int len, int indent, Message *m) 816 { 817 String *id; 818 char typeid[64]; 819 char *p, *e; 820 821 // create id 822 id = s_new(); 823 mkid(id, m); 824 825 e = buf + len; 826 827 snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type); 828 if(indent < 6) 829 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len); 830 else 831 p = seprint(buf, e, "%-64s %-6d ", typeid, m->len); 832 if(m->filename && *m->filename) 833 p = seprint(p, e, "(file,%s)", m->filename); 834 if(m->from && *m->from) 835 p = seprint(p, e, "(from,%s)", m->from); 836 if(m->subject && *m->subject) 837 seprint(p, e, "(subj,%s)", m->subject); 838 839 s_free(id); 840 } 841 842 char sstring[256]; 843 844 // cmd := range cmd ' ' arg-list ; 845 // range := address 846 // | address ',' address 847 // | 'g' search ; 848 // address := msgno 849 // | search ; 850 // msgno := number 851 // | number '/' msgno ; 852 // search := '/' string '/' 853 // | '%' string '%' ; 854 // 855 Reprog* 856 parsesearch(char **pp) 857 { 858 char *p, *np; 859 int c, n; 860 861 p = *pp; 862 c = *p++; 863 np = strchr(p, c); 864 if(np != nil){ 865 *np++ = 0; 866 *pp = np; 867 } else { 868 n = strlen(p); 869 *pp = p + n; 870 } 871 if(*p == 0) 872 p = sstring; 873 else{ 874 strncpy(sstring, p, sizeof(sstring)); 875 sstring[sizeof(sstring)-1] = 0; 876 } 877 return regcomp(p); 878 } 879 880 static char * 881 num2msg(Message **mp, int sign, int n, Message *first, Message *cur) 882 { 883 Message *m; 884 885 m = nil; 886 switch(sign){ 887 case 0: 888 for(m = first; m != nil; m = m->next) 889 if(m->id == n) 890 break; 891 break; 892 case -1: 893 if(cur != &top) 894 for(m = cur; m != nil && n > 0; n--) 895 m = m->prev; 896 break; 897 case 1: 898 if(cur == &top){ 899 n--; 900 cur = first; 901 } 902 for(m = cur; m != nil && n > 0; n--) 903 m = m->next; 904 break; 905 } 906 if(m == nil) 907 return "address"; 908 *mp = m; 909 return nil; 910 } 911 912 char* 913 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp) 914 { 915 int n; 916 Message *m; 917 char *p, *err; 918 Reprog *prog; 919 int c, sign; 920 char buf[256]; 921 922 *mp = nil; 923 p = *pp; 924 925 if(*p == '+'){ 926 sign = 1; 927 p++; 928 *pp = p; 929 } else if(*p == '-'){ 930 sign = -1; 931 p++; 932 *pp = p; 933 } else 934 sign = 0; 935 936 /* 937 * TODO: verify & install this. 938 * make + and - mean +1 and -1, as in ed. then -,.d won't 939 * delete all messages up to the current one. - geoff 940 */ 941 if(sign && (!isascii(*p) || !isdigit(*p))) { 942 err = num2msg(mp, sign, 1, first, cur); 943 if (err != nil) 944 return err; 945 } 946 947 switch(*p){ 948 default: 949 if(sign){ 950 n = 1; 951 goto number; 952 } 953 *mp = unspec; 954 break; 955 case '0': case '1': case '2': case '3': case '4': 956 case '5': case '6': case '7': case '8': case '9': 957 n = strtoul(p, pp, 10); 958 if(n == 0){ 959 if(sign) 960 *mp = cur; 961 else 962 *mp = ⊤ 963 break; 964 } 965 /* fall through */ 966 number: 967 err = num2msg(mp, sign, n, first, cur); 968 if (err != nil) 969 return err; 970 break; 971 case '%': 972 case '/': 973 case '?': 974 c = *p; 975 prog = parsesearch(pp); 976 if(prog == nil) 977 return "badly formed regular expression"; 978 m = nil; 979 switch(c){ 980 case '%': 981 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ 982 if(rawsearch(m, prog)) 983 break; 984 } 985 break; 986 case '/': 987 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ 988 snprintheader(buf, sizeof(buf), m); 989 if(regexec(prog, buf, nil, 0)) 990 break; 991 } 992 break; 993 case '?': 994 for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){ 995 snprintheader(buf, sizeof(buf), m); 996 if(regexec(prog, buf, nil, 0)) 997 break; 998 } 999 break; 1000 } 1001 if(m == nil) 1002 return "search"; 1003 *mp = m; 1004 free(prog); 1005 break; 1006 case '$': 1007 for(m = first; m != nil && m->next != nil; m = m->next) 1008 ; 1009 *mp = m; 1010 *pp = p+1; 1011 break; 1012 case '.': 1013 *mp = cur; 1014 *pp = p+1; 1015 break; 1016 case ',': 1017 if (*mp == nil) 1018 *mp = first; 1019 *pp = p; 1020 break; 1021 } 1022 1023 if(*mp != nil && **pp == '.'){ 1024 (*pp)++; 1025 if((*mp)->child == nil) 1026 return "no sub parts"; 1027 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp); 1028 } 1029 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%') 1030 return parseaddr(pp, first, *mp, *mp, mp); 1031 1032 return nil; 1033 } 1034 1035 // 1036 // search a message for a regular expression match 1037 // 1038 int 1039 rawsearch(Message *m, Reprog *prog) 1040 { 1041 char buf[4096+1]; 1042 int i, fd, rv; 1043 String *path; 1044 1045 path = extendpath(m->path, "raw"); 1046 fd = open(s_to_c(path), OREAD); 1047 if(fd < 0) 1048 return 0; 1049 1050 // march through raw message 4096 bytes at a time 1051 // with a 128 byte overlap to chain the re search. 1052 rv = 0; 1053 for(;;){ 1054 i = read(fd, buf, sizeof(buf)-1); 1055 if(i <= 0) 1056 break; 1057 buf[i] = 0; 1058 if(regexec(prog, buf, nil, 0)){ 1059 rv = 1; 1060 break; 1061 } 1062 if(i < sizeof(buf)-1) 1063 break; 1064 if(seek(fd, -128LL, 1) < 0) 1065 break; 1066 } 1067 1068 close(fd); 1069 s_free(path); 1070 return rv; 1071 } 1072 1073 1074 char* 1075 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur) 1076 { 1077 Reprog *prog; 1078 Message *m, *s, *e, **l, *last; 1079 char buf[256]; 1080 char *err; 1081 int i, c; 1082 char *q; 1083 static char errbuf[Errlen]; 1084 1085 cmd->delete = 0; 1086 l = &cmd->msgs; 1087 *l = nil; 1088 1089 // eat white space 1090 while(*p == ' ') 1091 p++; 1092 1093 // null command is a special case (advance and print) 1094 if(*p == 0){ 1095 if(cur == &top){ 1096 // special case 1097 m = first; 1098 } else { 1099 // walk to the next message even if we have to go up 1100 m = cur->next; 1101 while(m == nil && cur->parent != nil){ 1102 cur = cur->parent; 1103 m = cur->next; 1104 } 1105 } 1106 if(m == nil) 1107 return "address"; 1108 *l = m; 1109 m->cmd = nil; 1110 cmd->an = 0; 1111 cmd->f = pcmd; 1112 return nil; 1113 } 1114 1115 // global search ? 1116 if(*p == 'g'){ 1117 p++; 1118 1119 // no search string means all messages 1120 if(*p != '/' && *p != '%'){ 1121 for(m = first; m != nil; m = m->next){ 1122 *l = m; 1123 l = &m->cmd; 1124 *l = nil; 1125 } 1126 } else { 1127 // mark all messages matching this search string 1128 c = *p; 1129 prog = parsesearch(&p); 1130 if(prog == nil) 1131 return "badly formed regular expression"; 1132 if(c == '%'){ 1133 for(m = first; m != nil; m = m->next){ 1134 if(rawsearch(m, prog)){ 1135 *l = m; 1136 l = &m->cmd; 1137 *l = nil; 1138 } 1139 } 1140 } else { 1141 for(m = first; m != nil; m = m->next){ 1142 snprintheader(buf, sizeof(buf), m); 1143 if(regexec(prog, buf, nil, 0)){ 1144 *l = m; 1145 l = &m->cmd; 1146 *l = nil; 1147 } 1148 } 1149 } 1150 free(prog); 1151 } 1152 } else { 1153 1154 // parse an address 1155 s = e = nil; 1156 err = parseaddr(&p, first, cur, cur, &s); 1157 if(err != nil) 1158 return err; 1159 if(*p == ','){ 1160 // this is an address range 1161 if(s == &top) 1162 s = first; 1163 p++; 1164 for(last = s; last != nil && last->next != nil; last = last->next) 1165 ; 1166 err = parseaddr(&p, first, cur, last, &e); 1167 if(err != nil) 1168 return err; 1169 1170 // select all messages in the range 1171 for(; s != nil; s = s->next){ 1172 *l = s; 1173 l = &s->cmd; 1174 *l = nil; 1175 if(s == e) 1176 break; 1177 } 1178 if(s == nil) 1179 return "null address range"; 1180 } else { 1181 // single address 1182 if(s != &top){ 1183 *l = s; 1184 s->cmd = nil; 1185 } 1186 } 1187 } 1188 1189 // insert a space after '!'s and '|'s 1190 for(q = p; *q; q++) 1191 if(*q != '!' && *q != '|') 1192 break; 1193 if(q != p && *q != ' '){ 1194 memmove(q+1, q, strlen(q)+1); 1195 *q = ' '; 1196 } 1197 1198 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n"); 1199 if(cmd->an == 0 || *cmd->av[0] == 0) 1200 cmd->f = pcmd; 1201 else { 1202 // hack to allow all messages to start with 'd' 1203 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){ 1204 cmd->delete = 1; 1205 cmd->av[0]++; 1206 } 1207 1208 // search command table 1209 for(i = 0; cmdtab[i].cmd != nil; i++) 1210 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0) 1211 break; 1212 if(cmdtab[i].cmd == nil) 1213 return "illegal command"; 1214 if(cmdtab[i].args == 0 && cmd->an > 1){ 1215 snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd); 1216 return errbuf; 1217 } 1218 cmd->f = cmdtab[i].f; 1219 } 1220 return nil; 1221 } 1222 1223 // inefficient read from standard input 1224 char* 1225 readline(char *prompt, char *line, int len) 1226 { 1227 char *p, *e; 1228 int n; 1229 1230 retry: 1231 interrupted = 0; 1232 Bprint(&out, "%s", prompt); 1233 Bflush(&out); 1234 e = line + len; 1235 for(p = line; p < e; p++){ 1236 n = read(0, p, 1); 1237 if(n < 0){ 1238 if(interrupted) 1239 goto retry; 1240 return nil; 1241 } 1242 if(n == 0) 1243 return nil; 1244 if(*p == '\n') 1245 break; 1246 } 1247 *p = 0; 1248 return line; 1249 } 1250 1251 void 1252 messagecount(Message *m) 1253 { 1254 int i; 1255 1256 i = 0; 1257 for(; m != nil; m = m->next) 1258 i++; 1259 Bprint(&out, "%d message%s\n", i, plural(i)); 1260 } 1261 1262 Message* 1263 aichcmd(Message *m, int indent) 1264 { 1265 char hdr[256]; 1266 1267 if(m == &top) 1268 return nil; 1269 1270 snprintHeader(hdr, sizeof(hdr), indent, m); 1271 Bprint(&out, "%s\n", hdr); 1272 for(m = m->child; m != nil; m = m->next) 1273 aichcmd(m, indent+1); 1274 return nil; 1275 } 1276 1277 Message* 1278 Hcmd(Cmd*, Message *m) 1279 { 1280 if(m == &top) 1281 return nil; 1282 aichcmd(m, 0); 1283 return nil; 1284 } 1285 1286 Message* 1287 hcmd(Cmd*, Message *m) 1288 { 1289 char hdr[256]; 1290 1291 if(m == &top) 1292 return nil; 1293 1294 snprintheader(hdr, sizeof(hdr), m); 1295 Bprint(&out, "%s\n", hdr); 1296 return nil; 1297 } 1298 1299 Message* 1300 bcmd(Cmd*, Message *m) 1301 { 1302 int i; 1303 Message *om = m; 1304 1305 if(m == &top) 1306 m = top.child; 1307 for(i = 0; i < 10 && m != nil; i++){ 1308 hcmd(nil, m); 1309 om = m; 1310 m = m->next; 1311 } 1312 1313 return om; 1314 } 1315 1316 Message* 1317 ncmd(Cmd*, Message *m) 1318 { 1319 if(m == &top) 1320 return m->child; 1321 return m->next; 1322 } 1323 1324 int 1325 printpart(String *s, char *part) 1326 { 1327 char buf[4096]; 1328 int n, fd, tot; 1329 String *path; 1330 1331 path = extendpath(s, part); 1332 fd = open(s_to_c(path), OREAD); 1333 s_free(path); 1334 if(fd < 0){ 1335 fprint(2, "!message disappeared\n"); 1336 return 0; 1337 } 1338 tot = 0; 1339 while((n = read(fd, buf, sizeof(buf))) > 0){ 1340 if(interrupted) 1341 break; 1342 if(Bwrite(&out, buf, n) <= 0) 1343 break; 1344 tot += n; 1345 } 1346 close(fd); 1347 return tot; 1348 } 1349 1350 int 1351 printhtml(Message *m) 1352 { 1353 Cmd c; 1354 1355 c.an = 3; 1356 c.av[1] = "/bin/htmlfmt"; 1357 c.av[2] = "-l 40 -cutf-8"; 1358 Bprint(&out, "!%s\n", c.av[1]); 1359 Bflush(&out); 1360 pipecmd(&c, m); 1361 return 0; 1362 } 1363 1364 Message* 1365 Pcmd(Cmd*, Message *m) 1366 { 1367 if(m == &top) 1368 return ⊤ 1369 if(m->parent == &top) 1370 printpart(m->path, "unixheader"); 1371 printpart(m->path, "raw"); 1372 return m; 1373 } 1374 1375 void 1376 compress(char *p) 1377 { 1378 char *np; 1379 int last; 1380 1381 last = ' '; 1382 for(np = p; *p; p++){ 1383 if(*p != ' ' || last != ' '){ 1384 last = *p; 1385 *np++ = last; 1386 } 1387 } 1388 *np = 0; 1389 } 1390 1391 Message* 1392 pcmd(Cmd*, Message *m) 1393 { 1394 Message *nm; 1395 Ctype *cp; 1396 String *s; 1397 char buf[128]; 1398 1399 if(m == &top) 1400 return ⊤ 1401 if(m->parent == &top) 1402 printpart(m->path, "unixheader"); 1403 if(printpart(m->path, "header") > 0) 1404 Bprint(&out, "\n"); 1405 cp = findctype(m); 1406 if(cp->display){ 1407 if(strcmp(m->type, "text/html") == 0) 1408 printhtml(m); 1409 else 1410 printpart(m->path, "body"); 1411 } else if(strcmp(m->type, "multipart/alternative") == 0){ 1412 for(nm = m->child; nm != nil; nm = nm->next){ 1413 cp = findctype(nm); 1414 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) 1415 break; 1416 } 1417 if(nm == nil) 1418 for(nm = m->child; nm != nil; nm = nm->next){ 1419 cp = findctype(nm); 1420 if(cp->display) 1421 break; 1422 } 1423 if(nm != nil) 1424 pcmd(nil, nm); 1425 else 1426 hcmd(nil, m); 1427 } else if(strncmp(m->type, "multipart/", 10) == 0){ 1428 nm = m->child; 1429 if(nm != nil){ 1430 // always print first part 1431 pcmd(nil, nm); 1432 1433 for(nm = nm->next; nm != nil; nm = nm->next){ 1434 s = rooted(s_clone(nm->path)); 1435 cp = findctype(nm); 1436 snprintHeader(buf, sizeof buf, -1, nm); 1437 compress(buf); 1438 if(strcmp(nm->disposition, "inline") == 0){ 1439 if(cp->ext != nil) 1440 Bprint(&out, "\n--- %s %s/body.%s\n\n", 1441 buf, s_to_c(s), cp->ext); 1442 else 1443 Bprint(&out, "\n--- %s %s/body\n\n", 1444 buf, s_to_c(s)); 1445 pcmd(nil, nm); 1446 } else { 1447 if(cp->ext != nil) 1448 Bprint(&out, "\n!--- %s %s/body.%s\n", 1449 buf, s_to_c(s), cp->ext); 1450 else 1451 Bprint(&out, "\n!--- %s %s/body\n", 1452 buf, s_to_c(s)); 1453 } 1454 s_free(s); 1455 } 1456 } else { 1457 hcmd(nil, m); 1458 } 1459 } else if(strcmp(m->type, "message/rfc822") == 0){ 1460 pcmd(nil, m->child); 1461 } else if(plumb(m, cp) >= 0) 1462 Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type); 1463 else 1464 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type); 1465 1466 return m; 1467 } 1468 1469 void 1470 printpartindented(String *s, char *part, char *indent) 1471 { 1472 char *p; 1473 String *path; 1474 Biobuf *b; 1475 1476 path = extendpath(s, part); 1477 b = Bopen(s_to_c(path), OREAD); 1478 s_free(path); 1479 if(b == nil){ 1480 fprint(2, "!message disappeared\n"); 1481 return; 1482 } 1483 while((p = Brdline(b, '\n')) != nil){ 1484 if(interrupted) 1485 break; 1486 p[Blinelen(b)-1] = 0; 1487 if(Bprint(&out, "%s%s\n", indent, p) < 0) 1488 break; 1489 } 1490 Bprint(&out, "\n"); 1491 Bterm(b); 1492 } 1493 1494 Message* 1495 quotecmd(Cmd*, Message *m) 1496 { 1497 Message *nm; 1498 Ctype *cp; 1499 1500 if(m == &top) 1501 return ⊤ 1502 Bprint(&out, "\n"); 1503 if(m->from != nil && *m->from) 1504 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from); 1505 cp = findctype(m); 1506 if(cp->display){ 1507 printpartindented(m->path, "body", "> "); 1508 } else if(strcmp(m->type, "multipart/alternative") == 0){ 1509 for(nm = m->child; nm != nil; nm = nm->next){ 1510 cp = findctype(nm); 1511 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) 1512 break; 1513 } 1514 if(nm == nil) 1515 for(nm = m->child; nm != nil; nm = nm->next){ 1516 cp = findctype(nm); 1517 if(cp->display) 1518 break; 1519 } 1520 if(nm != nil) 1521 quotecmd(nil, nm); 1522 } else if(strncmp(m->type, "multipart/", 10) == 0){ 1523 nm = m->child; 1524 if(nm != nil){ 1525 cp = findctype(nm); 1526 if(cp->display || strncmp(m->type, "multipart/", 10) == 0) 1527 quotecmd(nil, nm); 1528 } 1529 } 1530 return m; 1531 } 1532 1533 // really delete messages 1534 Message* 1535 flushdeleted(Message *cur) 1536 { 1537 Message *m, **l; 1538 char buf[1024], *p, *e, *msg; 1539 int deld, n, fd; 1540 int i; 1541 1542 doflush = 0; 1543 deld = 0; 1544 1545 fd = open("/mail/fs/ctl", ORDWR); 1546 if(fd < 0){ 1547 fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n"); 1548 exitfs(0); 1549 } 1550 e = &buf[sizeof(buf)]; 1551 p = seprint(buf, e, "delete %s", mbname); 1552 n = 0; 1553 for(l = &top.child; *l != nil;){ 1554 m = *l; 1555 if(!m->deleted){ 1556 l = &(*l)->next; 1557 continue; 1558 } 1559 1560 // don't return a pointer to a deleted message 1561 if(m == cur) 1562 cur = m->next; 1563 1564 deld++; 1565 msg = strrchr(s_to_c(m->path), '/'); 1566 if(msg == nil) 1567 msg = s_to_c(m->path); 1568 else 1569 msg++; 1570 if(e-p < 10){ 1571 write(fd, buf, p-buf); 1572 n = 0; 1573 p = seprint(buf, e, "delete %s", mbname); 1574 } 1575 p = seprint(p, e, " %s", msg); 1576 n++; 1577 1578 // unchain and free 1579 *l = m->next; 1580 if(m->next) 1581 m->next->prev = m->prev; 1582 freemessage(m); 1583 } 1584 if(n) 1585 write(fd, buf, p-buf); 1586 1587 close(fd); 1588 1589 if(deld) 1590 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld)); 1591 1592 // renumber 1593 i = 1; 1594 for(m = top.child; m != nil; m = m->next) 1595 m->id = natural ? m->fileno : i++; 1596 1597 // if we're out of messages, go back to first 1598 // if no first, return the fake first 1599 if(cur == nil){ 1600 if(top.child) 1601 return top.child; 1602 else 1603 return ⊤ 1604 } 1605 return cur; 1606 } 1607 1608 Message* 1609 qcmd(Cmd*, Message*) 1610 { 1611 flushdeleted(nil); 1612 1613 if(didopen) 1614 closemb(); 1615 Bflush(&out); 1616 1617 exitfs(0); 1618 return nil; // not reached 1619 } 1620 1621 Message* 1622 ycmd(Cmd*, Message *m) 1623 { 1624 doflush = 1; 1625 1626 return icmd(nil, m); 1627 } 1628 1629 Message* 1630 xcmd(Cmd*, Message*) 1631 { 1632 exitfs(0); 1633 return nil; // not reached 1634 } 1635 1636 Message* 1637 eqcmd(Cmd*, Message *m) 1638 { 1639 if(m == &top) 1640 Bprint(&out, "0\n"); 1641 else 1642 Bprint(&out, "%d\n", m->id); 1643 return nil; 1644 } 1645 1646 Message* 1647 dcmd(Cmd*, Message *m) 1648 { 1649 if(m == &top){ 1650 Bprint(&out, "!address\n"); 1651 return nil; 1652 } 1653 while(m->parent != &top) 1654 m = m->parent; 1655 m->deleted = 1; 1656 return m; 1657 } 1658 1659 Message* 1660 ucmd(Cmd*, Message *m) 1661 { 1662 if(m == &top) 1663 return nil; 1664 while(m->parent != &top) 1665 m = m->parent; 1666 if(m->deleted < 0) 1667 Bprint(&out, "!can't undelete, already flushed\n"); 1668 m->deleted = 0; 1669 return m; 1670 } 1671 1672 1673 Message* 1674 icmd(Cmd*, Message *m) 1675 { 1676 int n; 1677 1678 n = dir2message(&top, reverse); 1679 if(n > 0) 1680 Bprint(&out, "%d new message%s\n", n, plural(n)); 1681 return m; 1682 } 1683 1684 Message* 1685 helpcmd(Cmd*, Message *m) 1686 { 1687 int i; 1688 1689 Bprint(&out, "Commands are of the form [<range>] <command> [args]\n"); 1690 Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n"); 1691 Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n"); 1692 Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n"); 1693 Bprint(&out, "<command> :=\n"); 1694 for(i = 0; cmdtab[i].cmd != nil; i++) 1695 Bprint(&out, "%s\n", cmdtab[i].help); 1696 return m; 1697 } 1698 1699 int 1700 tomailer(char **av) 1701 { 1702 Waitmsg *w; 1703 int pid, i; 1704 1705 // start the mailer and get out of the way 1706 switch(pid = fork()){ 1707 case -1: 1708 fprint(2, "can't fork: %r\n"); 1709 return -1; 1710 case 0: 1711 Bprint(&out, "!/bin/upas/marshal"); 1712 for(i = 1; av[i]; i++) 1713 Bprint(&out, " %q", av[i]); 1714 Bprint(&out, "\n"); 1715 Bflush(&out); 1716 av[0] = "marshal"; 1717 chdir(wd); 1718 exec("/bin/upas/marshal", av); 1719 fprint(2, "couldn't exec /bin/upas/marshal\n"); 1720 exits(0); 1721 default: 1722 w = wait(); 1723 if(w == nil){ 1724 if(interrupted) 1725 postnote(PNPROC, pid, "die"); 1726 waitpid(); 1727 return -1; 1728 } 1729 if(w->msg[0]){ 1730 fprint(2, "mailer failed: %s\n", w->msg); 1731 free(w); 1732 return -1; 1733 } 1734 free(w); 1735 Bprint(&out, "!\n"); 1736 break; 1737 } 1738 return 0; 1739 } 1740 1741 // 1742 // like tokenize but obey "" quoting 1743 // 1744 int 1745 tokenize822(char *str, char **args, int max) 1746 { 1747 int na; 1748 int intok = 0, inquote = 0; 1749 1750 if(max <= 0) 1751 return 0; 1752 for(na=0; ;str++) 1753 switch(*str) { 1754 case ' ': 1755 case '\t': 1756 if(inquote) 1757 goto Default; 1758 /* fall through */ 1759 case '\n': 1760 *str = 0; 1761 if(!intok) 1762 continue; 1763 intok = 0; 1764 if(na < max) 1765 continue; 1766 /* fall through */ 1767 case 0: 1768 return na; 1769 case '"': 1770 inquote ^= 1; 1771 /* fall through */ 1772 Default: 1773 default: 1774 if(intok) 1775 continue; 1776 args[na++] = str; 1777 intok = 1; 1778 } 1779 } 1780 1781 /* return reply-to address & set *nmp to corresponding Message */ 1782 static char * 1783 getreplyto(Message *m, Message **nmp) 1784 { 1785 Message *nm; 1786 1787 for(nm = m; nm != ⊤ nm = nm->parent) 1788 if(*nm->replyto != 0) 1789 break; 1790 *nmp = nm; 1791 return nm? nm->replyto: nil; 1792 } 1793 1794 Message* 1795 rcmd(Cmd *c, Message *m) 1796 { 1797 char *addr; 1798 char *av[128]; 1799 int i, ai = 1; 1800 String *from, *rpath, *path = nil, *subject = nil; 1801 Message *nm; 1802 1803 if(m == &top){ 1804 Bprint(&out, "!address\n"); 1805 return nil; 1806 } 1807 1808 addr = getreplyto(m, &nm); 1809 if(addr == nil){ 1810 Bprint(&out, "!no reply address\n"); 1811 return nil; 1812 } 1813 if(nm == &top){ 1814 print("!noone to reply to\n"); 1815 return nil; 1816 } 1817 1818 for(nm = m; nm != ⊤ nm = nm->parent){ 1819 if(*nm->subject){ 1820 av[ai++] = "-s"; 1821 subject = addrecolon(nm->subject); 1822 av[ai++] = s_to_c(subject); 1823 break; 1824 } 1825 } 1826 1827 av[ai++] = "-R"; 1828 rpath = rooted(s_clone(m->path)); 1829 av[ai++] = s_to_c(rpath); 1830 1831 if(strchr(c->av[0], 'f') != nil){ 1832 fcmd(c, m); 1833 av[ai++] = "-F"; 1834 } 1835 1836 if(strchr(c->av[0], 'R') != nil){ 1837 av[ai++] = "-t"; 1838 av[ai++] = "message/rfc822"; 1839 av[ai++] = "-A"; 1840 path = rooted(extendpath(m->path, "raw")); 1841 av[ai++] = s_to_c(path); 1842 } 1843 1844 for(i = 1; i < c->an && ai < nelem(av)-1; i++) 1845 av[ai++] = c->av[i]; 1846 from = s_copy(addr); 1847 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); 1848 av[ai] = 0; 1849 if(tomailer(av) < 0) 1850 m = nil; 1851 s_free(path); 1852 s_free(rpath); 1853 s_free(subject); 1854 s_free(from); 1855 return m; 1856 } 1857 1858 Message* 1859 mcmd(Cmd *c, Message *m) 1860 { 1861 char **av; 1862 int i, ai; 1863 String *path; 1864 1865 if(m == &top){ 1866 Bprint(&out, "!address\n"); 1867 return nil; 1868 } 1869 1870 if(c->an < 2){ 1871 fprint(2, "!usage: M list-of addresses\n"); 1872 return nil; 1873 } 1874 1875 ai = 1; 1876 av = malloc(sizeof(char*)*(c->an + 8)); 1877 1878 av[ai++] = "-t"; 1879 if(m->parent == &top) 1880 av[ai++] = "message/rfc822"; 1881 else 1882 av[ai++] = "mime"; 1883 1884 av[ai++] = "-A"; 1885 path = rooted(extendpath(m->path, "raw")); 1886 av[ai++] = s_to_c(path); 1887 1888 if(strchr(c->av[0], 'M') == nil) 1889 av[ai++] = "-n"; 1890 1891 for(i = 1; i < c->an; i++) 1892 av[ai++] = c->av[i]; 1893 av[ai] = 0; 1894 1895 if(tomailer(av) < 0) 1896 m = nil; 1897 if(path != nil) 1898 s_free(path); 1899 free(av); 1900 return m; 1901 } 1902 1903 Message* 1904 acmd(Cmd *c, Message *m) 1905 { 1906 char *av[128]; 1907 int i, ai = 1; 1908 String *from, *rpath, *path = nil, *subject = nil; 1909 String *to, *cc; 1910 1911 if(m == &top){ 1912 Bprint(&out, "!address\n"); 1913 return nil; 1914 } 1915 1916 if(*m->subject){ 1917 av[ai++] = "-s"; 1918 subject = addrecolon(m->subject); 1919 av[ai++] = s_to_c(subject); 1920 } 1921 1922 av[ai++] = "-R"; 1923 rpath = rooted(s_clone(m->path)); 1924 av[ai++] = s_to_c(rpath); 1925 1926 if(strchr(c->av[0], 'A') != nil){ 1927 av[ai++] = "-t"; 1928 av[ai++] = "message/rfc822"; 1929 av[ai++] = "-A"; 1930 path = rooted(extendpath(m->path, "raw")); 1931 av[ai++] = s_to_c(path); 1932 } 1933 1934 for(i = 1; i < c->an && ai < nelem(av)-1; i++) 1935 av[ai++] = c->av[i]; 1936 from = s_copy(m->from); 1937 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); 1938 to = s_copy(m->to); 1939 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai); 1940 cc = s_copy(m->cc); 1941 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai); 1942 av[ai] = 0; 1943 if(tomailer(av) < 0) 1944 m = nil; 1945 s_free(path); 1946 s_free(rpath); 1947 s_free(subject); 1948 s_free(from); 1949 s_free(to); 1950 s_free(cc); 1951 return m; 1952 } 1953 1954 String * 1955 relpath(char *path, String *to) 1956 { 1957 if (*path=='/' || strncmp(path, "./", 2) == 0 1958 || strncmp(path, "../", 3) == 0) { 1959 to = s_append(to, path); 1960 } else if(mbpath) { 1961 to = s_append(to, s_to_c(mbpath)); 1962 to->ptr = strrchr(to->base, '/')+1; 1963 s_append(to, path); 1964 } 1965 return to; 1966 } 1967 1968 int 1969 appendtofile(Message *m, char *part, char *base, int mbox) 1970 { 1971 String *file, *h; 1972 int in, out, rv; 1973 1974 file = extendpath(m->path, part); 1975 in = open(s_to_c(file), OREAD); 1976 if(in < 0){ 1977 fprint(2, "!message disappeared\n"); 1978 return -1; 1979 } 1980 1981 s_reset(file); 1982 1983 relpath(base, file); 1984 if(sysisdir(s_to_c(file))){ 1985 s_append(file, "/"); 1986 if(m->filename && strchr(m->filename, '/') == nil) 1987 s_append(file, m->filename); 1988 else { 1989 s_append(file, "att.XXXXXXXXXXX"); 1990 mktemp(s_to_c(file)); 1991 } 1992 } 1993 if(mbox) 1994 out = open(s_to_c(file), OWRITE); 1995 else 1996 out = open(s_to_c(file), OWRITE|OTRUNC); 1997 if(out < 0){ 1998 out = create(s_to_c(file), OWRITE, 0666); 1999 if(out < 0){ 2000 fprint(2, "!can't open %s: %r\n", s_to_c(file)); 2001 close(in); 2002 s_free(file); 2003 return -1; 2004 } 2005 } 2006 if(mbox) 2007 seek(out, 0, 2); 2008 2009 // put on a 'From ' line 2010 if(mbox){ 2011 while(m->parent != &top) 2012 m = m->parent; 2013 h = file2string(m->path, "unixheader"); 2014 fprint(out, "%s", s_to_c(h)); 2015 s_free(h); 2016 } 2017 2018 // copy the message escaping what we have to ad adding newlines if we have to 2019 if(mbox) 2020 rv = appendfiletombox(in, out); 2021 else 2022 rv = appendfiletofile(in, out); 2023 2024 close(in); 2025 close(out); 2026 2027 if(rv >= 0) 2028 print("!saved in %s\n", s_to_c(file)); 2029 s_free(file); 2030 return rv; 2031 } 2032 2033 Message* 2034 scmd(Cmd *c, Message *m) 2035 { 2036 char *file; 2037 2038 if(m == &top){ 2039 Bprint(&out, "!address\n"); 2040 return nil; 2041 } 2042 2043 switch(c->an){ 2044 case 1: 2045 file = "stored"; 2046 break; 2047 case 2: 2048 file = c->av[1]; 2049 break; 2050 default: 2051 fprint(2, "!usage: s filename\n"); 2052 return nil; 2053 } 2054 2055 if(appendtofile(m, "raw", file, 1) < 0) 2056 return nil; 2057 2058 m->stored = 1; 2059 return m; 2060 } 2061 2062 Message* 2063 wcmd(Cmd *c, Message *m) 2064 { 2065 char *file; 2066 2067 if(m == &top){ 2068 Bprint(&out, "!address\n"); 2069 return nil; 2070 } 2071 2072 switch(c->an){ 2073 case 2: 2074 file = c->av[1]; 2075 break; 2076 case 1: 2077 if(*m->filename == 0){ 2078 fprint(2, "!usage: w filename\n"); 2079 return nil; 2080 } 2081 file = strrchr(m->filename, '/'); 2082 if(file != nil) 2083 file++; 2084 else 2085 file = m->filename; 2086 break; 2087 default: 2088 fprint(2, "!usage: w filename\n"); 2089 return nil; 2090 } 2091 2092 if(appendtofile(m, "body", file, 0) < 0) 2093 return nil; 2094 m->stored = 1; 2095 return m; 2096 } 2097 2098 char *specialfile[] = 2099 { 2100 "pipeto", 2101 "pipefrom", 2102 "L.mbox", 2103 "forward", 2104 "names" 2105 }; 2106 2107 // return 1 if this is a special file 2108 static int 2109 special(String *s) 2110 { 2111 char *p; 2112 int i; 2113 2114 p = strrchr(s_to_c(s), '/'); 2115 if(p == nil) 2116 p = s_to_c(s); 2117 else 2118 p++; 2119 for(i = 0; i < nelem(specialfile); i++) 2120 if(strcmp(p, specialfile[i]) == 0) 2121 return 1; 2122 return 0; 2123 } 2124 2125 // open the folder using the recipients account name 2126 static String* 2127 foldername(char *rcvr) 2128 { 2129 char *p; 2130 int c; 2131 String *file; 2132 Dir *d; 2133 int scarey; 2134 2135 file = s_new(); 2136 mboxpath("f", user, file, 0); 2137 d = dirstat(s_to_c(file)); 2138 2139 // if $mail/f exists, store there, otherwise in $mail 2140 s_restart(file); 2141 if(d && d->qid.type == QTDIR){ 2142 scarey = 0; 2143 s_append(file, "f/"); 2144 } else { 2145 scarey = 1; 2146 } 2147 free(d); 2148 2149 p = strrchr(rcvr, '!'); 2150 if(p != nil) 2151 rcvr = p+1; 2152 2153 while(*rcvr && *rcvr != '@'){ 2154 c = *rcvr++; 2155 if(c == '/') 2156 c = '_'; 2157 s_putc(file, c); 2158 } 2159 s_terminate(file); 2160 2161 if(scarey && special(file)){ 2162 fprint(2, "!won't overwrite %s\n", s_to_c(file)); 2163 s_free(file); 2164 return nil; 2165 } 2166 2167 return file; 2168 } 2169 2170 Message* 2171 fcmd(Cmd *c, Message *m) 2172 { 2173 String *folder; 2174 2175 if(c->an > 1){ 2176 fprint(2, "!usage: f takes no arguments\n"); 2177 return nil; 2178 } 2179 2180 if(m == &top){ 2181 Bprint(&out, "!address\n"); 2182 return nil; 2183 } 2184 2185 folder = foldername(m->from); 2186 if(folder == nil) 2187 return nil; 2188 2189 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){ 2190 s_free(folder); 2191 return nil; 2192 } 2193 s_free(folder); 2194 2195 m->stored = 1; 2196 return m; 2197 } 2198 2199 void 2200 system(char *cmd, char **av, int in) 2201 { 2202 int pid; 2203 2204 switch(pid=fork()){ 2205 case -1: 2206 return; 2207 case 0: 2208 if(in >= 0){ 2209 close(0); 2210 dup(in, 0); 2211 close(in); 2212 } 2213 if(wd[0] != 0) 2214 chdir(wd); 2215 exec(cmd, av); 2216 fprint(2, "!couldn't exec %s\n", cmd); 2217 exits(0); 2218 default: 2219 if(in >= 0) 2220 close(in); 2221 while(waitpid() < 0){ 2222 if(!interrupted) 2223 break; 2224 postnote(PNPROC, pid, "die"); 2225 continue; 2226 } 2227 break; 2228 } 2229 } 2230 2231 Message* 2232 bangcmd(Cmd *c, Message *m) 2233 { 2234 char cmd[4*1024]; 2235 char *p, *e; 2236 char *av[4]; 2237 int i; 2238 2239 cmd[0] = 0; 2240 p = cmd; 2241 e = cmd+sizeof(cmd); 2242 for(i = 1; i < c->an; i++) 2243 p = seprint(p, e, "%s ", c->av[i]); 2244 av[0] = "rc"; 2245 av[1] = "-c"; 2246 av[2] = cmd; 2247 av[3] = 0; 2248 system("/bin/rc", av, -1); 2249 Bprint(&out, "!\n"); 2250 return m; 2251 } 2252 2253 Message* 2254 xpipecmd(Cmd *c, Message *m, char *part) 2255 { 2256 char cmd[128]; 2257 char *p, *e; 2258 char *av[4]; 2259 String *path; 2260 int i, fd; 2261 2262 if(c->an < 2){ 2263 Bprint(&out, "!usage: | cmd\n"); 2264 return nil; 2265 } 2266 2267 if(m == &top){ 2268 Bprint(&out, "!address\n"); 2269 return nil; 2270 } 2271 2272 path = extendpath(m->path, part); 2273 fd = open(s_to_c(path), OREAD); 2274 s_free(path); 2275 if(fd < 0){ // compatibility with older upas/fs 2276 path = extendpath(m->path, "raw"); 2277 fd = open(s_to_c(path), OREAD); 2278 s_free(path); 2279 } 2280 if(fd < 0){ 2281 fprint(2, "!message disappeared\n"); 2282 return nil; 2283 } 2284 2285 p = cmd; 2286 e = cmd+sizeof(cmd); 2287 cmd[0] = 0; 2288 for(i = 1; i < c->an; i++) 2289 p = seprint(p, e, "%s ", c->av[i]); 2290 av[0] = "rc"; 2291 av[1] = "-c"; 2292 av[2] = cmd; 2293 av[3] = 0; 2294 system("/bin/rc", av, fd); /* system closes fd */ 2295 Bprint(&out, "!\n"); 2296 return m; 2297 } 2298 2299 Message* 2300 pipecmd(Cmd *c, Message *m) 2301 { 2302 return xpipecmd(c, m, "body"); 2303 } 2304 2305 Message* 2306 rpipecmd(Cmd *c, Message *m) 2307 { 2308 return xpipecmd(c, m, "rawunix"); 2309 } 2310 2311 void 2312 closemb(void) 2313 { 2314 int fd; 2315 2316 fd = open("/mail/fs/ctl", ORDWR); 2317 if(fd < 0) 2318 sysfatal("can't open /mail/fs/ctl: %r"); 2319 2320 // close current mailbox 2321 if(*mbname && strcmp(mbname, "mbox") != 0) 2322 fprint(fd, "close %s", mbname); 2323 2324 close(fd); 2325 } 2326 2327 int 2328 switchmb(char *file, char *singleton) 2329 { 2330 char *p; 2331 int n, fd; 2332 String *path; 2333 char buf[256]; 2334 2335 // if the user didn't say anything and there 2336 // is an mbox mounted already, use that one 2337 // so that the upas/fs -fdefault default is honored. 2338 if(file 2339 || (singleton && access(singleton, 0)<0) 2340 || (!singleton && access("/mail/fs/mbox", 0)<0)){ 2341 if(file == nil) 2342 file = "mbox"; 2343 2344 // close current mailbox 2345 closemb(); 2346 didopen = 1; 2347 2348 fd = open("/mail/fs/ctl", ORDWR); 2349 if(fd < 0) 2350 sysfatal("can't open /mail/fs/ctl: %r"); 2351 2352 path = s_new(); 2353 2354 // get an absolute path to the mail box 2355 if(strncmp(file, "./", 2) == 0){ 2356 // resolve path here since upas/fs doesn't know 2357 // our working directory 2358 if(getwd(buf, sizeof(buf)-strlen(file)) == nil){ 2359 fprint(2, "!can't get working directory: %s\n", buf); 2360 return -1; 2361 } 2362 s_append(path, buf); 2363 s_append(path, file+1); 2364 } else { 2365 mboxpath(file, user, path, 0); 2366 } 2367 2368 // make up a handle to use when talking to fs 2369 p = strrchr(file, '/'); 2370 if(p == nil){ 2371 // if its in the mailbox directory, just use the name 2372 strncpy(mbname, file, sizeof(mbname)); 2373 mbname[sizeof(mbname)-1] = 0; 2374 } else { 2375 // make up a mailbox name 2376 p = strrchr(s_to_c(path), '/'); 2377 p++; 2378 if(*p == 0){ 2379 fprint(2, "!bad mbox name"); 2380 return -1; 2381 } 2382 strncpy(mbname, p, sizeof(mbname)); 2383 mbname[sizeof(mbname)-1] = 0; 2384 n = strlen(mbname); 2385 if(n > Elemlen-12) 2386 n = Elemlen-12; 2387 sprint(mbname+n, "%ld", time(0)); 2388 } 2389 2390 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){ 2391 fprint(2, "!can't 'open %s %s': %r\n", file, mbname); 2392 s_free(path); 2393 return -1; 2394 } 2395 close(fd); 2396 }else 2397 if (singleton && access(singleton, 0)==0 2398 && strncmp(singleton, "/mail/fs/", 9) == 0){ 2399 if ((p = strchr(singleton +10, '/')) == nil){ 2400 fprint(2, "!bad mbox name"); 2401 return -1; 2402 } 2403 n = p-(singleton+9); 2404 strncpy(mbname, singleton+9, n); 2405 mbname[n+1] = 0; 2406 path = s_reset(nil); 2407 mboxpath(mbname, user, path, 0); 2408 }else{ 2409 path = s_reset(nil); 2410 mboxpath("mbox", user, path, 0); 2411 strcpy(mbname, "mbox"); 2412 } 2413 2414 snprint(root, sizeof root, "/mail/fs/%s", mbname); 2415 if(getwd(wd, sizeof(wd)) == 0) 2416 wd[0] = 0; 2417 if(singleton == nil && chdir(root) >= 0) 2418 strcpy(root, "."); 2419 rootlen = strlen(root); 2420 2421 if(mbpath != nil) 2422 s_free(mbpath); 2423 mbpath = path; 2424 return 0; 2425 } 2426 2427 // like tokenize but for into lines 2428 int 2429 lineize(char *s, char **f, int n) 2430 { 2431 int i; 2432 2433 for(i = 0; *s && i < n; i++){ 2434 f[i] = s; 2435 s = strchr(s, '\n'); 2436 if(s == nil) 2437 break; 2438 *s++ = 0; 2439 } 2440 return i; 2441 } 2442 2443 2444 2445 String* 2446 rooted(String *s) 2447 { 2448 static char buf[256]; 2449 2450 if(strcmp(root, ".") != 0) 2451 return s; 2452 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s)); 2453 s_free(s); 2454 return s_copy(buf); 2455 } 2456 2457 int 2458 plumb(Message *m, Ctype *cp) 2459 { 2460 String *s; 2461 Plumbmsg *pm; 2462 static int fd = -2; 2463 2464 if(cp->plumbdest == nil) 2465 return -1; 2466 2467 if(fd < -1) 2468 fd = plumbopen("send", OWRITE); 2469 if(fd < 0) 2470 return -1; 2471 2472 pm = mallocz(sizeof(Plumbmsg), 1); 2473 pm->src = strdup("mail"); 2474 if(*cp->plumbdest) 2475 pm->dst = strdup(cp->plumbdest); 2476 pm->wdir = nil; 2477 pm->type = strdup("text"); 2478 pm->ndata = -1; 2479 s = rooted(extendpath(m->path, "body")); 2480 if(cp->ext != nil){ 2481 s_append(s, "."); 2482 s_append(s, cp->ext); 2483 } 2484 pm->data = strdup(s_to_c(s)); 2485 s_free(s); 2486 plumbsend(fd, pm); 2487 plumbfree(pm); 2488 return 0; 2489 } 2490 2491 void 2492 regerror(char*) 2493 { 2494 } 2495 2496 String* 2497 addrecolon(char *s) 2498 { 2499 String *str; 2500 2501 if(cistrncmp(s, "re:", 3) != 0){ 2502 str = s_copy("Re: "); 2503 s_append(str, s); 2504 } else 2505 str = s_copy(s); 2506 return str; 2507 } 2508 2509 void 2510 exitfs(char *rv) 2511 { 2512 if(startedfs) 2513 unmount(nil, "/mail/fs"); 2514 chdir("/sys/src/cmd/upas/ned"); /* for profiling? */ 2515 exits(rv); 2516 } 2517