ref: 76b565d8b219c8db94a882e93725b4ab735414de
dir: /appl/wm/sendmail.b/
implement WmSendmail; include "sys.m"; sys: Sys; include "draw.m"; draw: Draw; Context: import draw; include "tk.m"; tk: Tk; Toplevel: import tk; include "tkclient.m"; tkclient: Tkclient; include "dialog.m"; dialog: Dialog; include "selectfile.m"; selectfile: Selectfile; WmSendmail: module { init: fn(ctxt: ref Draw->Context, args: list of string); }; srv: Sys->Connection; main: ref Toplevel; ctxt: ref Context; username: string; mail_cfg := array[] of { "frame .top", "label .top.l -bitmap email.bit", "frame .top.con", "frame .top.con.b", "button .top.con.b.con -bitmap mailcon -command {send msg connect}", "bind .top.con.b.con <Enter> +{.top.status configure -text {connect/disconnect to mail server}}", "button .top.con.b.send -bitmap maildeliver -command {send msg send}", "bind .top.con.b.send <Enter> +{.top.status configure -text {deliver mail}}", "button .top.con.b.nocc -bitmap mailnocc -command {.hdr.e.cc delete 0 end}", "bind .top.con.b.nocc <Enter> +{.top.status configure -text {no carbon copy}}", "button .top.con.b.new -bitmap mailnew -command {send msg new}", "bind .top.con.b.new <Enter> +{.top.status configure -text {start a new message}}", "button .top.con.b.save -bitmap mailsave -command {send msg save}", "bind .top.con.b.save <Enter> +{.top.status configure -text {save message}}", "pack .top.con.b.con .top.con.b.send .top.con.b.nocc .top.con.b.new .top.con.b.save -padx 2 -side left", "label .top.status -text {not connected ...} -anchor w", "pack .top.l -side left", "pack .top.con -side left -padx 10", "pack .top.con.b .top.status -in .top.con -fill x -expand 1", "frame .hdr", "frame .hdr.l", "frame .hdr.e", "label .hdr.l.mt -text {Mail To:}", "label .hdr.l.cc -text {Mail CC:}", "label .hdr.l.sb -text {Subject:}", "pack .hdr.l.mt .hdr.l.cc .hdr.l.sb -fill y -expand 1", "entry .hdr.e.mt -bg white", "entry .hdr.e.cc -bg white", "entry .hdr.e.sb -bg white", "bind .hdr.e.mt <Key-\n> {}", "bind .hdr.e.cc <Key-\n> {}", "bind .hdr.e.sb <Key-\n> {}", "pack .hdr.e.mt .hdr.e.cc .hdr.e.sb -fill x -expand 1", "pack .hdr.l -side left -fill y", "pack .hdr.e -side left -fill x -expand 1", "frame .body", "scrollbar .body.scroll -command {.body.t yview}", "text .body.t -width 15c -height 7c -yscrollcommand {.body.scroll set} -bg white", "pack .body.t -side left -expand 1 -fill both", "pack .body.scroll -side left -fill y", "pack .top -anchor w -padx 5", "pack .hdr -fill x -anchor w -padx 5 -pady 5", "pack .body -expand 1 -fill both -padx 5 -pady 5", "pack .b -padx 5 -pady 5 -fill x", "pack propagate . 0", "update" }; con_cfg := array[] of { "frame .b", "button .b.ok -text {Connect} -command {send cmd ok}", "button .b.can -text {Cancel} -command {send cmd can}", "pack .b.ok .b.can -side left -fill x -padx 10 -pady 10 -expand 1", "frame .l", "label .l.h -text {Mail Server:} -anchor w", "label .l.u -text {User Name:} -anchor w", "pack .l.h .l.u -fill both -expand 1", "frame .e", "entry .e.h -width 30w", "entry .e.u -width 30w", "pack .e.h .e.u -fill x", "frame .f -borderwidth 2 -relief raised", "pack .l .e -fill both -expand 1 -side left -in .f", "bind .e.h <Key-\n> {send cmd ok}", "bind .e.u <Key-\n> {send cmd ok}", }; con_pack := array[] of { "pack .f", "pack .b -fill x -expand 1", "focus .e.u", "update", }; new_cmd := array[] of { ".hdr.e.mt delete 0 end", ".hdr.e.cc delete 0 end", ".hdr.e.sb delete 0 end", ".body.t delete 1.0 end", ".body.t see 1.0", "update" }; init(xctxt: ref Context, argv: list of string) { sys = load Sys Sys->PATH; if (xctxt == nil) { sys->fprint(sys->fildes(2), "sendmail: no window context\n"); raise "fail:bad context"; } draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; tkclient = load Tkclient Tkclient->PATH; dialog = load Dialog Dialog->PATH; selectfile = load Selectfile Selectfile->PATH; ctxt = xctxt; tkclient->init(); dialog->init(); selectfile->init(); tkargs := ""; argv = tl argv; if(argv != nil) { tkargs = hd argv; argv = tl argv; } titlectl: chan of string; (main, titlectl) = tkclient->toplevel(ctxt, tkargs, "MailStop: Sender", Tkclient->Appl); msg := chan of string; tk->namechan(main, msg, "msg"); for (c:=0; c<len mail_cfg; c++) tk->cmd(main, mail_cfg[c]); tkclient->onscreen(main, nil); tkclient->startinput(main, "kbd"::"ptr"::nil); if(argv != nil) fromreadmail(hd argv); for(;;) alt { s := <-main.ctxt.kbd => tk->keyboard(main, s); s := <-main.ctxt.ptr => tk->pointer(main, *s); s := <-main.ctxt.ctl or s = <-main.wreq or s = <-titlectl => if(s == "exit") { if(srv.dfd == nil) return; status("Closing connection..."); smtpcmd("QUIT"); return; } tkclient->wmctl(main, s); cmd := <-msg => case cmd { "connect" => if(srv.dfd == nil) { connect(main, 1); fixbutton(); break; } disconnect(); "save" => save(); "send" => sendmail(); "new" => for (c=0; c<len new_cmd; c++) tk->cmd(main, new_cmd[c]); } } } fixbutton() { s := "Connect"; if(srv.dfd != nil) s = "Disconnect"; tk->cmd(main, ".top.con configure -text "+s+"; update"); } sendmail() { if(srv.dfd == nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "You must be connected to deliver mail", 0, "Continue"::nil); return; } mto := tk->cmd(main, ".hdr.e.mt get"); if(mto == "") { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "You must fill in the \"Mail To\" entry", 0, "Continue (nothing sent)"::nil); return; } if(tk->cmd(main, ".body.t index end") == "1.0") { opt := "Cancel" :: "Send anyway" :: nil; if(dialog->prompt(ctxt, main.image, "warning -fg yellow", "Send", "The body of the mail is empty", 0, opt) == 0) return; } (err, s) := smtpcmd("MAIL FROM:<"+username+">"); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Failed to specify FROM correctly:\n"+err, 0, "Continue (nothing sent)"::nil); return; } status(s); (err, s) = smtpcmd("RCPT TO:<"+mto+">"); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Failed to specify TO correctly:\n"+err, 0, "Continue (nothing sent)"::nil); return; } status(s); cc := tk->cmd(main, ".hdr.e.cc get"); if(cc != nil) { (nil, l) := sys->tokenize(cc, "\t ,"); while(l != nil) { copy := hd l; (err, s) = smtpcmd("RCPT TO:<"+copy+">"); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Carbon copy to "+copy+"failed:\n"+err, 0, "Continue (nothing sent)"::nil); } } } (err, s) = smtpcmd("DATA"); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Failed to enter DATA mode:\n"+err, 0, "Continue (nothing sent)"::nil); return; } sub := tk->cmd(main, ".hdr.e.sb get"); if(sub != nil) sys->fprint(srv.dfd, "Subject: %s\n", sub); b := array of byte tk->cmd(main, ".body.t get 1.0 end"); n := sys->write(srv.dfd, b, len b); b = nil; if(n < 0) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Error writing server:\n"+sys->sprint("%r"), 0, "Abort (partial send)"::nil); return; } (err, s) = smtpcmd("\r\n."); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Send", "Failed to terminate message:\n"+err, 0, "Abort (partial send)"::nil); return; } status(s); } save() { mto := tk->cmd(main, ".hdr.e.to get"); if(mto == "") { dialog->prompt(ctxt, main.image, "error -fg red", "Save", "No message to save", 0, "Dismiss"::nil); return; } pat := list of { "*.letter (Saved mail)", "* (All files)" }; fname: string; fd: ref Sys->FD; for(;;) { fname = selectfile->filename(ctxt, main.image, "Save in Mailbox", pat, "/usr/"+rf("/dev/user")+"/mail"); if(fname == nil) return; fd = sys->create(fname, sys->OWRITE, 8r660); if(fd != nil) break; r := dialog->prompt(ctxt, main.image, "error -fg red", "Save", "Failed to create "+sys->sprint("%s\n%r", fname), 0, "Retry"::"Cancel"::nil); if(r > 0) return; } r := sys->fprint(srv.dfd, "Mail To: %s\n", mto); cc := tk->cmd(main, ".hdr.e.cc get"); if(cc != nil) r += sys->fprint(srv.dfd, "Mail CC: %s\n", cc); sb := tk->cmd(main, ".hdr.e.sb get"); if(sb != nil) r += sys->fprint(srv.dfd, "Subject: %s\n\n", sb); s := tk->cmd(main, ".body.t get 1.0 end"); b := array of byte s; n := sys->write(fd, b, len b); if(n < 0) { dialog->prompt(ctxt, main.image, "error -fg red", "Save", "Error writing file "+sys->sprint("%s\n%r", fname), 0, "Continue"::nil); return; } status("wrote "+string(n+r)+" bytes."); } status(msg: string) { tk->cmd(main, ".top.status configure -text {"+msg+"}; update"); } disconnect() { (err, s) := smtpcmd("QUIT"); srv.dfd = nil; fixbutton(); if(err != nil) { dialog->prompt(ctxt, main.image, "error -fg red", "Disconnect", "Server problem:\n"+err, 0, "Dismiss"::nil); return; } status(s); } connect(parent: ref Toplevel, interactive: int) { (t, conctl) := tkclient->toplevel(ctxt, postposn(parent), "Connection Parameters", 0); cmd := chan of string; tk->namechan(t, cmd, "cmd"); for (c:=0; c<len con_cfg; c++) tk->cmd(t, con_cfg[c]); username = rf("/dev/user"); s := rf("/usr/"+username+"/mail/smtpserver"); if(s != "") tk->cmd(t, ".e.h insert 0 '"+s); s = rf("/usr/"+username+"/mail/domain"); if(s != nil) username += "@"+s; u := tk->cmd(t, ".e.u get"); if(u == "") tk->cmd(t, ".e.u insert 0 '"+username); if(interactive == 0 && checkthendial(t) != 0) return; for (c=0; c<len con_pack; c++) tk->cmd(t, con_pack[c]); tkclient->onscreen(t, nil); tkclient->startinput(t, "kbd"::"ptr"::nil); for(;;) alt { ss := <-t.ctxt.kbd => tk->keyboard(t, ss); ss := <-t.ctxt.ptr => tk->pointer(t, *ss); ss := <-t.ctxt.ctl or ss = <-t.wreq or ss = <-conctl => if (ss == "exit") return; tkclient->wmctl(t, ss); s = <-cmd => if(s == "can") return; if(checkthendial(t) != 0) return; status("not connected"); } srv.dfd = nil; } checkthendial(t: ref Toplevel): int { server := tk->cmd(t, ".e.h get"); if(server == "") { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "You must supply a server address", 0, "Continue"::nil); return 0; } user := tk->cmd(t, ".e.u get"); if(user == "") { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "You must supply a user name", 0, "Continue"::nil); return 0; } if(dom(user) == "") { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "The user name must contain an '@'", 0, "Continue"::nil); return 0; } return dialer(t, server, user); } dialer(t: ref Toplevel, server, user: string): int { ok: int; status("dialing server..."); (ok, srv) = sys->dial(netmkaddr(server, nil, "25"), nil); if(ok < 0) { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "The following error occurred while\n"+ "dialing the server: "+sys->sprint("%r"), 0, "Continue"::nil); return 0; } status("connected..."); (err, s) := smtpresp(); if(err != nil) { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "An error occurred during sign on.\n"+err, 0, "Continue"::nil); return 0; } status(s); (err, s) = smtpcmd("HELO "+dom(user)); if(err != nil) { dialog->prompt(ctxt, t.image, "error -fg red", "Connect", "An error occurred during login.\n"+err, 0, "Continue"::nil); return 0; } status("ready to send..."); return 1; } rf(file: string): string { fd := sys->open(file, sys->OREAD); if(fd == nil) return ""; buf := array[128] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return ""; return string buf[0:n]; } postposn(parent: ref Toplevel): string { x := int tk->cmd(parent, ".top.con cget -actx"); y := int tk->cmd(parent, ".top.con cget -acty"); h := int tk->cmd(parent, ".top.con cget -height"); return "-x "+string(x-2)+" -y "+string(y+h+2); } dom(name: string): string { for(i := 0; i < len name; i++) if(name[i] == '@') return name[i+1:]; return nil; } fromreadmail(hdr: string) { (nil, l) := sys->tokenize(hdr, "\n"); while(l != nil) { s := hd l; l = tl l; n := match(s, "subject: "); if(n != nil) { tk->cmd(main, ".hdr.e.sb insert end '"+n); continue; } n = match(s, "cc: "); if(n != nil) { tk->cmd(main, ".hdr.e.cc insert end '"+n); continue; } n = match(s, "from: "); if(n != nil) { n = extract(n); tk->cmd(main, ".hdr.e.mt insert end '"+n); } } connect(main, 0); } extract(name: string): string { for(i := 0; i < len name; i++) { if(name[i] == '<') { for(j := i+1; j < len name; j++) if(name[j] == '>') break; return name[i+1:j]; } } for(i = 0; i < len name; i++) if(name[i] == ' ') break; return name[0:i]; } lower(c: int): int { if(c >= 'A' && c <= 'Z') c = 'a' + (c - 'A'); return c; } match(text, pat: string): string { for(i := 0; i < len pat; i++) { c := text[i]; p := pat[i]; if(c != p && lower(c) != p) return ""; } return text[i:]; } # # Talk SMTP # smtpcmd(cmd: string): (string, string) { cmd += "\r\n"; # sys->print("->%s", cmd); b := array of byte cmd; l := len b; n := sys->write(srv.dfd, b, l); if(n != l) return ("send to server:"+sys->sprint("%r"), nil); return smtpresp(); } smtpresp(): (string, string) { s := ""; i := 0; lastc := 0; for(;;) { c := smtpgetc(); if(c == -1) return ("read from server:"+sys->sprint("%r"), nil); if(lastc == '\r' && c == '\n') break; s[i++] = c; lastc = c; } # sys->print("<-%s\n", s); if(i < 3) return ("short read from server", nil); s = s[0:i-1]; case s[0] { '1' or '2' or '3' => i = 3; while(s[i] == ' ' && i < len s) i++; return (nil, s[i:]); '4'or '5' => i = 3; while(s[i] == ' ' && i < len s) i++; return (s[i:], nil); * => return ("invalid server response", nil); } } Iob: adt { nbyte: int; posn: int; buf: array of byte; }; smtpbuf: Iob; smtpgetc(): int { if(smtpbuf.nbyte > 0) { smtpbuf.nbyte--; return int smtpbuf.buf[smtpbuf.posn++]; } if(smtpbuf.buf == nil) smtpbuf.buf = array[512] of byte; smtpbuf.posn = 0; n := sys->read(srv.dfd, smtpbuf.buf, len smtpbuf.buf); if(n < 0) return -1; smtpbuf.nbyte = n-1; return int smtpbuf.buf[smtpbuf.posn++]; } netmkaddr(addr, net, svc: string): string { if(net == nil) net = "net"; (n, l) := sys->tokenize(addr, "!"); if(n <= 1){ if(svc== nil) return sys->sprint("%s!%s", net, addr); return sys->sprint("%s!%s!%s", net, addr, svc); } if(svc == nil || n > 2) return addr; return sys->sprint("%s!%s", addr, svc); }