1implement mailpop3; 2 3include "sys.m"; 4include "draw.m"; 5include "bufio.m"; 6include "daytime.m"; 7include "sh.m"; 8include "pop3.m"; 9 10mailpop3 : module { 11 init : fn(ctxt : ref Draw->Context, argl : list of string); 12}; 13 14sys : Sys; 15bufio : Bufio; 16daytime : Daytime; 17pop3 : Pop3; 18 19OREAD, OWRITE, ORDWR, NEWFD, FORKENV, FORKFD, NEWPGRP, UTFmax, EXCEPTION, ONCE : import Sys; 20FD, Dir, Exception : import sys; 21fprint, sprint, sleep, create, open, read, write, remove, stat, fstat, fwstat, fildes, pctl, pipe, dup, byte2char : import sys; 22Context : import Draw; 23EOF : import Bufio; 24Iobuf : import bufio; 25time : import daytime; 26 27DIRLEN : con 116; 28PNPROC, PNGROUP : con iota; 29False : con 0; 30True : con 1; 31EVENTSIZE : con 256; 32Runeself : con 16r80; 33OCEXEC : con 0; 34CHEXCL : con 0; # 16r20000000; 35CHAPPEND : con 0; # 16r40000000; 36 37Win : adt { 38 winid : int; 39 addr : ref FD; 40 body : ref Iobuf; 41 ctl : ref FD; 42 data : ref FD; 43 event : ref FD; 44 buf : array of byte; 45 bufp : int; 46 nbuf : int; 47 48 wnew : fn() : ref Win; 49 wwritebody : fn(w : self ref Win, s : string); 50 wread : fn(w : self ref Win, m : int, n : int) : string; 51 wclean : fn(w : self ref Win); 52 wname : fn(w : self ref Win, s : string); 53 wdormant : fn(w : self ref Win); 54 wevent : fn(w : self ref Win, e : ref Event); 55 wshow : fn(w : self ref Win); 56 wtagwrite : fn(w : self ref Win, s : string); 57 wwriteevent : fn(w : self ref Win, e : ref Event); 58 wslave : fn(w : self ref Win, c : chan of Event); 59 wreplace : fn(w : self ref Win, s : string, t : string); 60 wselect : fn(w : self ref Win, s : string); 61 wsetdump : fn(w : self ref Win, s : string, t : string); 62 wdel : fn(w : self ref Win, n : int) : int; 63 wreadall : fn(w : self ref Win) : string; 64 65 ctlwrite : fn(w : self ref Win, s : string); 66 getec : fn(w : self ref Win) : int; 67 geten : fn(w : self ref Win) : int; 68 geter : fn(w : self ref Win, s : array of byte) : (int, int); 69 openfile : fn(w : self ref Win, s : string) : ref FD; 70 openbody : fn(w : self ref Win, n : int); 71}; 72 73Mesg : adt { 74 w : ref Win; 75 id : int; 76 popno : int; 77 hdr : string; 78 realhdr : string; 79 replyto : string; 80 text : string; 81 subj : string; 82 next : cyclic ref Mesg; 83 lline1 : int; 84 box : cyclic ref Box; 85 isopen : int; 86 posted : int; 87 deleted : int; 88 89 read : fn(b : ref Box) : ref Mesg; 90 open : fn(m : self ref Mesg); 91 slave : fn(m : self ref Mesg); 92 free : fn(m : self ref Mesg); 93 save : fn(m : self ref Mesg, s : string); 94 mkreply : fn(m : self ref Mesg); 95 mkmail : fn(b : ref Box, s : string); 96 putpost : fn(m : self ref Mesg, e : ref Event); 97 98 command : fn(m : self ref Mesg, s : string) : int; 99 send : fn(m : self ref Mesg); 100}; 101 102Box : adt { 103 w : ref Win; 104 nm : int; 105 readonly : int; 106 m : cyclic ref Mesg; 107# io : ref Iobuf; 108 clean : int; 109 leng : int; 110 cdel : chan of ref Mesg; 111 cevent : chan of Event; 112 cmore : chan of int; 113 lst : list of int; 114 s : string; 115 116 line : string; 117 popno : int; 118 peekline : string; 119 120 read : fn(n : int) : ref Box; 121 readmore : fn(b : self ref Box); 122 readline : fn(b : self ref Box) : string; 123 unreadline : fn(b : self ref Box); 124 slave : fn(b : self ref Box); 125 mopen : fn(b : self ref Box, n : int); 126 rewrite : fn(b : self ref Box); 127 mdel : fn(b : self ref Box, m : ref Mesg); 128 event : fn(b : self ref Box, e : ref Event); 129 130 command : fn(b : self ref Box, s : string) : int; 131}; 132 133Event : adt { 134 c1 : int; 135 c2 : int; 136 q0 : int; 137 q1 : int; 138 flag : int; 139 nb : int; 140 nr : int; 141 b : array of byte; 142 r : array of int; 143}; 144 145Lock : adt { 146 cnt : int; 147 chann : chan of int; 148 149 init : fn() : ref Lock; 150 lock : fn(l : self ref Lock); 151 unlock : fn(l : self ref Lock); 152}; 153 154Ref : adt { 155 l : ref Lock; 156 cnt : int; 157 158 init : fn() : ref Ref; 159 inc : fn(r : self ref Ref) : int; 160}; 161 162mbox : ref Box; 163user : string; 164date : string; 165mailctxt : ref Context; 166stdout, stderr : ref FD; 167 168killing : int = 0; 169 170init(ctxt : ref Context, argl : list of string) 171{ 172 mailctxt = ctxt; 173 sys = load Sys Sys->PATH; 174 bufio = load Bufio Bufio->PATH; 175 daytime = load Daytime Daytime->PATH; 176 pop3 = load Pop3 Pop3->PATH; 177 stdout = fildes(1); 178 stderr = fildes(2); 179 main(); 180} 181 182dlock : ref Lock; 183dfd : ref Sys->FD; 184 185debug(s : string) 186{ 187 if (dfd == nil) { 188 dfd = sys->create("/usr/jrf/acme/debugmail", Sys->OWRITE, 8r600); 189 dlock = Lock.init(); 190 } 191 if (dfd == nil) 192 return; 193 dlock.lock(); 194 sys->fprint(dfd, "%s", s); 195 dlock.unlock(); 196} 197 198postnote(t : int, pid : int, note : string) : int 199{ 200 fd := open("#p/" + string pid + "/ctl", OWRITE); 201 if (fd == nil) 202 return -1; 203 if (t == PNGROUP) 204 note += "grp"; 205 fprint(fd, "%s", note); 206 fd = nil; 207 return 0; 208} 209 210exec(cmd : string, argl : list of string) 211{ 212 file := cmd; 213 if(len file<4 || file[len file-4:]!=".dis") 214 file += ".dis"; 215 216 c := load Command file; 217 if(c == nil) { 218 err := sprint("%r"); 219 if(file[0]!='/' && file[0:2]!="./"){ 220 c = load Command "/dis/"+file; 221 if(c == nil) 222 err = sprint("%r"); 223 } 224 if(c == nil){ 225 fprint(stderr, "%s: %s\n", cmd, err); 226 return; 227 } 228 } 229 c->init(mailctxt, argl); 230} 231 232swrite(fd : ref FD, s : string) : int 233{ 234 ab := array of byte s; 235 m := len ab; 236 p := write(fd, ab, m); 237 if (p == m) 238 return len s; 239 if (p <= 0) 240 return p; 241 return 0; 242} 243 244strchr(s : string, c : int) : int 245{ 246 for (i := 0; i < len s; i++) 247 if (s[i] == c) 248 return i; 249 return -1; 250} 251 252strrchr(s : string, c : int) : int 253{ 254 for (i := len s - 1; i >= 0; i--) 255 if (s[i] == c) 256 return i; 257 return -1; 258} 259 260strtoi(s : string) : (int, int) 261{ 262 m := 0; 263 neg := 0; 264 t := 0; 265 ls := len s; 266 while (t < ls && (s[t] == ' ' || s[t] == '\t')) 267 t++; 268 if (t < ls && s[t] == '+') 269 t++; 270 else if (t < ls && s[t] == '-') { 271 neg = 1; 272 t++; 273 } 274 while (t < ls && (s[t] >= '0' && s[t] <= '9')) { 275 m = 10*m + s[t]-'0'; 276 t++; 277 } 278 if (neg) 279 m = -m; 280 return (m, t); 281} 282 283access(s : string) : int 284{ 285 fd := open(s, 0); 286 if (fd == nil) 287 return -1; 288 fd = nil; 289 return 0; 290} 291 292newevent() : ref Event 293{ 294 e := ref Event; 295 e.b = array[EVENTSIZE*UTFmax+1] of byte; 296 e.r = array[EVENTSIZE+1] of int; 297 return e; 298} 299 300newmesg() : ref Mesg 301{ 302 m := ref Mesg; 303 m.id = m.lline1 = m.isopen = m.posted = m.deleted = 0; 304 return m; 305} 306 307lc, uc : chan of ref Lock; 308 309initlock() 310{ 311 lc = chan of ref Lock; 312 uc = chan of ref Lock; 313 spawn lockmgr(); 314} 315 316lockmgr() 317{ 318 l : ref Lock; 319 320 for (;;) { 321 alt { 322 l = <- lc => 323 if (l.cnt++ == 0) 324 l.chann <-= 1; 325 l = <- uc => 326 if (--l.cnt > 0) 327 l.chann <-= 1; 328 } 329 } 330} 331 332Lock.init() : ref Lock 333{ 334 return ref Lock(0, chan of int); 335} 336 337Lock.lock(l : self ref Lock) 338{ 339 lc <-= l; 340 <- l.chann; 341} 342 343Lock.unlock(l : self ref Lock) 344{ 345 uc <-= l; 346} 347 348Ref.init() : ref Ref 349{ 350 r := ref Ref; 351 r.l = Lock.init(); 352 r.cnt = 0; 353 return r; 354} 355 356Ref.inc(r : self ref Ref) : int 357{ 358 r.l.lock(); 359 i := r.cnt; 360 r.cnt++; 361 r.l.unlock(); 362 return i; 363} 364 365error(s : string) 366{ 367 if(s != nil) 368 fprint(stderr, "mail: %s\n", s); 369 postnote(PNGROUP, pctl(0, nil), "kill"); 370 killing = 1; 371 exit; 372} 373 374tryopen(s : string, mode : int) : ref FD 375{ 376 fd : ref FD; 377 try : int; 378 379 for(try=0; try<3; try++){ 380 fd = open(s, mode); 381 if(fd != nil) 382 return fd; 383 sleep(1000); 384 } 385 return nil; 386} 387 388run(argv : list of string, c : chan of int, p0 : ref FD) 389{ 390 # pctl(FORKFD|NEWPGRP, nil); # had RFMEM 391 pctl(FORKENV|NEWFD|NEWPGRP, 0::1::2::p0.fd::nil); 392 c <-= pctl(0, nil); 393 dup(p0.fd, 0); 394 p0 = nil; 395 exec(hd argv, argv); 396 exit; 397} 398 399getuser() : string 400{ 401 fd := open("/dev/user", OREAD); 402 if(fd == nil) 403 return ""; 404 buf := array[128] of byte; 405 n := read(fd, buf, len buf); 406 if(n < 0) 407 return ""; 408 return string buf[0:n]; 409} 410 411pop3conn : int = 0; 412pop3bad : int = 0; 413pop3lock : ref Lock; 414 415pop3open() 416{ 417 pop3lock.lock(); 418 if (!pop3conn) { 419 (ok, s) := pop3->open(user, "********", nil); # password now got from user in Mailpop3.b 420 if (ok < 0) { 421 if (!pop3bad) { 422 fprint(stderr, "mail: could not connect to POP3 mail server : %s\n", s); 423 pop3bad = 1; 424 } 425 return; 426 } 427 } 428 pop3conn = 1; 429 pop3bad = 0; 430} 431 432pop3close() 433{ 434 if (pop3conn) { 435 (ok, s) := pop3->close(); 436 if (ok < 0) { 437 fprint(stderr, "mail: could not close POP3 connection : %s\n", s); 438 pop3lock.unlock(); 439 return; 440 } 441 } 442 pop3conn = 0; 443 pop3lock.unlock(); 444} 445 446pop3stat(b : ref Box) : int 447{ 448 (ok, s, nm, nil) := pop3->stat(); 449 if (ok < 0 && pop3conn) { 450 fprint(stderr, "mail: could not stat POP3 server : %s\n", s); 451 return b.leng; 452 } 453 return nm; 454} 455 456pop3list() : list of int 457{ 458 (ok, s, l) := pop3->msgnolist(); 459 if (ok < 0 && pop3conn) { 460 fprint(stderr, "mail: could not get list from POP3 server : %s\n", s); 461 return nil; 462 } 463 return l; 464} 465 466pop3mesg(mno : int) : string 467{ 468 (ok, s, msg) := pop3->get(mno); 469 if (ok < 0 && pop3conn) { 470 fprint(stderr, "mail: could not retrieve a message from server : %s\n", s); 471 return "Acme Mail : FAILED TO RETRIEVE MESSAGE\n"; 472 } 473 return msg; 474} 475 476pop3del(mno : int) : int 477{ 478 (ok, s) := pop3->delete(mno); 479 if (ok < 0) 480 fprint(stderr, "mail: could not delete message : %s\n", s); 481 return ok; 482} 483 484pop3init(b : ref Box) 485{ 486 b.leng = pop3stat(b); 487 b.lst = pop3list(); 488 b.s = nil; 489 b.popno = 0; 490} 491 492pop3more(b : ref Box) 493{ 494 nl : list of int; 495 496 leng := b.leng; 497 b.leng = pop3stat(b); 498 b.lst = pop3list(); 499 b.s = nil; 500 b.popno = 0; 501 if (len b.lst != b.leng || b.leng <= leng) 502 error("bad lengths in pop3more()"); 503 # is this ok ? 504 nl = nil; 505 for (i := 0; i < leng; i++) { 506 nl = hd b.lst :: nl; 507 b.lst = tl b.lst; 508 } 509 # now update pop nos. 510 for (m := b.m; m != nil; m = m.next) { 511 # opopno := m.popno; 512 if (nl == nil) 513 error("message list too big"); 514 m.popno = hd nl; 515 nl = tl nl; 516 # debug(sys->sprint("%d : popno from %d to %d\n", m.id, opopno, m.popno)); 517 } 518 if (nl != nil) 519 error("message list too small"); 520} 521 522pop3next(b : ref Box) : string 523{ 524 mno : int = 0; 525 r : string; 526 527 if (b.s == nil) { 528 if (b.lst == nil) 529 return nil; # end of box 530 first := b.popno == 0; 531 mno = hd b.lst; 532 b.lst = tl b.lst; 533 b.s = pop3mesg(mno); 534 b.popno = mno; 535 if (!first) 536 return nil; # end of message 537 } 538 t := strchr(b.s, '\n'); 539 if (t >= 0) { 540 r = b.s[0:t+1]; 541 b.s = b.s[t+1:]; 542 } 543 else { 544 r = b.s; 545 b.s = nil; 546 } 547 return r; 548} 549 550main() 551{ 552 readonly : int; 553 554 initlock(); 555 initreply(); 556 date = time(); 557 if(date==nil) 558 error("can't get current time"); 559 user = getuser(); 560 if(user == nil) 561 user = "Wile.E.Coyote"; 562 readonly = False; 563 pop3lock = Lock.init(); 564 mbox = mbox.read(readonly); 565 spawn timeslave(mbox, mbox.cmore); 566 mbox.slave(); 567 error(nil); 568} 569 570timeslave(b : ref Box, c : chan of int) 571{ 572 for(;;){ 573 sleep(30*1000); 574 pop3open(); 575 leng := pop3stat(b); 576 pop3close(); 577 if (leng > b.leng) 578 c <-= 0; 579 } 580} 581 582Win.wnew() : ref Win 583{ 584 w := ref Win; 585 buf := array[12] of byte; 586 w.ctl = open("/chan/new/ctl", ORDWR); 587 if(w.ctl==nil || read(w.ctl, buf, 12)!=12) 588 error("can't open window ctl file: %r"); 589 w.ctlwrite("noscroll\n"); 590 w.winid = int string buf; 591 w.event = w.openfile("event"); 592 w.addr = nil; # will be opened when needed 593 w.body = nil; 594 w.data = nil; 595 w.bufp = w.nbuf = 0; 596 w.buf = array[512] of byte; 597 return w; 598} 599 600Win.openfile(w : self ref Win, f : string) : ref FD 601{ 602 buf := sprint("/chan/%d/%s", w.winid, f); 603 fd := open(buf, ORDWR|OCEXEC); 604 if(fd == nil) 605 error(sprint("can't open window %s file: %r", f)); 606 return fd; 607} 608 609Win.openbody(w : self ref Win, mode : int) 610{ 611 buf := sprint("/chan/%d/body", w.winid); 612 w.body = bufio->open(buf, mode|OCEXEC); 613 if(w.body == nil) 614 error("can't open window body file: %r"); 615} 616 617Win.wwritebody(w : self ref Win, s : string) 618{ 619 n := len s; 620 if(w.body == nil) 621 w.openbody(OWRITE); 622 if(w.body.puts(s) != n) 623 error("write error to window: %r"); 624} 625 626Win.wreplace(w : self ref Win, addr : string, repl : string) 627{ 628 if(w.addr == nil) 629 w.addr = w.openfile("addr"); 630 if(w.data == nil) 631 w.data = w.openfile("data"); 632 if(swrite(w.addr, addr) < 0){ 633 fprint(stderr, "mail: warning: bad address %s:%r\n", addr); 634 return; 635 } 636 if(swrite(w.data, repl) != len repl) 637 error("writing data: %r"); 638} 639 640nrunes(s : array of byte, nb : int) : int 641{ 642 i, n : int; 643 644 n = 0; 645 for(i=0; i<nb; n++) { 646 (r, b, ok) := byte2char(s, i); 647 if (!ok) 648 error("help needed in nrunes()"); 649 i += b; 650 } 651 return n; 652} 653 654Win.wread(w : self ref Win, q0 : int, q1 : int) : string 655{ 656 m, n, nr : int; 657 s, buf : string; 658 b : array of byte; 659 660 b = array[256] of byte; 661 if(w.addr == nil) 662 w.addr = w.openfile("addr"); 663 if(w.data == nil) 664 w.data = w.openfile("data"); 665 s = nil; 666 m = q0; 667 while(m < q1){ 668 buf = sprint("#%d", m); 669 if(swrite(w.addr, buf) != len buf) 670 error("writing addr: %r"); 671 n = read(w.data, b, len b); 672 if(n <= 0) 673 error("reading data: %r"); 674 nr = nrunes(b, n); 675 while(m+nr >q1){ 676 do; while(n>0 && (int b[--n]&16rC0)==16r80); 677 --nr; 678 } 679 if(n == 0) 680 break; 681 s += string b[0:n]; 682 m += nr; 683 } 684 return s; 685} 686 687Win.wshow(w : self ref Win) 688{ 689 w.ctlwrite("show\n"); 690} 691 692Win.wsetdump(w : self ref Win, dir : string, cmd : string) 693{ 694 t : string; 695 696 if(dir != nil){ 697 t = "dumpdir " + dir + "\n"; 698 w.ctlwrite(t); 699 t = nil; 700 } 701 if(cmd != nil){ 702 t = "dump " + cmd + "\n"; 703 w.ctlwrite(t); 704 t = nil; 705 } 706} 707 708Win.wselect(w : self ref Win, addr : string) 709{ 710 if(w.addr == nil) 711 w.addr = w.openfile("addr"); 712 if(swrite(w.addr, addr) < 0) 713 error("writing addr"); 714 w.ctlwrite("dot=addr\n"); 715} 716 717Win.wtagwrite(w : self ref Win, s : string) 718{ 719 fd : ref FD; 720 721 fd = w.openfile("tag"); 722 if(swrite(fd, s) != len s) 723 error("tag write: %r"); 724 fd = nil; 725} 726 727Win.ctlwrite(w : self ref Win, s : string) 728{ 729 if(swrite(w.ctl, s) != len s) 730 error("write error to ctl file: %r"); 731} 732 733Win.wdel(w : self ref Win, sure : int) : int 734{ 735 if (w == nil) 736 return False; 737 if(sure) 738 swrite(w.ctl, "delete\n"); 739 else if(swrite(w.ctl, "del\n") != 4) 740 return False; 741 w.wdormant(); 742 w.ctl = nil; 743 w.event = nil; 744 return True; 745} 746 747Win.wname(w : self ref Win, s : string) 748{ 749 w.ctlwrite("name " + s + "\n"); 750} 751 752Win.wclean(w : self ref Win) 753{ 754 if(w.body != nil) 755 w.body.flush(); 756 w.ctlwrite("clean\n"); 757} 758 759Win.wdormant(w : self ref Win) 760{ 761 w.addr = nil; 762 if(w.body != nil){ 763 w.body.close(); 764 w.body = nil; 765 } 766 w.data = nil; 767} 768 769Win.getec(w : self ref Win) : int 770{ 771 if(w.nbuf == 0){ 772 w.nbuf = read(w.event, w.buf, len w.buf); 773 if(w.nbuf <= 0 && !killing) { 774 error("event read error: %r"); 775 } 776 w.bufp = 0; 777 } 778 w.nbuf--; 779 return int w.buf[w.bufp++]; 780} 781 782Win.geten(w : self ref Win) : int 783{ 784 n, c : int; 785 786 n = 0; 787 while('0'<=(c=w.getec()) && c<='9') 788 n = n*10+(c-'0'); 789 if(c != ' ') 790 error("event number syntax"); 791 return n; 792} 793 794Win.geter(w : self ref Win, buf : array of byte) : (int, int) 795{ 796 r, m, n, ok : int; 797 798 r = w.getec(); 799 buf[0] = byte r; 800 n = 1; 801 if(r >= Runeself) { 802 for (;;) { 803 (r, m, ok) = byte2char(buf[0:n], 0); 804 if (m > 0) 805 return (r, n); 806 buf[n++] = byte w.getec(); 807 } 808 } 809 return (r, n); 810} 811 812Win.wevent(w : self ref Win, e : ref Event) 813{ 814 i, nb : int; 815 816 e.c1 = w.getec(); 817 e.c2 = w.getec(); 818 e.q0 = w.geten(); 819 e.q1 = w.geten(); 820 e.flag = w.geten(); 821 e.nr = w.geten(); 822 if(e.nr > EVENTSIZE) 823 error("event string too long"); 824 e.nb = 0; 825 for(i=0; i<e.nr; i++){ 826 (e.r[i], nb) = w.geter(e.b[e.nb:]); 827 e.nb += nb; 828 } 829 e.r[e.nr] = 0; 830 e.b[e.nb] = byte 0; 831 c := w.getec(); 832 if(c != '\n') 833 error("event syntax 2"); 834} 835 836Win.wslave(w : self ref Win, ce : chan of Event) 837{ 838 e : ref Event; 839 840 e = newevent(); 841 for(;;){ 842 w.wevent(e); 843 ce <-= *e; 844 } 845} 846 847Win.wwriteevent(w : self ref Win, e : ref Event) 848{ 849 fprint(w.event, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1); 850} 851 852Win.wreadall(w : self ref Win) : string 853{ 854 s, t : string; 855 856 if(w.body != nil) 857 w.body.close(); 858 w.openbody(OREAD); 859 s = nil; 860 while ((t = w.body.gets('\n')) != nil) 861 s += t; 862 w.body.close(); 863 w.body = nil; 864 return s; 865} 866 867None,Unknown,Ignore,CC,From,ReplyTo,Sender,Subject,Re,To, Date : con iota; 868NHeaders : con 200; 869 870Hdrs : adt { 871 name : string; 872 typex : int; 873}; 874 875 876hdrs := array[NHeaders+1] of { 877 Hdrs ( "CC:", CC ), 878 Hdrs ( "From:", From ), 879 Hdrs ( "Reply-To:", ReplyTo ), 880 Hdrs ( "Sender:", Sender ), 881 Hdrs ( "Subject:", Subject ), 882 Hdrs ( "Re:", Re ), 883 Hdrs ( "To:", To ), 884 Hdrs ( "Date:", Date), 885 * => Hdrs ( "", 0 ), 886}; 887 888StRnCmP(s : string, t : string, n : int) : int 889{ 890 c, d, i, j : int; 891 892 i = j = 0; 893 if (len s < n || len t < n) 894 return -1; 895 while(n > 0){ 896 c = s[i++]; 897 d = t[j++]; 898 --n; 899 if(c != d){ 900 if('a'<=c && c<='z') 901 c -= 'a'-'A'; 902 if('a'<=d && d<='z') 903 d -= 'a'-'A'; 904 if(c != d) 905 return c-d; 906 } 907 } 908 return 0; 909} 910 911readhdr(b : ref Box) : (string, int) 912{ 913 i, j, n, m, typex : int; 914 s, t : string; 915 916{ 917 s = b.readline(); 918 n = len s; 919 if(n <= 0) { 920 b.unreadline(); 921 raise("e"); 922 } 923 for(i=0; i<n; i++){ 924 j = s[i]; 925 if(i>0 && j == ':') 926 break; 927 if(j<'!' || '~'<j){ 928 b.unreadline(); 929 raise("e"); 930 } 931 } 932 typex = Unknown; 933 for(i=0; hdrs[i].name != nil; i++){ 934 j = len hdrs[i].name; 935 if(StRnCmP(hdrs[i].name, s, j) == 0){ 936 typex = hdrs[i].typex; 937 break; 938 } 939 } 940 # scan for multiple sublines 941 for(;;){ 942 t = b.readline(); 943 m = len t; 944 if(m<=0 || (t[0]!=' ' && t[0]!='\t')){ 945 b.unreadline(); 946 break; 947 } 948 # absorb 949 s += t; 950 } 951 return(s, typex); 952} 953exception{ 954 "*" => 955 return (nil, None); 956} 957} 958 959Mesg.read(b : ref Box) : ref Mesg 960{ 961 m : ref Mesg; 962 s : string; 963 n, typex : int; 964 965 s = b.readline(); 966 n = len s; 967 if(n <= 0) 968 return nil; 969 970{ 971 if(n < 5 || (s[0:5] !="From " && s[0:5] != "From:")) 972 raise("e"); 973 m = newmesg(); 974 m.popno = b.popno; 975 if (m.popno == 0) 976 error("bad pop3 id"); 977 m.realhdr = s; 978 # toss 'From ' 979 s = s[5:]; 980 n -= 5; 981 # toss spaces/tabs 982 while (n > 0 && (s[0] == ' ' || s[0] == '\t')) { 983 s = s[1:]; 984 n--; 985 } 986 m.hdr = s; 987 # convert first blank to tab 988 s0 := strchr(m.hdr, ' '); 989 if(s0 >= 0){ 990 m.hdr[s0] = '\t'; 991 # drop trailing seconds, time zone, and year if match local year 992 t := n-6; 993 if(t <= 0) 994 raise("e"); 995 if(m.hdr[t:n-1] == date[23:]){ 996 m.hdr = m.hdr[0:t] + "\n"; # drop year for sure 997 t = -1; 998 s1 := strchr(m.hdr[s0:], ':'); 999 if(s1 >= 0) 1000 t = strchr(m.hdr[s0+s1+1:], ':'); 1001 if(t >= 0) # drop seconds and time zone 1002 m.hdr = m.hdr[0:s0+s1+t+1] + "\n"; 1003 else{ # drop time zone 1004 t = strchr(m.hdr[s0+s1+1:], ' '); 1005 if(t >= 0) 1006 m.hdr = m.hdr[0:s0+s1+t+1] + "\n"; 1007 } 1008 n = len m.hdr; 1009 } 1010 } 1011 m.lline1 = n; 1012 m.text = nil; 1013 # read header 1014loop: 1015 for(;;){ 1016 (s, typex) = readhdr(b); 1017 case(typex){ 1018 None => 1019 break loop; 1020 ReplyTo => 1021 m.replyto = s[9:]; 1022 break; 1023 From => 1024 if(m.replyto == nil) 1025 m.replyto = s[5:]; 1026 break; 1027 Subject => 1028 m.subj = s[8:]; 1029 break; 1030 Re => 1031 m.subj = s[3:]; 1032 break; 1033 Date => 1034 break; 1035 } 1036 m.realhdr += s; 1037 if(typex != Ignore) 1038 m.hdr += s; 1039 } 1040 # read body 1041 for(;;){ 1042 s = b.readline(); 1043 n = len s; 1044 if(n <= 0) 1045 break; 1046# if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:")){ 1047# b.unreadline(); 1048# break; 1049# } 1050 m.text += s; 1051 } 1052 # remove trailing "morF\n" 1053 l := len m.text; 1054 if(l>6 && m.text[l-6:] == "\nmorF\n") 1055 m.text = m.text[0:l-5]; 1056 m.box = b; 1057 return m; 1058} 1059exception{ 1060 "*" => 1061 error("malformed header " + s); 1062 return nil; 1063} 1064} 1065 1066Mesg.mkmail(b : ref Box, hdr : string) 1067{ 1068 r : ref Mesg; 1069 1070 r = newmesg(); 1071 r.hdr = hdr + "\n"; 1072 r.lline1 = len r.hdr; 1073 r.text = nil; 1074 r.box = b; 1075 r.open(); 1076 r.w.wdormant(); 1077} 1078 1079replyaddr(r : string) : string 1080{ 1081 p, q, rr : int; 1082 1083 rr = 0; 1084 while(r[rr]==' ' || r[rr]=='\t') 1085 rr++; 1086 r = r[rr:]; 1087 p = strchr(r, '<'); 1088 if(p >= 0){ 1089 q = strchr(r[p+1:], '>'); 1090 if(q < 0) 1091 r = r[p+1:]; 1092 else 1093 r = r[p+1:p+q] + "\n"; 1094 return r; 1095 } 1096 p = strchr(r, '('); 1097 if(p >= 0){ 1098 q = strchr(r[p:], ')'); 1099 if(q < 0) 1100 r = r[0:p]; 1101 else 1102 r = r[0:p] + r[p+q+1:]; 1103 } 1104 return r; 1105} 1106 1107Mesg.mkreply(m : self ref Mesg) 1108{ 1109 r : ref Mesg; 1110 1111 r = newmesg(); 1112 if(m.replyto != nil){ 1113 r.hdr = replyaddr(m.replyto); 1114 r.lline1 = len r.hdr; 1115 }else{ 1116 r.hdr = m.hdr[0:m.lline1]; 1117 r.lline1 = m.lline1; # was len m.hdr; 1118 } 1119 if(m.subj != nil){ 1120 if(StRnCmP(m.subj, "re:", 3)==0 || StRnCmP(m.subj, " re:", 4)==0) 1121 r.text = "Subject:" + m.subj + "\n"; 1122 else 1123 r.text = "Subject: Re:" + m.subj + "\n"; 1124 } 1125 else 1126 r.text = nil; 1127 r.box = m.box; 1128 r.open(); 1129 r.w.wselect("$"); 1130 r.w.wdormant(); 1131} 1132 1133Mesg.free(m : self ref Mesg) 1134{ 1135 m.text = nil; 1136 m.hdr = nil; 1137 m.subj = nil; 1138 m.realhdr = nil; 1139 m.replyto = nil; 1140 m = nil; 1141} 1142 1143replyid : ref Ref; 1144 1145initreply() 1146{ 1147 replyid = Ref.init(); 1148} 1149 1150Mesg.open(m : self ref Mesg) 1151{ 1152 buf, s : string; 1153 1154 if(m.isopen) 1155 return; 1156 m.w = Win.wnew(); 1157 if(m.id != 0) 1158 m.w.wwritebody("From "); 1159 m.w.wwritebody(m.hdr); 1160 m.w.wwritebody(m.text); 1161 if(m.id){ 1162 buf = sprint("Mail/box/%d", m.id); 1163 m.w.wtagwrite("Reply Delmesg Save"); 1164 }else{ 1165 buf = sprint("Mail/%s/Reply%d", s, replyid.inc()); 1166 m.w.wtagwrite("Post"); 1167 } 1168 m.w.wname(buf); 1169 m.w.wclean(); 1170 m.w.wselect("0"); 1171 m.isopen = True; 1172 m.posted = False; 1173 spawn m.slave(); 1174} 1175 1176Mesg.putpost(m : self ref Mesg, e : ref Event) 1177{ 1178 if(m.posted || m.id==0) 1179 return; 1180 if(e.q0 >= len m.hdr+5) # include "From " 1181 return; 1182 m.w.wtagwrite(" Post"); 1183 m.posted = True; 1184 return; 1185} 1186 1187Mesg.slave(m : self ref Mesg) 1188{ 1189 e, e2, ea, etoss, eq : ref Event; 1190 s : string; 1191 na : int; 1192 1193 e = newevent(); 1194 e2 = newevent(); 1195 ea = newevent(); 1196 etoss = newevent(); 1197 for(;;){ 1198 m.w.wevent(e); 1199 case(e.c1){ 1200 'E' => # write to body; can't affect us 1201 break; 1202 'F' => # generated by our actions; ignore 1203 break; 1204 'K' or 'M' => # type away; we don't care 1205 case(e.c2){ 1206 'x' or 'X' => # mouse only 1207 eq = e; 1208 if(e.flag & 2){ 1209 m.w.wevent(e2); 1210 eq = e2; 1211 } 1212 if(e.flag & 8){ 1213 m.w.wevent(ea); 1214 m.w.wevent(etoss); 1215 na = ea.nb; 1216 }else 1217 na = 0; 1218 if(eq.q1>eq.q0 && eq.nb==0) 1219 s = m.w.wread(eq.q0, eq.q1); 1220 else 1221 s = string eq.b[0:eq.nb]; 1222 if(na) 1223 s = s + " " + string ea.b[0:ea.nb]; 1224 if(!m.command(s)) # send it back 1225 m.w.wwriteevent(e); 1226 s = nil; 1227 break; 1228 'l' or 'L' => # mouse only 1229 if(e.flag & 2) 1230 m.w.wevent(e2); 1231 # just send it back 1232 m.w.wwriteevent(e); 1233 break; 1234 'I' or 'D' => # modify away; we don't care 1235 m.putpost(e); 1236 break; 1237 'd' or 'i' => 1238 break; 1239 * => 1240 fprint(stdout, "unknown message %c%c\n", e.c1, e.c2); 1241 break; 1242 } 1243 * => 1244 fprint(stdout, "unknown message %c%c\n", e.c1, e.c2); 1245 break; 1246 } 1247 } 1248} 1249 1250Mesg.command(m : self ref Mesg, s : string) : int 1251{ 1252 while(s[0]==' ' || s[0]=='\t' || s[0]=='\n') 1253 s = s[1:]; 1254 if(s == "Post"){ 1255 m.send(); 1256 return True; 1257 } 1258 if(len s >= 4 && s[0:4] == "Save"){ 1259 s = s[4:]; 1260 while(s[0]==' ' || s[0]=='\t' || s[0]=='\n') 1261 s = s[1:]; 1262 if(s == nil) 1263 m.save("stored"); 1264 else{ 1265 ss := 0; 1266 while(ss < len s && s[ss]!=' ' && s[ss]!='\t' && s[ss]!='\n') 1267 ss++; 1268 m.save(s[0:ss]); 1269 } 1270 return True; 1271 } 1272 if(s == "Reply"){ 1273 m.mkreply(); 1274 return True; 1275 } 1276 if(s == "Del"){ 1277 if(m.w.wdel(False)){ 1278 m.isopen = False; 1279 exit; 1280 } 1281 return True; 1282 } 1283 if(s == "Delmesg"){ 1284 if(m.w.wdel(False)){ 1285 m.isopen = False; 1286 m.box.cdel <-= m; 1287 exit; 1288 } 1289 return True; 1290 } 1291 return False; 1292} 1293 1294Mesg.save(m : self ref Mesg, base : string) 1295{ 1296 s, buf : string; 1297 n : int; 1298 fd : ref FD; 1299 b : ref Iobuf; 1300 1301 if(m.id <= 0){ 1302 fprint(stderr, "can't save reply message; mail it to yourself\n"); 1303 return; 1304 } 1305 buf = nil; 1306 s = base; 1307{ 1308 if(access(s) < 0) 1309 raise("e"); 1310 fd = tryopen(s, OWRITE); 1311 if(fd == nil) 1312 raise("e"); 1313 buf = nil; 1314 b = bufio->fopen(fd, OWRITE); 1315 # seek to end in case file isn't append-only 1316 b.seek(big 0, 2); 1317 # use edited headers: first line of real header followed by remainder of selected ones 1318 for(n=0; n<len m.realhdr && m.realhdr[n++]!='\n'; ) 1319 ; 1320 b.puts(m.realhdr[0:n]); 1321 b.puts(m.hdr[m.lline1:]); 1322 b.puts(m.text); 1323 b.close(); 1324 b = nil; 1325 fd = nil; 1326} 1327exception{ 1328 "*" => 1329 buf = nil; 1330 fprint(stderr, "mail: can't open %s: %r\n", base); 1331 return; 1332} 1333} 1334 1335Mesg.send(m : self ref Mesg) 1336{ 1337 s, buf : string; 1338 t, u : int; 1339 a, b : list of string; 1340 n : int; 1341 p : array of ref FD; 1342 c : chan of int; 1343 1344 p = array[2] of ref FD; 1345 s = m.w.wreadall(); 1346 a = "sendmail" :: nil; 1347 if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:")) 1348 s = s[5:]; 1349 for(t=0; t < len s && s[t]!='\n' && s[t]!='\t';){ 1350 while(t < len s && (s[t]==' ' || s[t]==',')) 1351 t++; 1352 u = t; 1353 while(t < len s && s[t]!=' ' && s[t]!=',' && s[t]!='\t' && s[t]!='\n') 1354 t++; 1355 if(t == u) 1356 break; 1357 a = s[u:t] :: a; 1358 } 1359 b = nil; 1360 for ( ; a != nil; a = tl a) 1361 b = hd a :: b; 1362 a = b; 1363 while(t < len s && s[t]!='\n') 1364 t++; 1365 if(s[t] == '\n') 1366 t++; 1367 if(pipe(p) < 0) 1368 error("can't pipe: %r"); 1369 c = chan of int; 1370 spawn run(a, c, p[0]); 1371 <-c; 1372 c = nil; 1373 p[0] = nil; 1374 n = len s - t; 1375 if(swrite(p[1], s[t:]) != n) 1376 fprint(stderr, "write to pipe failed: %r\n"); 1377 p[1] = nil; 1378 # run() frees the arg list 1379 buf = sprint("Mail/box/%d-R", m.id); 1380 m.w.wname(buf); 1381 m.w.wclean(); 1382} 1383 1384Box.read(readonly : int) : ref Box 1385{ 1386 b : ref Box; 1387 m : ref Mesg; 1388 buf : string; 1389 1390 b = ref Box; 1391 b.nm = 0; 1392 b.leng = 0; 1393 b.readonly = readonly; 1394 pop3open(); 1395 pop3init(b); 1396 while((m = m.read(b)) != nil){ 1397 m.next = b.m; 1398 b.m = m; 1399 b.nm++; 1400 m.id = b.nm; 1401 } 1402 pop3close(); 1403 if (b.leng != b.nm) 1404 error("bad message count in Box.read()"); 1405 b.w = Win.wnew(); 1406 for(m=b.m; m != nil; m=m.next){ 1407 if(m.subj != nil) 1408 buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj); 1409 else 1410 buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]); 1411 b.w.wwritebody(buf); 1412 } 1413 buf = sprint("Mail/box/"); 1414 b.w.wname(buf); 1415 if(b.readonly) 1416 b.w.wtagwrite("Mail"); 1417 else 1418 b.w.wtagwrite("Put Mail"); 1419 buf = "Mail " + "box"; 1420 b.w.wsetdump("/acme/mail", buf); 1421 b.w.wclean(); 1422 b.w.wselect("0"); 1423 b.w.wdormant(); 1424 b.cdel= chan of ref Mesg; 1425 b.cevent = chan of Event; 1426 b.cmore = chan of int; 1427 spawn b.w.wslave(b.cevent); 1428 b.clean = True; 1429 return b; 1430} 1431 1432Box.readmore(b : self ref Box) 1433{ 1434 m : ref Mesg; 1435 new : int; 1436 buf : string; 1437 1438 new = False; 1439 leng := b.leng; 1440 n := 0; 1441 pop3open(); 1442 pop3more(b); 1443 while((m = m.read(b)) != nil){ 1444 m.next = b.m; 1445 b.m = m; 1446 b.nm++; 1447 n++; 1448 m.id = b.nm; 1449 if(m.subj != nil) 1450 buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj); 1451 else 1452 buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]); 1453 b.w.wreplace("0", buf); 1454 new = True; 1455 } 1456 pop3close(); 1457 if (b.leng != leng+n) 1458 error("bad message count in Box.readmore()"); 1459 if(new){ 1460 if(b.clean) 1461 b.w.wclean(); 1462 b.w.wselect("0;/.*(\\n[ \t].*)*"); 1463 b.w.wshow(); 1464 } 1465 b.w.wdormant(); 1466} 1467 1468Box.readline(b : self ref Box) : string 1469{ 1470 for (;;) { 1471 if(b.peekline != nil){ 1472 b.line = b.peekline; 1473 b.peekline = nil; 1474 }else 1475 b.line = pop3next(b); 1476 # nulls appear in mailboxes! 1477 if(b.line != nil && strchr(b.line, 0) >= 0) 1478 ; 1479 else 1480 break; 1481 } 1482 return b.line; 1483} 1484 1485Box.unreadline(b : self ref Box) 1486{ 1487 b.peekline = b.line; 1488} 1489 1490Box.slave(b : self ref Box) 1491{ 1492 e : ref Event; 1493 m : ref Mesg; 1494 1495 e = newevent(); 1496 for(;;){ 1497 alt{ 1498 *e = <-b.cevent => 1499 b.event(e); 1500 break; 1501 <-b.cmore => 1502 b.readmore(); 1503 break; 1504 m = <-b.cdel => 1505 b.mdel(m); 1506 break; 1507 } 1508 } 1509} 1510 1511Box.event(b : self ref Box, e : ref Event) 1512{ 1513 e2, ea, eq : ref Event; 1514 s : string; 1515 t : int; 1516 n, na, nopen : int; 1517 1518 e2 = newevent(); 1519 ea = newevent(); 1520 case(e.c1){ 1521 'E' => # write to body; can't affect us 1522 break; 1523 'F' => # generated by our actions; ignore 1524 break; 1525 'K' => # type away; we don't care 1526 break; 1527 'M' => 1528 case(e.c2){ 1529 'x' or 'X' => 1530 if(e.flag & 2) 1531 *e2 = <-b.cevent; 1532 if(e.flag & 8){ 1533 *ea = <-b.cevent; 1534 na = ea.nb; 1535 <- b.cevent; 1536 }else 1537 na = 0; 1538 s = string e.b[0:e.nb]; 1539 # if it's a known command, do it 1540 if((e.flag&2) && e.nb==0) 1541 s = string e2.b[0:e2.nb]; 1542 if(na) 1543 s = sprint("%s %s", s, string ea.b[0:ea.nb]); 1544 # if it's a long message, it can't be for us anyway 1545 if(!b.command(s)) # send it back 1546 b.w.wwriteevent(e); 1547 if(na) 1548 s = nil; 1549 break; 1550 'l' or 'L' => 1551 eq = e; 1552 if(e.flag & 2){ 1553 *e2 = <-b.cevent; 1554 eq = e2; 1555 } 1556 s = string eq.b[0:eq.nb]; 1557 if(eq.q1>eq.q0 && eq.nb==0) 1558 s = b.w.wread(eq.q0, eq.q1); 1559 nopen = 0; 1560 do{ 1561 t = 0; 1562 (n, t) = strtoi(s); 1563 if(n>0 && (t == len s || s[t]==' ' || s[t]=='\t' || s[t]=='\n')){ 1564 b.mopen(n); 1565 nopen++; 1566 s = s[t:]; 1567 } 1568 while(s != nil && s[0]!='\n') 1569 s = s[1:]; 1570 }while(s != nil); 1571 if(nopen == 0) # send it back 1572 b.w.wwriteevent(e); 1573 break; 1574 'I' or 'D' or 'd' or 'i' => # modify away; we don't care 1575 break; 1576 * => 1577 fprint(stdout, "unknown message %c%c\n", e.c1, e.c2); 1578 break; 1579 } 1580 * => 1581 fprint(stdout, "unknown message %c%c\n", e.c1, e.c2); 1582 break; 1583 } 1584} 1585 1586Box.mopen(b : self ref Box, id : int) 1587{ 1588 m : ref Mesg; 1589 1590 for(m=b.m; m != nil; m=m.next) 1591 if(m.id == id){ 1592 m.open(); 1593 break; 1594 } 1595} 1596 1597Box.mdel(b : self ref Box, dm : ref Mesg) 1598{ 1599 m : ref Mesg; 1600 buf : string; 1601 1602 if(dm.id){ 1603 for(m=b.m; m!=nil && m!=dm; m=m.next) 1604 ; 1605 if(m == nil) 1606 error(sprint("message %d not found", dm.id)); 1607 m.deleted = 1; 1608 # remove from screen: use acme to help 1609 buf = sprint("/^%d .*\\n(^[ \t].*\\n)*/", m.id); 1610 b.w.wreplace(buf, ""); 1611 } 1612 dm.free(); 1613 b.clean = False; 1614} 1615 1616Box.command(b : self ref Box, s : string) : int 1617{ 1618 t : int; 1619 m : ref Mesg; 1620 1621 while(s[0]==' ' || s[0]=='\t' || s[0]=='\n') 1622 s = s[1:]; 1623 if(len s >= 4 && s[0:4] == "Mail"){ 1624 s = s[4:]; 1625 while(s != nil && (s[0]==' ' || s[0]=='\t' || s[0]=='\n')) 1626 s = s[1:]; 1627 t = 0; 1628 while(t < len s && s[t] && s[t]!=' ' && s[t]!='\t' && s[t]!='\n') 1629 t++; 1630 m = b.m; # avoid warning message on b.m.mkmail(...) 1631 m.mkmail(b, s[0:t]); 1632 return True; 1633 } 1634 if(s == "Del"){ 1635 1636 if(!b.clean){ 1637 b.clean = True; 1638 fprint(stderr, "mail: mailbox not written\n"); 1639 return True; 1640 } 1641 postnote(PNGROUP, pctl(0, nil), "kill"); 1642 killing = 1; 1643 pctl(NEWPGRP, nil); 1644 b.w.wdel(True); 1645 for(m=b.m; m != nil; m=m.next) 1646 m.w.wdel(False); 1647 exit; 1648 return True; 1649 } 1650 if(s == "Put"){ 1651 if(b.readonly) 1652 fprint(stderr, "Mail is read-only\n"); 1653 else 1654 b.rewrite(); 1655 return True; 1656 } 1657 return False; 1658} 1659 1660Box.rewrite(b : self ref Box) 1661{ 1662 prev, m : ref Mesg; 1663 1664 if(b.clean){ 1665 b.w.wclean(); 1666 return; 1667 } 1668 prev = nil; 1669 pop3open(); 1670 for(m=b.m; m!=nil; m=m.next) { 1671 if (m.deleted && pop3del(m.popno) >= 0) { 1672 b.leng--; 1673 if (prev == nil) 1674 b.m=m.next; 1675 else 1676 prev.next=m.next; 1677 } 1678 else 1679 prev = m; 1680 } 1681 pop3close(); 1682 b.w.wclean(); 1683 b.clean = True; 1684} 1685