📄 pop3.c
字号:
#include "common.h"#include <ctype.h>#include <auth.h>#include <libsec.h>typedef struct Cmd Cmd;struct Cmd{ char *name; int needauth; int (*f)(char*);};static void hello(void);static int apopcmd(char*);static int capacmd(char*);static int delecmd(char*);static int listcmd(char*);static int noopcmd(char*);static int passcmd(char*);static int quitcmd(char*);static int rsetcmd(char*);static int retrcmd(char*);static int statcmd(char*);static int stlscmd(char*);static int topcmd(char*);static int synccmd(char*);static int uidlcmd(char*);static int usercmd(char*);static char *nextarg(char*);static int getcrnl(char*, int);static int readmbox(char*);static void sendcrnl(char*, ...);static int senderr(char*, ...);static int sendok(char*, ...);#pragma varargck argpos sendcrnl 1#pragma varargck argpos senderr 1#pragma varargck argpos sendok 1Cmd cmdtab[] ={ "apop", 0, apopcmd, "capa", 0, capacmd, "dele", 1, delecmd, "list", 1, listcmd, "noop", 0, noopcmd, "pass", 0, passcmd, "quit", 0, quitcmd, "rset", 0, rsetcmd, "retr", 1, retrcmd, "stat", 1, statcmd, "stls", 0, stlscmd, "sync", 1, synccmd, "top", 1, topcmd, "uidl", 1, uidlcmd, "user", 0, usercmd, 0, 0, 0,};static Biobuf in;static Biobuf out;static int passwordinclear;static int didtls;typedef struct Msg Msg;struct Msg { int upasnum; char digest[64]; int bytes; int deleted;};static int totalbytes;static int totalmsgs;static Msg *msg;static int nmsg;static int loggedin;static int debug;static uchar *tlscert;static int ntlscert;static char *peeraddr;static char tmpaddr[64];voidusage(void){ fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n"); exits("usage");}voidmain(int argc, char **argv){ int fd; char *arg, cmdbuf[1024]; Cmd *c; rfork(RFNAMEG); Binit(&in, 0, OREAD); Binit(&out, 1, OWRITE); ARGBEGIN{ case 'a': loggedin = 1; if(readmbox(EARGF(usage())) < 0) exits(nil); break; case 'd': debug++; if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){ dup(fd, 2); close(fd); } break; case 'r': strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage())); if(arg = strchr(tmpaddr, '!')) *arg = '\0'; peeraddr = tmpaddr; break; case 't': tlscert = readcert(EARGF(usage()), &ntlscert); if(tlscert == nil){ senderr("cannot read TLS certificate: %r"); exits(nil); } break; case 'p': passwordinclear = 1; break; }ARGEND /* do before TLS */ if(peeraddr == nil) peeraddr = remoteaddr(0,0); hello(); while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){ arg = nextarg(cmdbuf); for(c=cmdtab; c->name; c++) if(cistrcmp(c->name, cmdbuf) == 0) break; if(c->name == 0){ senderr("unknown command %s", cmdbuf); continue; } if(c->needauth && !loggedin){ senderr("%s requires authentication", cmdbuf); continue; } (*c->f)(arg); } exits(nil);}/* sort directories in increasing message number order */static intdircmp(void *a, void *b){ return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);}static intreadmbox(char *box){ int fd, i, n, nd, lines, pid; char buf[100], err[ERRMAX]; char *p; Biobuf *b; Dir *d, *draw; Msg *m; Waitmsg *w; unmount(nil, "/mail/fs"); switch(pid = fork()){ case -1: return senderr("can't fork to start upas/fs"); case 0: close(0); close(1); open("/dev/null", OREAD); open("/dev/null", OWRITE); execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil); snprint(err, sizeof err, "upas/fs: %r"); _exits(err); break; default: break; } if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){ if(w && w->pid==pid) return senderr("%s", w->msg); else return senderr("can't initialize upas/fs"); } free(w); if(chdir("/mail/fs/mbox") < 0) return senderr("can't initialize upas/fs: %r"); if((fd = open(".", OREAD)) < 0) return senderr("cannot open /mail/fs/mbox: %r"); nd = dirreadall(fd, &d); close(fd); if(nd < 0) return senderr("cannot read from /mail/fs/mbox: %r"); msg = mallocz(sizeof(Msg)*nd, 1); if(msg == nil) return senderr("out of memory"); if(nd == 0) return 0; qsort(d, nd, sizeof(d[0]), dircmp); for(i=0; i<nd; i++){ m = &msg[nmsg]; m->upasnum = atoi(d[i].name); sprint(buf, "%d/digest", m->upasnum); if((fd = open(buf, OREAD)) < 0) continue; n = readn(fd, m->digest, sizeof m->digest - 1); close(fd); if(n < 0) continue; m->digest[n] = '\0'; /* * We need the number of message lines so that we * can adjust the byte count to include \r's. * Upas/fs gives us the number of lines in the raw body * in the lines file, but we have to count rawheader ourselves. * There is one blank line between raw header and raw body. */ sprint(buf, "%d/rawheader", m->upasnum); if((b = Bopen(buf, OREAD)) == nil) continue; lines = 0; for(;;){ p = Brdline(b, '\n'); if(p == nil){ if((n = Blinelen(b)) == 0) break; Bseek(b, n, 1); }else lines++; } Bterm(b); lines++; sprint(buf, "%d/lines", m->upasnum); if((fd = open(buf, OREAD)) < 0) continue; n = readn(fd, buf, sizeof buf - 1); close(fd); if(n < 0) continue; buf[n] = '\0'; lines += atoi(buf); sprint(buf, "%d/raw", m->upasnum); if((draw = dirstat(buf)) == nil) continue; m->bytes = lines+draw->length; free(draw); nmsg++; totalmsgs++; totalbytes += m->bytes; } return 0;}/* * get a line that ends in crnl or cr, turn terminating crnl into a nl * * return 0 on EOF */static intgetcrnl(char *buf, int n){ int c; char *ep; char *bp; Biobuf *fp = ∈ Bflush(&out); bp = buf; ep = bp + n - 1; while(bp != ep){ c = Bgetc(fp); if(debug) { seek(2, 0, 2); fprint(2, "%c", c); } switch(c){ case -1: *bp = 0; if(bp==buf) return 0; else return bp-buf; case '\r': c = Bgetc(fp); if(c == '\n'){ if(debug) { seek(2, 0, 2); fprint(2, "%c", c); } *bp = 0; return bp-buf; } Bungetc(fp); c = '\r'; break; case '\n': *bp = 0; return bp-buf; } *bp++ = c; } *bp = 0; return bp-buf;}static voidsendcrnl(char *fmt, ...){ char buf[1024]; va_list arg; va_start(arg, fmt); vseprint(buf, buf+sizeof(buf), fmt, arg); va_end(arg); if(debug) fprint(2, "-> %s\n", buf); Bprint(&out, "%s\r\n", buf);}static intsenderr(char *fmt, ...){ char buf[1024]; va_list arg; va_start(arg, fmt); vseprint(buf, buf+sizeof(buf), fmt, arg); va_end(arg); if(debug) fprint(2, "-> -ERR %s\n", buf); Bprint(&out, "-ERR %s\r\n", buf); return -1;}static intsendok(char *fmt, ...){ char buf[1024]; va_list arg; va_start(arg, fmt); vseprint(buf, buf+sizeof(buf), fmt, arg); va_end(arg); if(*buf){ if(debug) fprint(2, "-> +OK %s\n", buf); Bprint(&out, "+OK %s\r\n", buf); } else { if(debug) fprint(2, "-> +OK\n"); Bprint(&out, "+OK\r\n"); } return 0;}static intcapacmd(char*){ sendok(""); sendcrnl("TOP"); if(passwordinclear || didtls) sendcrnl("USER"); sendcrnl("PIPELINING"); sendcrnl("UIDL"); sendcrnl("STLS"); sendcrnl("."); return 0;}static intdelecmd(char *arg){ int n; if(*arg==0) return senderr("DELE requires a message number"); n = atoi(arg)-1; if(n < 0 || n >= nmsg || msg[n].deleted) return senderr("no such message"); msg[n].deleted = 1; totalmsgs--; totalbytes -= msg[n].bytes; sendok("message %d deleted", n+1); return 0;}static intlistcmd(char *arg){ int i, n; if(*arg == 0){ sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes); for(i=0; i<nmsg; i++){ if(msg[i].deleted) continue; sendcrnl("%d %d", i+1, msg[i].bytes); } sendcrnl("."); }else{ n = atoi(arg)-1; if(n < 0 || n >= nmsg || msg[n].deleted) return senderr("no such message"); sendok("%d %d", n+1, msg[n].bytes); } return 0;}static intnoopcmd(char *arg){ USED(arg); sendok(""); return 0;}static void_synccmd(char*){ int i, fd; char *s; Fmt f; if(!loggedin){ sendok(""); return; } fmtstrinit(&f); fmtprint(&f, "delete mbox"); for(i=0; i<nmsg; i++) if(msg[i].deleted) fmtprint(&f, " %d", msg[i].upasnum); s = fmtstrflush(&f); if(strcmp(s, "delete mbox") != 0){ /* must have something to delete */ if((fd = open("../ctl", OWRITE)) < 0){ senderr("open ctl to delete messages: %r"); return; } if(write(fd, s, strlen(s)) < 0){ senderr("error deleting messages: %r"); return; } } sendok("");}static intsynccmd(char*){ _synccmd(nil); return 0;}static intquitcmd(char*){ synccmd(nil); exits(nil); return 0;}static intretrcmd(char *arg){ int n; Biobuf *b; char buf[40], *p; if(*arg == 0) return senderr("RETR requires a message number"); n = atoi(arg)-1; if(n < 0 || n >= nmsg || msg[n].deleted) return senderr("no such message"); snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum); if((b = Bopen(buf, OREAD)) == nil) return senderr("message disappeared"); sendok(""); while((p = Brdstr(b, '\n', 1)) != nil){ if(p[0]=='.') Bwrite(&out, ".", 1); Bwrite(&out, p, strlen(p)); Bwrite(&out, "\r\n", 2); free(p); } Bterm(b); sendcrnl("."); return 0;}static intrsetcmd(char*){ int i; for(i=0; i<nmsg; i++){ if(msg[i].deleted){ msg[i].deleted = 0; totalmsgs++; totalbytes += msg[i].bytes; } } return sendok("");}static intstatcmd(char*){ return sendok("%d %d", totalmsgs, totalbytes);}static inttrace(char *fmt, ...){ va_list arg; int n; va_start(arg, fmt); n = vfprint(2, fmt, arg); va_end(arg); return n;}static intstlscmd(char*){ int fd; TLSconn conn; if(didtls) return senderr("tls already started"); if(!tlscert) return senderr("don't have any tls credentials"); sendok(""); Bflush(&out); memset(&conn, 0, sizeof conn); conn.cert = tlscert; conn.certlen = ntlscert; if(debug) conn.trace = trace; fd = tlsServer(0, &conn); if(fd < 0) sysfatal("tlsServer: %r"); dup(fd, 0); dup(fd, 1); close(fd); Binit(&in, 0, OREAD); Binit(&out, 1, OWRITE); didtls = 1; return 0;} static inttopcmd(char *arg){ int done, i, lines, n; char buf[40], *p; Biobuf *b; if(*arg == 0) return senderr("TOP requires a message number"); n = atoi(arg)-1; if(n < 0 || n >= nmsg || msg[n].deleted) return senderr("no such message"); arg = nextarg(arg); if(*arg == 0) return senderr("TOP requires a line count"); lines = atoi(arg); if(lines < 0) return senderr("bad args to TOP"); snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum); if((b = Bopen(buf, OREAD)) == nil) return senderr("message disappeared"); sendok(""); while(p = Brdstr(b, '\n', 1)){ if(p[0]=='.') Bputc(&out, '.'); Bwrite(&out, p, strlen(p)); Bwrite(&out, "\r\n", 2); done = p[0]=='\0'; free(p); if(done) break; } for(i=0; i<lines; i++){ p = Brdstr(b, '\n', 1); if(p == nil) break; if(p[0]=='.') Bwrite(&out, ".", 1); Bwrite(&out, p, strlen(p)); Bwrite(&out, "\r\n", 2); free(p); } sendcrnl("."); Bterm(b); return 0;}static intuidlcmd(char *arg){ int n; if(*arg==0){ sendok(""); for(n=0; n<nmsg; n++){ if(msg[n].deleted) continue; sendcrnl("%d %s", n+1, msg[n].digest); } sendcrnl("."); }else{ n = atoi(arg)-1; if(n < 0 || n >= nmsg || msg[n].deleted) return senderr("no such message"); sendok("%d %s", n+1, msg[n].digest); } return 0; }static char*nextarg(char *p){ while(*p && *p != ' ' && *p != '\t') p++; while(*p == ' ' || *p == '\t') *p++ = 0; return p;}/* * authentication */Chalstate *chs;char user[256];char box[256];char cbox[256];static voidhello(void){ fmtinstall('H', encodefmt); if((chs = auth_challenge("proto=apop role=server")) == nil){ senderr("auth server not responding, try later"); exits(nil); } sendok("POP3 server ready %s", chs->chal);}static intsetuser(char *arg){ char *p; strcpy(box, "/mail/box/"); strecpy(box+strlen(box), box+sizeof box-7, arg); strcpy(cbox, box); cleanname(cbox); if(strcmp(cbox, box) != 0) return senderr("bad mailbox name"); strcat(box, "/mbox"); strecpy(user, user+sizeof user, arg); if(p = strchr(user, '/')) *p = '\0'; return 0;}static intusercmd(char *arg){ if(loggedin) return senderr("already authenticated"); if(*arg == 0) return senderr("USER requires argument"); if(setuser(arg) < 0) return -1; return sendok("");}static voidenableaddr(void){ int fd; char buf[64]; /* hide the peer IP address under a rock in the ratifier FS */ if(peeraddr == 0 || *peeraddr == 0) return; sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr); /* * if the address is already there and the user owns it, * remove it and recreate it to give him a new time quanta. */ if(access(buf, 0) >= 0 && remove(buf) < 0) return; fd = create(buf, OREAD, 0666); if(fd >= 0){ close(fd);// syslog(0, "pop3", "ratified %s", peeraddr); }}static intdologin(char *response){ AuthInfo *ai; static int tries; chs->user = user; chs->resp = response; chs->nresp = strlen(response); if((ai = auth_response(chs)) == nil){ if(tries++ >= 5){ senderr("authentication failed: %r; server exiting"); exits(nil); } return senderr("authentication failed"); } if(auth_chuid(ai, nil) < 0){ senderr("chuid failed: %r; server exiting"); exits(nil); } auth_freeAI(ai); auth_freechal(chs); chs = nil; loggedin = 1; if(newns(user, 0) < 0){ senderr("newns failed: %r; server exiting"); exits(nil); } enableaddr(); if(readmbox(box) < 0) exits(nil); return sendok("mailbox is %s", box);}static intpasscmd(char *arg){ DigestState *s; uchar digest[MD5dlen]; char response[2*MD5dlen+1]; if(passwordinclear==0 && didtls==0) return senderr("password in the clear disallowed"); /* use password to encode challenge */ if((chs = auth_challenge("proto=apop role=server")) == nil) return senderr("couldn't get apop challenge"); // hash challenge with secret and convert to ascii s = md5((uchar*)chs->chal, chs->nchal, 0, 0); md5((uchar*)arg, strlen(arg), digest, s); snprint(response, sizeof response, "%.*H", MD5dlen, digest); return dologin(response);}static intapopcmd(char *arg){ char *resp; resp = nextarg(arg); if(setuser(arg) < 0) return -1; return dologin(resp);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -