summaryrefslogtreecommitdiff
path: root/appl/wm/readmail.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/readmail.b')
-rw-r--r--appl/wm/readmail.b885
1 files changed, 885 insertions, 0 deletions
diff --git a/appl/wm/readmail.b b/appl/wm/readmail.b
new file mode 100644
index 00000000..e674bb4b
--- /dev/null
+++ b/appl/wm/readmail.b
@@ -0,0 +1,885 @@
+implement WmReadmail;
+
+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;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+WmReadmail: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+WmSendmail: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+srv: Sys->Connection;
+main: ref Toplevel;
+ctxt: ref Context;
+nmesg: int;
+cmesg: int;
+map: array of byte;
+Ok, Deleted: con iota;
+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.next -bitmap mailnext -command {send msg next}",
+ "bind .top.con.b.next <Enter> +{.top.status configure -text {next message}}",
+ "button .top.con.b.prev -bitmap mailprev -command {send msg prev}",
+ "bind .top.con.b.prev <Enter> +{.top.status configure -text {previous message}}",
+ "button .top.con.b.del -bitmap maildel -command {send msg dele}",
+ "bind .top.con.b.del <Enter> +{.top.status configure -text {delete message}}",
+ "button .top.con.b.reply -bitmap mailreply -command {send msg reply}",
+ "bind .top.con.b.reply <Enter> +{.top.status configure -text {reply to message}}",
+ "button .top.con.b.fwd -bitmap mailforward",
+ "bind .top.con.b.fwd <Enter> +{.top.status configure -text {forward message}}",
+ "button .top.con.b.hdr -bitmap mailhdr -command {send msg hdrs}",
+ "bind .top.con.b.hdr <Enter> +{.top.status configure -text {fetch message headers}}",
+ "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.prev .top.con.b.next .top.con.b.del .top.con.b.reply .top.con.b.fwd .top.con.b.hdr .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",
+ "scrollbar .hdr.scroll -command {.hdr.t yview}",
+ "text .hdr.t -height 3c -yscrollcommand {.hdr.scroll set} -bg white",
+ "frame .hdr.pad -width 2c",
+ "pack .hdr.t -side left -fill x -expand 1",
+ "pack .hdr.scroll -side left -fill y",
+ "pack .hdr.pad",
+ "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",
+ "label .l.s -text {Secret:} -anchor w",
+ "pack .l.h .l.u .l.s -fill both -expand 1",
+ "frame .e",
+ "entry .e.h",
+ "entry .e.u",
+ "entry .e.s -show •",
+ "pack .e.h .e.u .e.s -fill x",
+ "frame .f -borderwidth 2 -relief raised",
+ "pack .l .e -fill both -expand 1 -side left -in .f",
+ "pack .f",
+ "pack .b -fill x -expand 1",
+ "bind .e.h <Key-\n> {send cmd ok}",
+ "bind .e.u <Key-\n> {send cmd ok}",
+ "bind .e.s <Key-\n> {send cmd ok}",
+ "focus .e.s",
+};
+
+hdr_cfg := array[] of {
+ "scrollbar .sh -orient horizontal -command {.f.l xview}",
+ "scrollbar .f.sv -command {.f.l yview}",
+ "frame .f",
+ "listbox .f.l -width 80w -height 20h -yscrollcommand { .f.sv set} -xscrollcommand { .sh set}",
+ "pack .f.l -side left -fill both -expand 1",
+ "pack .f.sv -side left -fill y",
+ "pack .f -fill both -expand 1",
+ "pack .sh -fill x",
+ "pack propagate . 0",
+ "bind .f.l <Double-Button> { send tomain [.f.l get [.f.l curselection]] }",
+ "update",
+};
+
+init(xctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (xctxt == nil) {
+ sys->fprint(sys->fildes(2), "readmail: 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;
+ str = load String String->PATH;
+ kr = load Keyring Keyring->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, "Readmail: Reader", Tkclient->Appl);
+
+ msg := chan of string;
+ tk->namechan(main, msg, "msg");
+ hdr := chan of string;
+
+ for (c:=0; c<len mail_cfg; c++)
+ tk->cmd(main, mail_cfg[c]);
+ tkclient->onscreen(main, nil);
+ tkclient->startinput(main, "kbd"::"ptr"::nil);
+
+ 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) {
+ status("Updating mail box...");
+ pop3cmd("QUIT");
+ }
+ return;
+ }
+ tkclient->wmctl(main, s);
+ cmd := <-msg =>
+ case cmd {
+ "connect" =>
+ if(srv.dfd == nil) {
+ connect(main);
+ if(srv.dfd != nil)
+ initialize();
+ break;
+ }
+ disconnect();
+ "prev" =>
+ if(cmesg > nmesg) {
+ status("no more messages.");
+ break;
+ }
+ for(new := cmesg+1; new <= nmesg; new++) {
+ if(map[new] == byte Ok) {
+ cmesg = new;
+ loadmesg();
+ break;
+ }
+ }
+ "next" =>
+ for(new := cmesg-1; new >= 1; new--) {
+ if(map[new] == byte Ok) {
+ cmesg = new;
+ loadmesg();
+ break;
+ }
+ }
+ "dele" =>
+ delete();
+ if(cmesg > 0) {
+ cmesg--;
+ loadmesg();
+ }
+ "hdrs" =>
+ headers(hdr);
+ "save" =>
+ save();
+ "reply" =>
+ reply();
+ }
+ get := <-hdr =>
+ new := int get;
+ if(new < 1 || new > nmesg || map[new] != byte Ok)
+ break;
+ cmesg = new;
+ loadmesg();
+ }
+}
+
+headers(tomain: chan of string)
+{
+ (hdr, hdrctl) := tkclient->toplevel(ctxt, nil,
+ "Readmail: Headers", Tkclient->Appl);
+
+ tk->namechan(hdr, tomain, "tomain");
+
+ for (c:=0; c<len hdr_cfg; c++)
+ tk->cmd(hdr, hdr_cfg[c]);
+
+ for(i := 1; i <= nmesg; i++) {
+ if(map[i] == byte Deleted) {
+ info := sys->sprint("%4d ...Deleted...\n", i);
+ tk->cmd(hdr, ".f.l insert 0 '"+info);
+ continue;
+ }
+ if(topit(hdr, i) == 0)
+ break;
+ alt {
+ s := <-hdrctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(hdr, s);
+ * =>
+ ;
+ }
+ if((i%10) == 9)
+ tk->cmd(hdr, "update");
+ }
+ tk->cmd(hdr, "update");
+ tkclient->onscreen(hdr, nil);
+ tkclient->startinput(hdr, "kbd"::"ptr"::nil);
+
+ spawn hproc(hdrctl, hdr);
+}
+
+trunc(name: string): string
+{
+ for(i := 0; i < len name; i++)
+ if(name[i] == '<')
+ break;
+ i++;
+ if(i >= len name)
+ return name;
+ for(j := i; j < len name; j++)
+ if(name[j] == '>')
+ break;
+ return name[i:j];
+}
+
+topit(hdr: ref Toplevel, msg: int): int
+{
+ (err, s) := pop3cmd("TOP "+string msg+" 0");
+ if(err != nil) {
+ dialog->prompt(ctxt, hdr.image, "error -fg red", "POP3 Error",
+ "Ecountered a problem fetching headers\n"+err,
+ 0, "Dismiss"::nil);
+ return 0;
+ }
+
+ size := int s;
+ b := pop3body(size);
+ if(b == nil)
+ return 0;
+
+ from := getfield("from", b);
+ from = trunc(from);
+ date := getfield("date", b);
+ subj := getfield("subject", b);
+ if(len subj > 20)
+ subj = subj[0:19];
+
+ if(len subj > 0)
+ info := sys->sprint("%4d %5d %s \"%s\" %s", msg, size, from, subj, date);
+ else
+ info = sys->sprint("%4d %5d %s %s", msg, size, from, date);
+
+ tk->cmd(hdr, ".f.l insert 0 '"+info);
+ return 1;
+}
+
+mapdown(b: array of byte): string
+{
+ lb := len b;
+ l := array[lb] of byte;
+ for(i := 0; i < lb; i++) {
+ c := b[i];
+ if(c >= byte 'A' && c <= byte 'Z')
+ c += byte('a' - 'A');
+ l[i] = c;
+ }
+ return string l;
+}
+
+getfield(key: string, text: array of byte): string
+{
+ key[len key] = ':';
+ lk := len key;
+ cl := byte key[0];
+ cu := cl - byte ('a' - 'A');
+
+ lc: byte;
+ for(i := 0; i < len text - lk; i++) {
+ t := text[i];
+ if(t == byte '\n' && lc == byte '\n') # end header
+ break;
+ lc = t;
+ if(t != cu && t != cl)
+ continue;
+ if(key == mapdown(text[i:i+lk])) {
+ i += lk+1;
+ for(j := i+1; j < len text; j++) {
+ c := text[j];
+ if(c == byte '\r' || c == byte '\n')
+ break;
+ }
+ return string text[i:j];
+ }
+ }
+ return "";
+}
+
+hproc(wmctl: chan of string, top: ref Toplevel)
+{
+ for(;;) {
+ alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq or
+ s = <-wmctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(top, s);
+ }
+ }
+}
+
+reply()
+{
+ if(cmesg == 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "No message to reply to",
+ 0, "Abort"::nil);
+ return;
+ }
+
+ hdr := tk->cmd(main, ".hdr.t get 1.0 end");
+ if(hdr == "") {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "Mail has no header to reply to",
+ 0, "Abort"::nil);
+ return;
+ }
+
+ wmsender := load WmSendmail "/dis/wm/sendmail.dis";
+ if(wmsender == nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "Failed to load mail sender:\n"+sys->sprint("%r"),
+ 0, "Abort"::nil);
+ return;
+ }
+
+ spawn wmsender->init(ctxt, "sendmail" :: hdr :: nil);
+}
+
+save()
+{
+ if(cmesg == 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "No current message",
+ 0, "Continue"::nil);
+ return;
+ }
+ pat := list of {
+ "*.let (Saved mail)",
+ "* (All files)"
+ };
+
+ fd: ref Sys->FD;
+ fname: string;
+ for(;;) {
+ fname = selectfile->filename(ctxt, main.image, "Save in Mailbox",
+ pat, "/usr/"+username+"/mail");
+ if(fname == nil)
+ return;
+
+ fd = sys->create(fname, sys->OWRITE, 8r660);
+ if(fd != nil)
+ break;
+
+ labs := list of {
+ "New name",
+ "Abort"
+ };
+
+ r := dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Failed to create "+sys->sprint("%s\n%r", fname),
+ 0, labs);
+ if(r == 1)
+ return;
+ }
+ s := tk->cmd(main, ".hdr.t get 1.0 end");
+ b := array of byte s;
+ r := sys->write(fd, b, len b);
+ if(r < 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Error writing file"+sys->sprint("%s\n%r", fname),
+ 0, "Continue (not saved)":: nil);
+ return;
+ }
+ 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 (not saved)":: nil);
+ return;
+ }
+ status("wrote "+string(n+r)+" bytes.");
+}
+
+delete()
+{
+ if(srv.dfd == nil) {
+ dialog->prompt(ctxt, main.image, "warning -fg yellow", "Delete",
+ "You must be connected to delete messages",
+ 0, "Continue"::nil);
+ return;
+ }
+ (err, s) := pop3cmd("DELE "+string cmesg);
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Delete",
+ "Encountered POP3 problem during delete\n"+err,
+ 0, "Continue"::nil);
+ return;
+ }
+ map[cmesg] = byte Deleted;
+ status(s);
+}
+
+status(msg: string)
+{
+ tk->cmd(main, ".top.status configure -text {"+msg+"}; update");
+}
+
+disconnect()
+{
+ (err, s) := pop3cmd("QUIT");
+ srv.dfd = nil;
+ tk->cmd(main, ".top.con configure -text Connect");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Disconnect",
+ "POP3 protocol problem\n"+err,
+ 0, "Proceed"::nil);
+ return;
+ }
+ status(s);
+}
+
+connect(parent: ref Toplevel)
+{
+ (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");
+ sv := rf("/usr/"+username+"/mail/popserver");
+ if(sv != "")
+ tk->cmd(t, ".e.h insert 0 '"+sv);
+
+ u := tk->cmd(t, ".e.u get");
+ if(u == "")
+ tk->cmd(t, ".e.u insert 0 '"+username);
+
+ tk->cmd(t, "update");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-conctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(t, s);
+ s := <-cmd =>
+ if(s == "can")
+ return;
+ 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, "Proceed"::nil);
+ break;
+ }
+ 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, "Proceed"::nil);
+ break;
+ }
+ pass := tk->cmd(t, ".e.s get");
+ if(pass == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must give a secret or password",
+ 0, "Proceed"::nil);
+ break;
+ }
+ if(dialer(t, server, user, pass) != 0)
+ return;
+ status("not connected");
+ }
+ srv.dfd = nil;
+}
+
+initialize()
+{
+ (err, s) := pop3cmd("STAT");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Mailbox Status",
+ "The following error occurred while "+
+ "checking your mailbox:\n"+err,
+ 0, "Dismiss"::nil);
+ srv.dfd = nil;
+ status("not connected");
+ return;
+ }
+
+ tk->cmd(main, ".top.con configure -text Disconnect; update");
+ nmesg = int s;
+ if(nmesg == 0) {
+ status("There are no messages.");
+ return;
+ }
+
+ map = array[nmesg+1] of byte;
+ for(i := 0; i <= nmesg; i++)
+ map[i] = byte Ok;
+
+ s = "";
+ if(nmesg > 1)
+ s = "s";
+ status("You have "+string nmesg+" message"+s);
+ cmesg = nmesg;
+ loadmesg();
+}
+
+loadmesg()
+{
+ if(srv.dfd == nil) {
+ dialog->prompt(ctxt, main.image, "warning -fg yellow", "Read",
+ "You must be connected to read messages",
+ 0, "Continue"::nil);
+ return;
+ }
+ (err, s) := pop3cmd("RETR "+sys->sprint("%d", cmesg));
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Read",
+ "Error retrieving message:\n"+err,
+ 0, "Continue"::nil);
+ return;
+ }
+
+ tk->cmd(main, ".hdr.t delete 1.0 end; .body.t delete 1.0 end");
+ size := int s;
+
+ status("reading "+string size+" bytes ...");
+
+ b := pop3body(size);
+
+ (headr, body) := split(string b);
+ b = nil;
+ tk->cmd(main, ".hdr.t insert end '"+headr);
+ tk->cmd(main, ".body.t insert end '"+body);
+ tk->cmd(main, ".hdr.t see 1.0; .body.t see 1.0");
+ status("read message "+string cmesg+" of "+string nmesg+" , ready...");
+}
+
+split(text: string): (string, string)
+{
+ c, lc: int;
+ hdr, body: string;
+
+ hp := 0;
+ for(i := 0; i < len text; i++) {
+ c = text[i];
+ if(c == '\r')
+ continue;
+ hdr[hp++] = c;
+ if(lc == '\n' && c == '\n')
+ break;
+ lc = c;
+ }
+ bp := 0;
+ while(i < len text) {
+ c = text[i++];
+ if(c != '\r')
+ body[bp++] = c;
+ }
+ return (hdr, body);
+}
+
+dialer(t: ref Toplevel, server, user, pass: string): int
+{
+ ok: int;
+
+ for(;;) {
+ status("dialing server...");
+ (ok, srv) = sys->dial(netmkaddr(server, nil, "110"), nil);
+ if(ok >= 0)
+ break;
+
+ labs := list of {
+ "Retry",
+ "Cancel"
+ };
+ ok = dialog->prompt(ctxt, t.image, "error -fg", "Connect",
+ "The following error occurred while\n"+
+ "dialing the server: "+sys->sprint("%r"),
+ 0, labs);
+ if(ok != 0)
+ return 0;
+ }
+ status("connected...");
+ (err, s) := pop3resp();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during sign on.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ status(s);
+ (nil, s) = str->splitl(s, "<");
+ (chal, nil) := str->splitr(s, ">");
+ if(chal != nil){
+ ca := array of byte chal;
+ digest := array[kr->MD5dlen] of byte;
+ md5state := kr->md5(ca, len ca, nil, nil);
+ pa := array of byte pass;
+ kr->md5(pa, len pa, digest, md5state);
+ s = nil;
+ for(i := 0; i < kr->MD5dlen; i++)
+ s += sys->sprint("%2.2ux", int digest[i]);
+ (err, s) = pop3cmd("APOP "+user+" "+s);
+ if(err == nil) {
+ status("ready to serve...");
+ return 1;
+ } else {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "Challenge/response failed.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ }
+ (err, s) = pop3cmd("USER "+user);
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during login.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ (err, s) = pop3cmd("PASS "+pass);
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during login.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ status("ready to serve...");
+ return 1;
+}
+
+rf(file: string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[Sys->NAMEMAX] 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);
+}
+
+#
+# Talk POP3
+#
+pop3cmd(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 pop3resp();
+}
+
+pop3resp(): (string, string)
+{
+ s := "";
+ i := 0;
+ lastc := 0;
+ for(;;) {
+ c := pop3getc();
+ 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];
+ if(s[0:3] == "+OK") {
+ i = 3;
+ while(i < len s && s[i] == ' ')
+ i++;
+ return (nil, s[i:]);
+ }
+ if(s[0:4] == "-ERR") {
+ i = 4;
+ while(s[i] == ' ' && i < len s)
+ i++;
+ return (s[i:], nil);
+ }
+ return ("invalid server response", nil);
+}
+
+pop3body(size: int): array of byte
+{
+ size += 512;
+ b := array[size] of byte;
+
+ cnt := emptypopbuf(b);
+ size -= cnt;
+
+ for(;;) {
+
+ if(cnt > 5 && string b[cnt-5:cnt] == "\r\n.\r\n") {
+ b = b[0:cnt-5];
+ break;
+ }
+ # resize buffer
+ if(size == 0) {
+ nb := array[len b + 4096] of byte;
+ nb[0:] = b;
+ size = len nb - len b;
+ b = nb;
+ nb = nil;
+ }
+ n := sys->read(srv.dfd, b[cnt:], len b - cnt);
+ if(n <= 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Read",
+ sys->sprint("Error retrieving message: %r"),
+ 0, "Continue"::nil);
+ return nil;
+ }
+ size -= n;
+ cnt += n;
+ }
+ return b;
+}
+
+Iob: adt
+{
+ nbyte: int;
+ posn: int;
+ buf: array of byte;
+};
+popbuf: Iob;
+
+pop3getc(): int
+{
+ if(popbuf.nbyte > 0) {
+ popbuf.nbyte--;
+ return int popbuf.buf[popbuf.posn++];
+ }
+ if(popbuf.buf == nil)
+ popbuf.buf = array[512] of byte;
+
+ popbuf.posn = 0;
+ n := sys->read(srv.dfd, popbuf.buf, len popbuf.buf);
+ if(n < 0)
+ return -1;
+
+ popbuf.nbyte = n-1;
+ return int popbuf.buf[popbuf.posn++];
+}
+
+emptypopbuf(a: array of byte) : int
+{
+ i := popbuf.nbyte;
+
+ if (i) {
+ a[0:] = popbuf.buf[popbuf.posn:(popbuf.posn+popbuf.nbyte)];
+ popbuf.nbyte = 0;
+ }
+
+ return i;
+}
+
+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);
+}