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 char *err; 216 int n, cflag; 217 char *av[4]; 218 String *prompt; 219 char *file, *singleton; 220 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 char* 881 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp) 882 { 883 int n; 884 Message *m; 885 char *p; 886 Reprog *prog; 887 int c, sign; 888 char buf[256]; 889 890 *mp = nil; 891 p = *pp; 892 893 if(*p == '+'){ 894 sign = 1; 895 p++; 896 *pp = p; 897 } else if(*p == '-'){ 898 sign = -1; 899 p++; 900 *pp = p; 901 } else 902 sign = 0; 903 904 switch(*p){ 905 default: 906 if(sign){ 907 n = 1; 908 goto number; 909 } 910 *mp = unspec; 911 break; 912 case '0': case '1': case '2': case '3': case '4': 913 case '5': case '6': case '7': case '8': case '9': 914 n = strtoul(p, pp, 10); 915 if(n == 0){ 916 if(sign) 917 *mp = cur; 918 else 919 *mp = ⊤ 920 break; 921 } 922 number: 923 m = nil; 924 switch(sign){ 925 case 0: 926 for(m = first; m != nil; m = m->next) 927 if(m->id == n) 928 break; 929 break; 930 case -1: 931 if(cur != &top) 932 for(m = cur; m != nil && n > 0; n--) 933 m = m->prev; 934 break; 935 case 1: 936 if(cur == &top){ 937 n--; 938 cur = first; 939 } 940 for(m = cur; m != nil && n > 0; n--) 941 m = m->next; 942 break; 943 } 944 if(m == nil) 945 return "address"; 946 *mp = m; 947 break; 948 case '%': 949 case '/': 950 case '?': 951 c = *p; 952 prog = parsesearch(pp); 953 if(prog == nil) 954 return "badly formed regular expression"; 955 m = nil; 956 switch(c){ 957 case '%': 958 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ 959 if(rawsearch(m, prog)) 960 break; 961 } 962 break; 963 case '/': 964 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ 965 snprintheader(buf, sizeof(buf), m); 966 if(regexec(prog, buf, nil, 0)) 967 break; 968 } 969 break; 970 case '?': 971 for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){ 972 snprintheader(buf, sizeof(buf), m); 973 if(regexec(prog, buf, nil, 0)) 974 break; 975 } 976 break; 977 } 978 if(m == nil) 979 return "search"; 980 *mp = m; 981 free(prog); 982 break; 983 case '$': 984 for(m = first; m != nil && m->next != nil; m = m->next) 985 ; 986 *mp = m; 987 *pp = p+1; 988 break; 989 case '.': 990 *mp = cur; 991 *pp = p+1; 992 break; 993 case ',': 994 *mp = first; 995 *pp = p; 996 break; 997 } 998 999 if(*mp != nil && **pp == '.'){ 1000 (*pp)++; 1001 if((*mp)->child == nil) 1002 return "no sub parts"; 1003 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp); 1004 } 1005 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%') 1006 return parseaddr(pp, first, *mp, *mp, mp); 1007 1008 return nil; 1009 } 1010 1011 // 1012 // search a message for a regular expression match 1013 // 1014 int 1015 rawsearch(Message *m, Reprog *prog) 1016 { 1017 char buf[4096+1]; 1018 int i, fd, rv; 1019 String *path; 1020 1021 path = extendpath(m->path, "raw"); 1022 fd = open(s_to_c(path), OREAD); 1023 if(fd < 0) 1024 return 0; 1025 1026 // march through raw message 4096 bytes at a time 1027 // with a 128 byte overlap to chain the re search. 1028 rv = 0; 1029 for(;;){ 1030 i = read(fd, buf, sizeof(buf)-1); 1031 if(i <= 0) 1032 break; 1033 buf[i] = 0; 1034 if(regexec(prog, buf, nil, 0)){ 1035 rv = 1; 1036 break; 1037 } 1038 if(i < sizeof(buf)-1) 1039 break; 1040 if(seek(fd, -128LL, 1) < 0) 1041 break; 1042 } 1043 1044 close(fd); 1045 s_free(path); 1046 return rv; 1047 } 1048 1049 1050 char* 1051 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur) 1052 { 1053 Reprog *prog; 1054 Message *m, *s, *e, **l, *last; 1055 char buf[256]; 1056 char *err; 1057 int i, c; 1058 char *q; 1059 static char errbuf[Errlen]; 1060 1061 cmd->delete = 0; 1062 l = &cmd->msgs; 1063 *l = nil; 1064 1065 // eat white space 1066 while(*p == ' ') 1067 p++; 1068 1069 // null command is a special case (advance and print) 1070 if(*p == 0){ 1071 if(cur == &top){ 1072 // special case 1073 m = first; 1074 } else { 1075 // walk to the next message even if we have to go up 1076 m = cur->next; 1077 while(m == nil && cur->parent != nil){ 1078 cur = cur->parent; 1079 m = cur->next; 1080 } 1081 } 1082 if(m == nil) 1083 return "address"; 1084 *l = m; 1085 m->cmd = nil; 1086 cmd->an = 0; 1087 cmd->f = pcmd; 1088 return nil; 1089 } 1090 1091 // global search ? 1092 if(*p == 'g'){ 1093 p++; 1094 1095 // no search string means all messages 1096 if(*p != '/' && *p != '%'){ 1097 for(m = first; m != nil; m = m->next){ 1098 *l = m; 1099 l = &m->cmd; 1100 *l = nil; 1101 } 1102 } else { 1103 // mark all messages matching this search string 1104 c = *p; 1105 prog = parsesearch(&p); 1106 if(prog == nil) 1107 return "badly formed regular expression"; 1108 if(c == '%'){ 1109 for(m = first; m != nil; m = m->next){ 1110 if(rawsearch(m, prog)){ 1111 *l = m; 1112 l = &m->cmd; 1113 *l = nil; 1114 } 1115 } 1116 } else { 1117 for(m = first; m != nil; m = m->next){ 1118 snprintheader(buf, sizeof(buf), m); 1119 if(regexec(prog, buf, nil, 0)){ 1120 *l = m; 1121 l = &m->cmd; 1122 *l = nil; 1123 } 1124 } 1125 } 1126 free(prog); 1127 } 1128 } else { 1129 1130 // parse an address 1131 s = e = nil; 1132 err = parseaddr(&p, first, cur, cur, &s); 1133 if(err != nil) 1134 return err; 1135 if(*p == ','){ 1136 // this is an address range 1137 if(s == &top) 1138 s = first; 1139 p++; 1140 for(last = s; last != nil && last->next != nil; last = last->next) 1141 ; 1142 err = parseaddr(&p, first, cur, last, &e); 1143 if(err != nil) 1144 return err; 1145 1146 // select all messages in the range 1147 for(; s != nil; s = s->next){ 1148 *l = s; 1149 l = &s->cmd; 1150 *l = nil; 1151 if(s == e) 1152 break; 1153 } 1154 if(s == nil) 1155 return "null address range"; 1156 } else { 1157 // single address 1158 if(s != &top){ 1159 *l = s; 1160 s->cmd = nil; 1161 } 1162 } 1163 } 1164 1165 // insert a space after '!'s and '|'s 1166 for(q = p; *q; q++) 1167 if(*q != '!' && *q != '|') 1168 break; 1169 if(q != p && *q != ' '){ 1170 memmove(q+1, q, strlen(q)+1); 1171 *q = ' '; 1172 } 1173 1174 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n"); 1175 if(cmd->an == 0 || *cmd->av[0] == 0) 1176 cmd->f = pcmd; 1177 else { 1178 // hack to allow all messages to start with 'd' 1179 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){ 1180 cmd->delete = 1; 1181 cmd->av[0]++; 1182 } 1183 1184 // search command table 1185 for(i = 0; cmdtab[i].cmd != nil; i++) 1186 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0) 1187 break; 1188 if(cmdtab[i].cmd == nil) 1189 return "illegal command"; 1190 if(cmdtab[i].args == 0 && cmd->an > 1){ 1191 snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd); 1192 return errbuf; 1193 } 1194 cmd->f = cmdtab[i].f; 1195 } 1196 return nil; 1197 } 1198 1199 // inefficient read from standard input 1200 char* 1201 readline(char *prompt, char *line, int len) 1202 { 1203 char *p, *e; 1204 int n; 1205 1206 retry: 1207 interrupted = 0; 1208 Bprint(&out, "%s", prompt); 1209 Bflush(&out); 1210 e = line + len; 1211 for(p = line; p < e; p++){ 1212 n = read(0, p, 1); 1213 if(n < 0){ 1214 if(interrupted) 1215 goto retry; 1216 return nil; 1217 } 1218 if(n == 0) 1219 return nil; 1220 if(*p == '\n') 1221 break; 1222 } 1223 *p = 0; 1224 return line; 1225 } 1226 1227 void 1228 messagecount(Message *m) 1229 { 1230 int i; 1231 1232 i = 0; 1233 for(; m != nil; m = m->next) 1234 i++; 1235 Bprint(&out, "%d message%s\n", i, plural(i)); 1236 } 1237 1238 Message* 1239 aichcmd(Message *m, int indent) 1240 { 1241 char hdr[256]; 1242 1243 if(m == &top) 1244 return nil; 1245 1246 snprintHeader(hdr, sizeof(hdr), indent, m); 1247 Bprint(&out, "%s\n", hdr); 1248 for(m = m->child; m != nil; m = m->next) 1249 aichcmd(m, indent+1); 1250 return nil; 1251 } 1252 1253 Message* 1254 Hcmd(Cmd*, Message *m) 1255 { 1256 if(m == &top) 1257 return nil; 1258 aichcmd(m, 0); 1259 return nil; 1260 } 1261 1262 Message* 1263 hcmd(Cmd*, Message *m) 1264 { 1265 char hdr[256]; 1266 1267 if(m == &top) 1268 return nil; 1269 1270 snprintheader(hdr, sizeof(hdr), m); 1271 Bprint(&out, "%s\n", hdr); 1272 return nil; 1273 } 1274 1275 Message* 1276 bcmd(Cmd*, Message *m) 1277 { 1278 int i; 1279 Message *om = m; 1280 1281 if(m == &top) 1282 m = top.child; 1283 for(i = 0; i < 10 && m != nil; i++){ 1284 hcmd(nil, m); 1285 om = m; 1286 m = m->next; 1287 } 1288 1289 return om; 1290 } 1291 1292 Message* 1293 ncmd(Cmd*, Message *m) 1294 { 1295 if(m == &top) 1296 return m->child; 1297 return m->next; 1298 } 1299 1300 int 1301 printpart(String *s, char *part) 1302 { 1303 char buf[4096]; 1304 int n, fd, tot; 1305 String *path; 1306 1307 path = extendpath(s, part); 1308 fd = open(s_to_c(path), OREAD); 1309 s_free(path); 1310 if(fd < 0){ 1311 fprint(2, "!message disappeared\n"); 1312 return 0; 1313 } 1314 tot = 0; 1315 while((n = read(fd, buf, sizeof(buf))) > 0){ 1316 if(interrupted) 1317 break; 1318 if(Bwrite(&out, buf, n) <= 0) 1319 break; 1320 tot += n; 1321 } 1322 close(fd); 1323 return tot; 1324 } 1325 1326 int 1327 printhtml(Message *m) 1328 { 1329 Cmd c; 1330 1331 c.an = 3; 1332 c.av[1] = "/bin/htmlfmt"; 1333 c.av[2] = "-l 40 -cutf-8"; 1334 Bprint(&out, "!%s\n", c.av[1]); 1335 Bflush(&out); 1336 pipecmd(&c, m); 1337 return 0; 1338 } 1339 1340 Message* 1341 Pcmd(Cmd*, Message *m) 1342 { 1343 if(m == &top) 1344 return ⊤ 1345 if(m->parent == &top) 1346 printpart(m->path, "unixheader"); 1347 printpart(m->path, "raw"); 1348 return m; 1349 } 1350 1351 void 1352 compress(char *p) 1353 { 1354 char *np; 1355 int last; 1356 1357 last = ' '; 1358 for(np = p; *p; p++){ 1359 if(*p != ' ' || last != ' '){ 1360 last = *p; 1361 *np++ = last; 1362 } 1363 } 1364 *np = 0; 1365 } 1366 1367 Message* 1368 pcmd(Cmd*, Message *m) 1369 { 1370 Message *nm; 1371 Ctype *cp; 1372 String *s; 1373 char buf[128]; 1374 1375 if(m == &top) 1376 return ⊤ 1377 if(m->parent == &top) 1378 printpart(m->path, "unixheader"); 1379 if(printpart(m->path, "header") > 0) 1380 Bprint(&out, "\n"); 1381 cp = findctype(m); 1382 if(cp->display){ 1383 if(strcmp(m->type, "text/html") == 0) 1384 printhtml(m); 1385 else 1386 printpart(m->path, "body"); 1387 } else if(strcmp(m->type, "multipart/alternative") == 0){ 1388 for(nm = m->child; nm != nil; nm = nm->next){ 1389 cp = findctype(nm); 1390 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) 1391 break; 1392 } 1393 if(nm == nil) 1394 for(nm = m->child; nm != nil; nm = nm->next){ 1395 cp = findctype(nm); 1396 if(cp->display) 1397 break; 1398 } 1399 if(nm != nil) 1400 pcmd(nil, nm); 1401 else 1402 hcmd(nil, m); 1403 } else if(strncmp(m->type, "multipart/", 10) == 0){ 1404 nm = m->child; 1405 if(nm != nil){ 1406 // always print first part 1407 pcmd(nil, nm); 1408 1409 for(nm = nm->next; nm != nil; nm = nm->next){ 1410 s = rooted(s_clone(nm->path)); 1411 cp = findctype(nm); 1412 snprintHeader(buf, sizeof buf, -1, nm); 1413 compress(buf); 1414 if(strcmp(nm->disposition, "inline") == 0){ 1415 if(cp->ext != nil) 1416 Bprint(&out, "\n--- %s %s/body.%s\n\n", 1417 buf, s_to_c(s), cp->ext); 1418 else 1419 Bprint(&out, "\n--- %s %s/body\n\n", 1420 buf, s_to_c(s)); 1421 pcmd(nil, nm); 1422 } else { 1423 if(cp->ext != nil) 1424 Bprint(&out, "\n!--- %s %s/body.%s\n", 1425 buf, s_to_c(s), cp->ext); 1426 else 1427 Bprint(&out, "\n!--- %s %s/body\n", 1428 buf, s_to_c(s)); 1429 } 1430 s_free(s); 1431 } 1432 } else { 1433 hcmd(nil, m); 1434 } 1435 } else if(strcmp(m->type, "message/rfc822") == 0){ 1436 pcmd(nil, m->child); 1437 } else if(plumb(m, cp) >= 0) 1438 Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type); 1439 else 1440 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type); 1441 1442 return m; 1443 } 1444 1445 void 1446 printpartindented(String *s, char *part, char *indent) 1447 { 1448 char *p; 1449 String *path; 1450 Biobuf *b; 1451 1452 path = extendpath(s, part); 1453 b = Bopen(s_to_c(path), OREAD); 1454 s_free(path); 1455 if(b == nil){ 1456 fprint(2, "!message disappeared\n"); 1457 return; 1458 } 1459 while((p = Brdline(b, '\n')) != nil){ 1460 if(interrupted) 1461 break; 1462 p[Blinelen(b)-1] = 0; 1463 if(Bprint(&out, "%s%s\n", indent, p) < 0) 1464 break; 1465 } 1466 Bprint(&out, "\n"); 1467 Bterm(b); 1468 } 1469 1470 Message* 1471 quotecmd(Cmd*, Message *m) 1472 { 1473 Message *nm; 1474 Ctype *cp; 1475 1476 if(m == &top) 1477 return ⊤ 1478 Bprint(&out, "\n"); 1479 if(m->from != nil && *m->from) 1480 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from); 1481 cp = findctype(m); 1482 if(cp->display){ 1483 printpartindented(m->path, "body", "> "); 1484 } else if(strcmp(m->type, "multipart/alternative") == 0){ 1485 for(nm = m->child; nm != nil; nm = nm->next){ 1486 cp = findctype(nm); 1487 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) 1488 break; 1489 } 1490 if(nm == nil) 1491 for(nm = m->child; nm != nil; nm = nm->next){ 1492 cp = findctype(nm); 1493 if(cp->display) 1494 break; 1495 } 1496 if(nm != nil) 1497 quotecmd(nil, nm); 1498 } else if(strncmp(m->type, "multipart/", 10) == 0){ 1499 nm = m->child; 1500 if(nm != nil){ 1501 cp = findctype(nm); 1502 if(cp->display || strncmp(m->type, "multipart/", 10) == 0) 1503 quotecmd(nil, nm); 1504 } 1505 } 1506 return m; 1507 } 1508 1509 // really delete messages 1510 Message* 1511 flushdeleted(Message *cur) 1512 { 1513 Message *m, **l; 1514 char buf[1024], *p, *e, *msg; 1515 int deld, n, fd; 1516 int i; 1517 1518 doflush = 0; 1519 deld = 0; 1520 1521 fd = open("/mail/fs/ctl", ORDWR); 1522 if(fd < 0){ 1523 fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n"); 1524 exitfs(0); 1525 } 1526 e = &buf[sizeof(buf)]; 1527 p = seprint(buf, e, "delete %s", mbname); 1528 n = 0; 1529 for(l = &top.child; *l != nil;){ 1530 m = *l; 1531 if(!m->deleted){ 1532 l = &(*l)->next; 1533 continue; 1534 } 1535 1536 // don't return a pointer to a deleted message 1537 if(m == cur) 1538 cur = m->next; 1539 1540 deld++; 1541 msg = strrchr(s_to_c(m->path), '/'); 1542 if(msg == nil) 1543 msg = s_to_c(m->path); 1544 else 1545 msg++; 1546 if(e-p < 10){ 1547 write(fd, buf, p-buf); 1548 n = 0; 1549 p = seprint(buf, e, "delete %s", mbname); 1550 } 1551 p = seprint(p, e, " %s", msg); 1552 n++; 1553 1554 // unchain and free 1555 *l = m->next; 1556 if(m->next) 1557 m->next->prev = m->prev; 1558 freemessage(m); 1559 } 1560 if(n) 1561 write(fd, buf, p-buf); 1562 1563 close(fd); 1564 1565 if(deld) 1566 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld)); 1567 1568 // renumber 1569 i = 1; 1570 for(m = top.child; m != nil; m = m->next) 1571 m->id = natural ? m->fileno : i++; 1572 1573 // if we're out of messages, go back to first 1574 // if no first, return the fake first 1575 if(cur == nil){ 1576 if(top.child) 1577 return top.child; 1578 else 1579 return ⊤ 1580 } 1581 return cur; 1582 } 1583 1584 Message* 1585 qcmd(Cmd*, Message*) 1586 { 1587 flushdeleted(nil); 1588 1589 if(didopen) 1590 closemb(); 1591 Bflush(&out); 1592 1593 exitfs(0); 1594 return nil; // not reached 1595 } 1596 1597 Message* 1598 ycmd(Cmd*, Message *m) 1599 { 1600 doflush = 1; 1601 1602 return icmd(nil, m); 1603 } 1604 1605 Message* 1606 xcmd(Cmd*, Message*) 1607 { 1608 exitfs(0); 1609 return nil; // not reached 1610 } 1611 1612 Message* 1613 eqcmd(Cmd*, Message *m) 1614 { 1615 if(m == &top) 1616 Bprint(&out, "0\n"); 1617 else 1618 Bprint(&out, "%d\n", m->id); 1619 return nil; 1620 } 1621 1622 Message* 1623 dcmd(Cmd*, Message *m) 1624 { 1625 if(m == &top){ 1626 Bprint(&out, "!address\n"); 1627 return nil; 1628 } 1629 while(m->parent != &top) 1630 m = m->parent; 1631 m->deleted = 1; 1632 return m; 1633 } 1634 1635 Message* 1636 ucmd(Cmd*, Message *m) 1637 { 1638 if(m == &top) 1639 return nil; 1640 while(m->parent != &top) 1641 m = m->parent; 1642 if(m->deleted < 0) 1643 Bprint(&out, "!can't undelete, already flushed\n"); 1644 m->deleted = 0; 1645 return m; 1646 } 1647 1648 1649 Message* 1650 icmd(Cmd*, Message *m) 1651 { 1652 int n; 1653 1654 n = dir2message(&top, reverse); 1655 if(n > 0) 1656 Bprint(&out, "%d new message%s\n", n, plural(n)); 1657 return m; 1658 } 1659 1660 Message* 1661 helpcmd(Cmd*, Message *m) 1662 { 1663 int i; 1664 1665 Bprint(&out, "Commands are of the form [<range>] <command> [args]\n"); 1666 Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n"); 1667 Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n"); 1668 Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n"); 1669 Bprint(&out, "<command> :=\n"); 1670 for(i = 0; cmdtab[i].cmd != nil; i++) 1671 Bprint(&out, "%s\n", cmdtab[i].help); 1672 return m; 1673 } 1674 1675 int 1676 tomailer(char **av) 1677 { 1678 Waitmsg *w; 1679 int pid, i; 1680 1681 // start the mailer and get out of the way 1682 switch(pid = fork()){ 1683 case -1: 1684 fprint(2, "can't fork: %r\n"); 1685 return -1; 1686 case 0: 1687 Bprint(&out, "!/bin/upas/marshal"); 1688 for(i = 1; av[i]; i++){ 1689 if(strchr(av[i], ' ') != nil) 1690 Bprint(&out, " '%s'", av[i]); 1691 else 1692 Bprint(&out, " %s", av[i]); 1693 } 1694 Bprint(&out, "\n"); 1695 Bflush(&out); 1696 av[0] = "marshal"; 1697 chdir(wd); 1698 exec("/bin/upas/marshal", av); 1699 fprint(2, "couldn't exec /bin/upas/marshal\n"); 1700 exits(0); 1701 default: 1702 w = wait(); 1703 if(w == nil){ 1704 if(interrupted) 1705 postnote(PNPROC, pid, "die"); 1706 waitpid(); 1707 return -1; 1708 } 1709 if(w->msg[0]){ 1710 fprint(2, "mailer failed: %s\n", w->msg); 1711 free(w); 1712 return -1; 1713 } 1714 free(w); 1715 Bprint(&out, "!\n"); 1716 break; 1717 } 1718 return 0; 1719 } 1720 1721 // 1722 // like tokenize but obey "" quoting 1723 // 1724 int 1725 tokenize822(char *str, char **args, int max) 1726 { 1727 int na; 1728 int intok = 0, inquote = 0; 1729 1730 if(max <= 0) 1731 return 0; 1732 for(na=0; ;str++) 1733 switch(*str) { 1734 case ' ': 1735 case '\t': 1736 if(inquote) 1737 goto Default; 1738 /* fall through */ 1739 case '\n': 1740 *str = 0; 1741 if(!intok) 1742 continue; 1743 intok = 0; 1744 if(na < max) 1745 continue; 1746 /* fall through */ 1747 case 0: 1748 return na; 1749 case '"': 1750 inquote ^= 1; 1751 /* fall through */ 1752 Default: 1753 default: 1754 if(intok) 1755 continue; 1756 args[na++] = str; 1757 intok = 1; 1758 } 1759 } 1760 1761 /* return reply-to address & set *nmp to corresponding Message */ 1762 static char * 1763 getreplyto(Message *m, Message **nmp) 1764 { 1765 Message *nm; 1766 1767 for(nm = m; nm != ⊤ nm = nm->parent) 1768 if(*nm->replyto != 0) 1769 break; 1770 *nmp = nm; 1771 return nm? nm->replyto: nil; 1772 } 1773 1774 Message* 1775 rcmd(Cmd *c, Message *m) 1776 { 1777 char *addr; 1778 char *av[128]; 1779 int i, ai = 1; 1780 String *from, *rpath, *path = nil, *subject = nil; 1781 Message *nm; 1782 1783 if(m == &top){ 1784 Bprint(&out, "!address\n"); 1785 return nil; 1786 } 1787 1788 addr = getreplyto(m, &nm); 1789 if(addr == nil){ 1790 Bprint(&out, "!no reply address\n"); 1791 return nil; 1792 } 1793 if(nm == &top){ 1794 print("!noone to reply to\n"); 1795 return nil; 1796 } 1797 1798 for(nm = m; nm != ⊤ nm = nm->parent){ 1799 if(*nm->subject){ 1800 av[ai++] = "-s"; 1801 subject = addrecolon(nm->subject); 1802 av[ai++] = s_to_c(subject); 1803 break; 1804 } 1805 } 1806 1807 av[ai++] = "-R"; 1808 rpath = rooted(s_clone(m->path)); 1809 av[ai++] = s_to_c(rpath); 1810 1811 if(strchr(c->av[0], 'f') != nil){ 1812 fcmd(c, m); 1813 av[ai++] = "-F"; 1814 } 1815 1816 if(strchr(c->av[0], 'R') != nil){ 1817 av[ai++] = "-t"; 1818 av[ai++] = "message/rfc822"; 1819 av[ai++] = "-A"; 1820 path = rooted(extendpath(m->path, "raw")); 1821 av[ai++] = s_to_c(path); 1822 } 1823 1824 for(i = 1; i < c->an && ai < nelem(av)-1; i++) 1825 av[ai++] = c->av[i]; 1826 from = s_copy(addr); 1827 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); 1828 av[ai] = 0; 1829 if(tomailer(av) < 0) 1830 m = nil; 1831 s_free(path); 1832 s_free(rpath); 1833 s_free(subject); 1834 s_free(from); 1835 return m; 1836 } 1837 1838 Message* 1839 mcmd(Cmd *c, Message *m) 1840 { 1841 char **av; 1842 int i, ai; 1843 String *path; 1844 1845 if(m == &top){ 1846 Bprint(&out, "!address\n"); 1847 return nil; 1848 } 1849 1850 if(c->an < 2){ 1851 fprint(2, "!usage: M list-of addresses\n"); 1852 return nil; 1853 } 1854 1855 ai = 1; 1856 av = malloc(sizeof(char*)*(c->an + 8)); 1857 1858 av[ai++] = "-t"; 1859 if(m->parent == &top) 1860 av[ai++] = "message/rfc822"; 1861 else 1862 av[ai++] = "mime"; 1863 1864 av[ai++] = "-A"; 1865 path = rooted(extendpath(m->path, "raw")); 1866 av[ai++] = s_to_c(path); 1867 1868 if(strchr(c->av[0], 'M') == nil) 1869 av[ai++] = "-n"; 1870 1871 for(i = 1; i < c->an; i++) 1872 av[ai++] = c->av[i]; 1873 av[ai] = 0; 1874 1875 if(tomailer(av) < 0) 1876 m = nil; 1877 if(path != nil) 1878 s_free(path); 1879 free(av); 1880 return m; 1881 } 1882 1883 Message* 1884 acmd(Cmd *c, Message *m) 1885 { 1886 char *av[128]; 1887 int i, ai = 1; 1888 String *from, *rpath, *path = nil, *subject = nil; 1889 String *to, *cc; 1890 1891 if(m == &top){ 1892 Bprint(&out, "!address\n"); 1893 return nil; 1894 } 1895 1896 if(*m->subject){ 1897 av[ai++] = "-s"; 1898 subject = addrecolon(m->subject); 1899 av[ai++] = s_to_c(subject); 1900 } 1901 1902 av[ai++] = "-R"; 1903 rpath = rooted(s_clone(m->path)); 1904 av[ai++] = s_to_c(rpath); 1905 1906 if(strchr(c->av[0], 'A') != nil){ 1907 av[ai++] = "-t"; 1908 av[ai++] = "message/rfc822"; 1909 av[ai++] = "-A"; 1910 path = rooted(extendpath(m->path, "raw")); 1911 av[ai++] = s_to_c(path); 1912 } 1913 1914 for(i = 1; i < c->an && ai < nelem(av)-1; i++) 1915 av[ai++] = c->av[i]; 1916 from = s_copy(m->from); 1917 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); 1918 to = s_copy(m->to); 1919 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai); 1920 cc = s_copy(m->cc); 1921 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai); 1922 av[ai] = 0; 1923 if(tomailer(av) < 0) 1924 m = nil; 1925 s_free(path); 1926 s_free(rpath); 1927 s_free(subject); 1928 s_free(from); 1929 s_free(to); 1930 s_free(cc); 1931 return m; 1932 } 1933 1934 String * 1935 relpath(char *path, String *to) 1936 { 1937 if (*path=='/' || strncmp(path, "./", 2) == 0 1938 || strncmp(path, "../", 3) == 0) { 1939 to = s_append(to, path); 1940 } else if(mbpath) { 1941 to = s_append(to, s_to_c(mbpath)); 1942 to->ptr = strrchr(to->base, '/')+1; 1943 s_append(to, path); 1944 } 1945 return to; 1946 } 1947 1948 int 1949 appendtofile(Message *m, char *part, char *base, int mbox) 1950 { 1951 String *file, *h; 1952 int in, out, rv; 1953 1954 file = extendpath(m->path, part); 1955 in = open(s_to_c(file), OREAD); 1956 if(in < 0){ 1957 fprint(2, "!message disappeared\n"); 1958 return -1; 1959 } 1960 1961 s_reset(file); 1962 1963 relpath(base, file); 1964 if(sysisdir(s_to_c(file))){ 1965 s_append(file, "/"); 1966 if(m->filename && strchr(m->filename, '/') == nil) 1967 s_append(file, m->filename); 1968 else { 1969 s_append(file, "att.XXXXXXXXXXX"); 1970 mktemp(s_to_c(file)); 1971 } 1972 } 1973 if(mbox) 1974 out = open(s_to_c(file), OWRITE); 1975 else 1976 out = open(s_to_c(file), OWRITE|OTRUNC); 1977 if(out < 0){ 1978 out = create(s_to_c(file), OWRITE, 0666); 1979 if(out < 0){ 1980 fprint(2, "!can't open %s: %r\n", s_to_c(file)); 1981 close(in); 1982 s_free(file); 1983 return -1; 1984 } 1985 } 1986 if(mbox) 1987 seek(out, 0, 2); 1988 1989 // put on a 'From ' line 1990 if(mbox){ 1991 while(m->parent != &top) 1992 m = m->parent; 1993 h = file2string(m->path, "unixheader"); 1994 fprint(out, "%s", s_to_c(h)); 1995 s_free(h); 1996 } 1997 1998 // copy the message escaping what we have to ad adding newlines if we have to 1999 if(mbox) 2000 rv = appendfiletombox(in, out); 2001 else 2002 rv = appendfiletofile(in, out); 2003 2004 close(in); 2005 close(out); 2006 2007 if(rv >= 0) 2008 print("!saved in %s\n", s_to_c(file)); 2009 s_free(file); 2010 return rv; 2011 } 2012 2013 Message* 2014 scmd(Cmd *c, Message *m) 2015 { 2016 char *file; 2017 2018 if(m == &top){ 2019 Bprint(&out, "!address\n"); 2020 return nil; 2021 } 2022 2023 switch(c->an){ 2024 case 1: 2025 file = "stored"; 2026 break; 2027 case 2: 2028 file = c->av[1]; 2029 break; 2030 default: 2031 fprint(2, "!usage: s filename\n"); 2032 return nil; 2033 } 2034 2035 if(appendtofile(m, "raw", file, 1) < 0) 2036 return nil; 2037 2038 m->stored = 1; 2039 return m; 2040 } 2041 2042 Message* 2043 wcmd(Cmd *c, Message *m) 2044 { 2045 char *file; 2046 2047 if(m == &top){ 2048 Bprint(&out, "!address\n"); 2049 return nil; 2050 } 2051 2052 switch(c->an){ 2053 case 2: 2054 file = c->av[1]; 2055 break; 2056 case 1: 2057 if(*m->filename == 0){ 2058 fprint(2, "!usage: w filename\n"); 2059 return nil; 2060 } 2061 file = strrchr(m->filename, '/'); 2062 if(file != nil) 2063 file++; 2064 else 2065 file = m->filename; 2066 break; 2067 default: 2068 fprint(2, "!usage: w filename\n"); 2069 return nil; 2070 } 2071 2072 if(appendtofile(m, "body", file, 0) < 0) 2073 return nil; 2074 m->stored = 1; 2075 return m; 2076 } 2077 2078 char *specialfile[] = 2079 { 2080 "pipeto", 2081 "pipefrom", 2082 "L.mbox", 2083 "forward", 2084 "names" 2085 }; 2086 2087 // return 1 if this is a special file 2088 static int 2089 special(String *s) 2090 { 2091 char *p; 2092 int i; 2093 2094 p = strrchr(s_to_c(s), '/'); 2095 if(p == nil) 2096 p = s_to_c(s); 2097 else 2098 p++; 2099 for(i = 0; i < nelem(specialfile); i++) 2100 if(strcmp(p, specialfile[i]) == 0) 2101 return 1; 2102 return 0; 2103 } 2104 2105 // open the folder using the recipients account name 2106 static String* 2107 foldername(char *rcvr) 2108 { 2109 char *p; 2110 int c; 2111 String *file; 2112 Dir *d; 2113 int scarey; 2114 2115 file = s_new(); 2116 mboxpath("f", user, file, 0); 2117 d = dirstat(s_to_c(file)); 2118 2119 // if $mail/f exists, store there, otherwise in $mail 2120 s_restart(file); 2121 if(d && d->qid.type == QTDIR){ 2122 scarey = 0; 2123 s_append(file, "f/"); 2124 } else { 2125 scarey = 1; 2126 } 2127 free(d); 2128 2129 p = strrchr(rcvr, '!'); 2130 if(p != nil) 2131 rcvr = p+1; 2132 2133 while(*rcvr && *rcvr != '@'){ 2134 c = *rcvr++; 2135 if(c == '/') 2136 c = '_'; 2137 s_putc(file, c); 2138 } 2139 s_terminate(file); 2140 2141 if(scarey && special(file)){ 2142 fprint(2, "!won't overwrite %s\n", s_to_c(file)); 2143 s_free(file); 2144 return nil; 2145 } 2146 2147 return file; 2148 } 2149 2150 Message* 2151 fcmd(Cmd *c, Message *m) 2152 { 2153 String *folder; 2154 2155 if(c->an > 1){ 2156 fprint(2, "!usage: f takes no arguments\n"); 2157 return nil; 2158 } 2159 2160 if(m == &top){ 2161 Bprint(&out, "!address\n"); 2162 return nil; 2163 } 2164 2165 folder = foldername(m->from); 2166 if(folder == nil) 2167 return nil; 2168 2169 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){ 2170 s_free(folder); 2171 return nil; 2172 } 2173 s_free(folder); 2174 2175 m->stored = 1; 2176 return m; 2177 } 2178 2179 void 2180 system(char *cmd, char **av, int in) 2181 { 2182 int pid; 2183 2184 switch(pid=fork()){ 2185 case -1: 2186 return; 2187 case 0: 2188 if(in >= 0){ 2189 close(0); 2190 dup(in, 0); 2191 close(in); 2192 } 2193 if(wd[0] != 0) 2194 chdir(wd); 2195 exec(cmd, av); 2196 fprint(2, "!couldn't exec %s\n", cmd); 2197 exits(0); 2198 default: 2199 if(in >= 0) 2200 close(in); 2201 while(waitpid() < 0){ 2202 if(!interrupted) 2203 break; 2204 postnote(PNPROC, pid, "die"); 2205 continue; 2206 } 2207 break; 2208 } 2209 } 2210 2211 Message* 2212 bangcmd(Cmd *c, Message *m) 2213 { 2214 char cmd[4*1024]; 2215 char *p, *e; 2216 char *av[4]; 2217 int i; 2218 2219 cmd[0] = 0; 2220 p = cmd; 2221 e = cmd+sizeof(cmd); 2222 for(i = 1; i < c->an; i++) 2223 p = seprint(p, e, "%s ", c->av[i]); 2224 av[0] = "rc"; 2225 av[1] = "-c"; 2226 av[2] = cmd; 2227 av[3] = 0; 2228 system("/bin/rc", av, -1); 2229 Bprint(&out, "!\n"); 2230 return m; 2231 } 2232 2233 Message* 2234 xpipecmd(Cmd *c, Message *m, char *part) 2235 { 2236 char cmd[128]; 2237 char *p, *e; 2238 char *av[4]; 2239 String *path; 2240 int i, fd; 2241 2242 if(c->an < 2){ 2243 Bprint(&out, "!usage: | cmd\n"); 2244 return nil; 2245 } 2246 2247 if(m == &top){ 2248 Bprint(&out, "!address\n"); 2249 return nil; 2250 } 2251 2252 path = extendpath(m->path, part); 2253 fd = open(s_to_c(path), OREAD); 2254 s_free(path); 2255 if(fd < 0){ // compatibility with older upas/fs 2256 path = extendpath(m->path, "raw"); 2257 fd = open(s_to_c(path), OREAD); 2258 s_free(path); 2259 } 2260 if(fd < 0){ 2261 fprint(2, "!message disappeared\n"); 2262 return nil; 2263 } 2264 2265 p = cmd; 2266 e = cmd+sizeof(cmd); 2267 cmd[0] = 0; 2268 for(i = 1; i < c->an; i++) 2269 p = seprint(p, e, "%s ", c->av[i]); 2270 av[0] = "rc"; 2271 av[1] = "-c"; 2272 av[2] = cmd; 2273 av[3] = 0; 2274 system("/bin/rc", av, fd); /* system closes fd */ 2275 Bprint(&out, "!\n"); 2276 return m; 2277 } 2278 2279 Message* 2280 pipecmd(Cmd *c, Message *m) 2281 { 2282 return xpipecmd(c, m, "body"); 2283 } 2284 2285 Message* 2286 rpipecmd(Cmd *c, Message *m) 2287 { 2288 return xpipecmd(c, m, "rawunix"); 2289 } 2290 2291 void 2292 closemb(void) 2293 { 2294 int fd; 2295 2296 fd = open("/mail/fs/ctl", ORDWR); 2297 if(fd < 0) 2298 sysfatal("can't open /mail/fs/ctl: %r"); 2299 2300 // close current mailbox 2301 if(*mbname && strcmp(mbname, "mbox") != 0) 2302 fprint(fd, "close %s", mbname); 2303 2304 close(fd); 2305 } 2306 2307 int 2308 switchmb(char *file, char *singleton) 2309 { 2310 char *p; 2311 int n, fd; 2312 String *path; 2313 char buf[256]; 2314 2315 // if the user didn't say anything and there 2316 // is an mbox mounted already, use that one 2317 // so that the upas/fs -fdefault default is honored. 2318 if(file 2319 || (singleton && access(singleton, 0)<0) 2320 || (!singleton && access("/mail/fs/mbox", 0)<0)){ 2321 if(file == nil) 2322 file = "mbox"; 2323 2324 // close current mailbox 2325 closemb(); 2326 didopen = 1; 2327 2328 fd = open("/mail/fs/ctl", ORDWR); 2329 if(fd < 0) 2330 sysfatal("can't open /mail/fs/ctl: %r"); 2331 2332 path = s_new(); 2333 2334 // get an absolute path to the mail box 2335 if(strncmp(file, "./", 2) == 0){ 2336 // resolve path here since upas/fs doesn't know 2337 // our working directory 2338 if(getwd(buf, sizeof(buf)-strlen(file)) == nil){ 2339 fprint(2, "!can't get working directory: %s\n", buf); 2340 return -1; 2341 } 2342 s_append(path, buf); 2343 s_append(path, file+1); 2344 } else { 2345 mboxpath(file, user, path, 0); 2346 } 2347 2348 // make up a handle to use when talking to fs 2349 p = strrchr(file, '/'); 2350 if(p == nil){ 2351 // if its in the mailbox directory, just use the name 2352 strncpy(mbname, file, sizeof(mbname)); 2353 mbname[sizeof(mbname)-1] = 0; 2354 } else { 2355 // make up a mailbox name 2356 p = strrchr(s_to_c(path), '/'); 2357 p++; 2358 if(*p == 0){ 2359 fprint(2, "!bad mbox name"); 2360 return -1; 2361 } 2362 strncpy(mbname, p, sizeof(mbname)); 2363 mbname[sizeof(mbname)-1] = 0; 2364 n = strlen(mbname); 2365 if(n > Elemlen-12) 2366 n = Elemlen-12; 2367 sprint(mbname+n, "%ld", time(0)); 2368 } 2369 2370 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){ 2371 fprint(2, "!can't 'open %s %s': %r\n", file, mbname); 2372 s_free(path); 2373 return -1; 2374 } 2375 close(fd); 2376 }else 2377 if (singleton && access(singleton, 0)==0 2378 && strncmp(singleton, "/mail/fs/", 9) == 0){ 2379 if ((p = strchr(singleton +10, '/')) == nil){ 2380 fprint(2, "!bad mbox name"); 2381 return -1; 2382 } 2383 n = p-(singleton+9); 2384 strncpy(mbname, singleton+9, n); 2385 mbname[n+1] = 0; 2386 path = s_reset(nil); 2387 mboxpath(mbname, user, path, 0); 2388 }else{ 2389 path = s_reset(nil); 2390 mboxpath("mbox", user, path, 0); 2391 strcpy(mbname, "mbox"); 2392 } 2393 2394 sprint(root, "/mail/fs/%s", mbname); 2395 if(getwd(wd, sizeof(wd)) == 0) 2396 wd[0] = 0; 2397 if(singleton == nil && chdir(root) >= 0) 2398 strcpy(root, "."); 2399 rootlen = strlen(root); 2400 2401 if(mbpath != nil) 2402 s_free(mbpath); 2403 mbpath = path; 2404 return 0; 2405 } 2406 2407 // like tokenize but for into lines 2408 int 2409 lineize(char *s, char **f, int n) 2410 { 2411 int i; 2412 2413 for(i = 0; *s && i < n; i++){ 2414 f[i] = s; 2415 s = strchr(s, '\n'); 2416 if(s == nil) 2417 break; 2418 *s++ = 0; 2419 } 2420 return i; 2421 } 2422 2423 2424 2425 String* 2426 rooted(String *s) 2427 { 2428 static char buf[256]; 2429 2430 if(strcmp(root, ".") != 0) 2431 return s; 2432 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s)); 2433 s_free(s); 2434 return s_copy(buf); 2435 } 2436 2437 int 2438 plumb(Message *m, Ctype *cp) 2439 { 2440 String *s; 2441 Plumbmsg *pm; 2442 static int fd = -2; 2443 2444 if(cp->plumbdest == nil) 2445 return -1; 2446 2447 if(fd < -1) 2448 fd = plumbopen("send", OWRITE); 2449 if(fd < 0) 2450 return -1; 2451 2452 pm = mallocz(sizeof(Plumbmsg), 1); 2453 pm->src = strdup("mail"); 2454 if(*cp->plumbdest) 2455 pm->dst = strdup(cp->plumbdest); 2456 pm->wdir = nil; 2457 pm->type = strdup("text"); 2458 pm->ndata = -1; 2459 s = rooted(extendpath(m->path, "body")); 2460 if(cp->ext != nil){ 2461 s_append(s, "."); 2462 s_append(s, cp->ext); 2463 } 2464 pm->data = strdup(s_to_c(s)); 2465 s_free(s); 2466 plumbsend(fd, pm); 2467 plumbfree(pm); 2468 return 0; 2469 } 2470 2471 void 2472 regerror(char*) 2473 { 2474 } 2475 2476 String* 2477 addrecolon(char *s) 2478 { 2479 String *str; 2480 2481 if(cistrncmp(s, "re:", 3) != 0){ 2482 str = s_copy("Re: "); 2483 s_append(str, s); 2484 } else 2485 str = s_copy(s); 2486 return str; 2487 } 2488 2489 void 2490 exitfs(char *rv) 2491 { 2492 if(startedfs) 2493 unmount(nil, "/mail/fs"); 2494 chdir("/sys/src/cmd/upas/ned"); /* for profiling? */ 2495 exits(rv); 2496 } 2497