summaryrefslogtreecommitdiff
path: root/appl/wm/sendmail.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/sendmail.b')
-rw-r--r--appl/wm/sendmail.b652
1 files changed, 652 insertions, 0 deletions
diff --git a/appl/wm/sendmail.b b/appl/wm/sendmail.b
new file mode 100644
index 00000000..da28eca2
--- /dev/null
+++ b/appl/wm/sendmail.b
@@ -0,0 +1,652 @@
+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);
+}