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