URI:
       tpop3.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tpop3.c (14326B)
       ---
            1 #include "common.h"
            2 #include <ctype.h>
            3 #include <auth.h>
            4 #include <libsec.h>
            5 
            6 typedef struct Cmd Cmd;
            7 struct Cmd
            8 {
            9         char *name;
           10         int needauth;
           11         int (*f)(char*);
           12 };
           13 
           14 static void hello(void);
           15 static int apopcmd(char*);
           16 static int capacmd(char*);
           17 static int delecmd(char*);
           18 static int listcmd(char*);
           19 static int noopcmd(char*);
           20 static int passcmd(char*);
           21 static int quitcmd(char*);
           22 static int rsetcmd(char*);
           23 static int retrcmd(char*);
           24 static int statcmd(char*);
           25 static int stlscmd(char*);
           26 static int topcmd(char*);
           27 static int synccmd(char*);
           28 static int uidlcmd(char*);
           29 static int usercmd(char*);
           30 static char *nextarg(char*);
           31 static int getcrnl(char*, int);
           32 static int readmbox(char*);
           33 static void sendcrnl(char*, ...);
           34 static int senderr(char*, ...);
           35 static int sendok(char*, ...);
           36 #pragma varargck argpos sendcrnl 1
           37 #pragma varargck argpos senderr 1
           38 #pragma varargck argpos sendok 1
           39 
           40 Cmd cmdtab[] =
           41 {
           42         "apop", 0, apopcmd,
           43         "capa", 0, capacmd,
           44         "dele", 1, delecmd,
           45         "list", 1, listcmd,
           46         "noop", 0, noopcmd,
           47         "pass", 0, passcmd,
           48         "quit", 0, quitcmd,
           49         "rset", 0, rsetcmd,
           50         "retr", 1, retrcmd,
           51         "stat", 1, statcmd,
           52         "stls", 0, stlscmd,
           53         "sync", 1, synccmd,
           54         "top", 1, topcmd,
           55         "uidl", 1, uidlcmd,
           56         "user", 0, usercmd,
           57         0, 0, 0
           58 };
           59 
           60 static Biobuf in;
           61 static Biobuf out;
           62 static int passwordinclear;
           63 static int didtls;
           64 
           65 typedef struct Msg Msg;
           66 struct Msg
           67 {
           68         int upasnum;
           69         char digest[64];
           70         int bytes;
           71         int deleted;
           72 };
           73 
           74 static int totalbytes;
           75 static int totalmsgs;
           76 static Msg *msg;
           77 static int nmsg;
           78 static int loggedin;
           79 static int debug;
           80 static uchar *tlscert;
           81 static int ntlscert;
           82 static char *peeraddr;
           83 static char tmpaddr[64];
           84 
           85 void
           86 usage(void)
           87 {
           88         fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n");
           89         exits("usage");
           90 }
           91 
           92 void
           93 main(int argc, char **argv)
           94 {
           95         int fd;
           96         char *arg, cmdbuf[1024];
           97         Cmd *c;
           98 
           99         rfork(RFNAMEG);
          100         Binit(&in, 0, OREAD);
          101         Binit(&out, 1, OWRITE);
          102 
          103         ARGBEGIN{
          104         case 'a':
          105                 loggedin = 1;
          106                 if(readmbox(EARGF(usage())) < 0)
          107                         exits(nil);
          108                 break;
          109         case 'd':
          110                 debug++;
          111                 if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
          112                         dup(fd, 2);
          113                         close(fd);
          114                 }
          115                 break;
          116         case 'r':
          117                 strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
          118                 if(arg = strchr(tmpaddr, '!'))
          119                         *arg = '\0';
          120                 peeraddr = tmpaddr;
          121                 break;
          122         case 't':
          123                 tlscert = readcert(EARGF(usage()), &ntlscert);
          124                 if(tlscert == nil){
          125                         senderr("cannot read TLS certificate: %r");
          126                         exits(nil);
          127                 }
          128                 break;
          129         case 'p':
          130                 passwordinclear = 1;
          131                 break;
          132         }ARGEND
          133 
          134         /* do before TLS */
          135         if(peeraddr == nil)
          136                 peeraddr = remoteaddr(0,0);
          137 
          138         hello();
          139 
          140         while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
          141                 arg = nextarg(cmdbuf);
          142                 for(c=cmdtab; c->name; c++)
          143                         if(cistrcmp(c->name, cmdbuf) == 0)
          144                                 break;
          145                 if(c->name == 0){
          146                         senderr("unknown command %s", cmdbuf);
          147                         continue;
          148                 }
          149                 if(c->needauth && !loggedin){
          150                         senderr("%s requires authentication", cmdbuf);
          151                         continue;
          152                 }
          153                 (*c->f)(arg);
          154         }
          155         exits(nil);
          156 }
          157 
          158 /* sort directories in increasing message number order */
          159 static int
          160 dircmp(void *a, void *b)
          161 {
          162         return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
          163 }
          164 
          165 static int
          166 readmbox(char *box)
          167 {
          168         int fd, i, n, nd, lines, pid;
          169         char buf[100], err[ERRMAX];
          170         char *p;
          171         Biobuf *b;
          172         Dir *d, *draw;
          173         Msg *m;
          174         Waitmsg *w;
          175 
          176         unmount(nil, "/mail/fs");
          177         switch(pid = fork()){
          178         case -1:
          179                 return senderr("can't fork to start upas/fs");
          180 
          181         case 0:
          182                 close(0);
          183                 close(1);
          184                 open("/dev/null", OREAD);
          185                 open("/dev/null", OWRITE);
          186                 execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
          187                 snprint(err, sizeof err, "upas/fs: %r");
          188                 _exits(err);
          189                 break;
          190 
          191         default:
          192                 break;
          193         }
          194 
          195         if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
          196                 if(w && w->pid==pid)
          197                         return senderr("%s", w->msg);
          198                 else
          199                         return senderr("can't initialize upas/fs");
          200         }
          201         free(w);
          202 
          203         if(chdir("/mail/fs/mbox") < 0)
          204                 return senderr("can't initialize upas/fs: %r");
          205 
          206         if((fd = open(".", OREAD)) < 0)
          207                 return senderr("cannot open /mail/fs/mbox: %r");
          208         nd = dirreadall(fd, &d);
          209         close(fd);
          210         if(nd < 0)
          211                 return senderr("cannot read from /mail/fs/mbox: %r");
          212 
          213         msg = mallocz(sizeof(Msg)*nd, 1);
          214         if(msg == nil)
          215                 return senderr("out of memory");
          216 
          217         if(nd == 0)
          218                 return 0;
          219         qsort(d, nd, sizeof(d[0]), dircmp);
          220 
          221         for(i=0; i<nd; i++){
          222                 m = &msg[nmsg];
          223                 m->upasnum = atoi(d[i].name);
          224                 sprint(buf, "%d/digest", m->upasnum);
          225                 if((fd = open(buf, OREAD)) < 0)
          226                         continue;
          227                 n = readn(fd, m->digest, sizeof m->digest - 1);
          228                 close(fd);
          229                 if(n < 0)
          230                         continue;
          231                 m->digest[n] = '\0';
          232 
          233                 /*
          234                  * We need the number of message lines so that we
          235                  * can adjust the byte count to include \r's.
          236                  * Upas/fs gives us the number of lines in the raw body
          237                  * in the lines file, but we have to count rawheader ourselves.
          238                  * There is one blank line between raw header and raw body.
          239                  */
          240                 sprint(buf, "%d/rawheader", m->upasnum);
          241                 if((b = Bopen(buf, OREAD)) == nil)
          242                         continue;
          243                 lines = 0;
          244                 for(;;){
          245                         p = Brdline(b, '\n');
          246                         if(p == nil){
          247                                 if((n = Blinelen(b)) == 0)
          248                                         break;
          249                                 Bseek(b, n, 1);
          250                         }else
          251                                 lines++;
          252                 }
          253                 Bterm(b);
          254                 lines++;
          255                 sprint(buf, "%d/lines", m->upasnum);
          256                 if((fd = open(buf, OREAD)) < 0)
          257                         continue;
          258                 n = readn(fd, buf, sizeof buf - 1);
          259                 close(fd);
          260                 if(n < 0)
          261                         continue;
          262                 buf[n] = '\0';
          263                 lines += atoi(buf);
          264 
          265                 sprint(buf, "%d/raw", m->upasnum);
          266                 if((draw = dirstat(buf)) == nil)
          267                         continue;
          268                 m->bytes = lines+draw->length;
          269                 free(draw);
          270                 nmsg++;
          271                 totalmsgs++;
          272                 totalbytes += m->bytes;
          273         }
          274         return 0;
          275 }
          276 
          277 /*
          278  *  get a line that ends in crnl or cr, turn terminating crnl into a nl
          279  *
          280  *  return 0 on EOF
          281  */
          282 static int
          283 getcrnl(char *buf, int n)
          284 {
          285         int c;
          286         char *ep;
          287         char *bp;
          288         Biobuf *fp = &in;
          289 
          290         Bflush(&out);
          291 
          292         bp = buf;
          293         ep = bp + n - 1;
          294         while(bp != ep){
          295                 c = Bgetc(fp);
          296                 if(debug) {
          297                         seek(2, 0, 2);
          298                         fprint(2, "%c", c);
          299                 }
          300                 switch(c){
          301                 case -1:
          302                         *bp = 0;
          303                         if(bp==buf)
          304                                 return 0;
          305                         else
          306                                 return bp-buf;
          307                 case '\r':
          308                         c = Bgetc(fp);
          309                         if(c == '\n'){
          310                                 if(debug) {
          311                                         seek(2, 0, 2);
          312                                         fprint(2, "%c", c);
          313                                 }
          314                                 *bp = 0;
          315                                 return bp-buf;
          316                         }
          317                         Bungetc(fp);
          318                         c = '\r';
          319                         break;
          320                 case '\n':
          321                         *bp = 0;
          322                         return bp-buf;
          323                 }
          324                 *bp++ = c;
          325         }
          326         *bp = 0;
          327         return bp-buf;
          328 }
          329 
          330 static void
          331 sendcrnl(char *fmt, ...)
          332 {
          333         char buf[1024];
          334         va_list arg;
          335 
          336         va_start(arg, fmt);
          337         vseprint(buf, buf+sizeof(buf), fmt, arg);
          338         va_end(arg);
          339         if(debug)
          340                 fprint(2, "-> %s\n", buf);
          341         Bprint(&out, "%s\r\n", buf);
          342 }
          343 
          344 static int
          345 senderr(char *fmt, ...)
          346 {
          347         char buf[1024];
          348         va_list arg;
          349 
          350         va_start(arg, fmt);
          351         vseprint(buf, buf+sizeof(buf), fmt, arg);
          352         va_end(arg);
          353         if(debug)
          354                 fprint(2, "-> -ERR %s\n", buf);
          355         Bprint(&out, "-ERR %s\r\n", buf);
          356         return -1;
          357 }
          358 
          359 static int
          360 sendok(char *fmt, ...)
          361 {
          362         char buf[1024];
          363         va_list arg;
          364 
          365         va_start(arg, fmt);
          366         vseprint(buf, buf+sizeof(buf), fmt, arg);
          367         va_end(arg);
          368         if(*buf){
          369                 if(debug)
          370                         fprint(2, "-> +OK %s\n", buf);
          371                 Bprint(&out, "+OK %s\r\n", buf);
          372         } else {
          373                 if(debug)
          374                         fprint(2, "-> +OK\n");
          375                 Bprint(&out, "+OK\r\n");
          376         }
          377         return 0;
          378 }
          379 
          380 static int
          381 capacmd(char*)
          382 {
          383         sendok("");
          384         sendcrnl("TOP");
          385         if(passwordinclear || didtls)
          386                 sendcrnl("USER");
          387         sendcrnl("PIPELINING");
          388         sendcrnl("UIDL");
          389         sendcrnl("STLS");
          390         sendcrnl(".");
          391         return 0;
          392 }
          393 
          394 static int
          395 delecmd(char *arg)
          396 {
          397         int n;
          398 
          399         if(*arg==0)
          400                 return senderr("DELE requires a message number");
          401 
          402         n = atoi(arg)-1;
          403         if(n < 0 || n >= nmsg || msg[n].deleted)
          404                 return senderr("no such message");
          405 
          406         msg[n].deleted = 1;
          407         totalmsgs--;
          408         totalbytes -= msg[n].bytes;
          409         sendok("message %d deleted", n+1);
          410         return 0;
          411 }
          412 
          413 static int
          414 listcmd(char *arg)
          415 {
          416         int i, n;
          417 
          418         if(*arg == 0){
          419                 sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
          420                 for(i=0; i<nmsg; i++){
          421                         if(msg[i].deleted)
          422                                 continue;
          423                         sendcrnl("%d %d", i+1, msg[i].bytes);
          424                 }
          425                 sendcrnl(".");
          426         }else{
          427                 n = atoi(arg)-1;
          428                 if(n < 0 || n >= nmsg || msg[n].deleted)
          429                         return senderr("no such message");
          430                 sendok("%d %d", n+1, msg[n].bytes);
          431         }
          432         return 0;
          433 }
          434 
          435 static int
          436 noopcmd(char *arg)
          437 {
          438         USED(arg);
          439         sendok("");
          440         return 0;
          441 }
          442 
          443 static void
          444 _synccmd(char*)
          445 {
          446         int i, fd;
          447         char *s;
          448         Fmt f;
          449 
          450         if(!loggedin){
          451                 sendok("");
          452                 return;
          453         }
          454 
          455         fmtstrinit(&f);
          456         fmtprint(&f, "delete mbox");
          457         for(i=0; i<nmsg; i++)
          458                 if(msg[i].deleted)
          459                         fmtprint(&f, " %d", msg[i].upasnum);
          460         s = fmtstrflush(&f);
          461         if(strcmp(s, "delete mbox") != 0){        /* must have something to delete */
          462                 if((fd = open("../ctl", OWRITE)) < 0){
          463                         senderr("open ctl to delete messages: %r");
          464                         return;
          465                 }
          466                 if(write(fd, s, strlen(s)) < 0){
          467                         senderr("error deleting messages: %r");
          468                         return;
          469                 }
          470         }
          471         sendok("");
          472 }
          473 
          474 static int
          475 synccmd(char*)
          476 {
          477         _synccmd(nil);
          478         return 0;
          479 }
          480 
          481 static int
          482 quitcmd(char*)
          483 {
          484         synccmd(nil);
          485         exits(nil);
          486         return 0;
          487 }
          488 
          489 static int
          490 retrcmd(char *arg)
          491 {
          492         int n;
          493         Biobuf *b;
          494         char buf[40], *p;
          495 
          496         if(*arg == 0)
          497                 return senderr("RETR requires a message number");
          498         n = atoi(arg)-1;
          499         if(n < 0 || n >= nmsg || msg[n].deleted)
          500                 return senderr("no such message");
          501         snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
          502         if((b = Bopen(buf, OREAD)) == nil)
          503                 return senderr("message disappeared");
          504         sendok("");
          505         while((p = Brdstr(b, '\n', 1)) != nil){
          506                 if(p[0]=='.')
          507                         Bwrite(&out, ".", 1);
          508                 Bwrite(&out, p, strlen(p));
          509                 Bwrite(&out, "\r\n", 2);
          510                 free(p);
          511         }
          512         Bterm(b);
          513         sendcrnl(".");
          514         return 0;
          515 }
          516 
          517 static int
          518 rsetcmd(char*)
          519 {
          520         int i;
          521 
          522         for(i=0; i<nmsg; i++){
          523                 if(msg[i].deleted){
          524                         msg[i].deleted = 0;
          525                         totalmsgs++;
          526                         totalbytes += msg[i].bytes;
          527                 }
          528         }
          529         return sendok("");
          530 }
          531 
          532 static int
          533 statcmd(char*)
          534 {
          535         return sendok("%d %d", totalmsgs, totalbytes);
          536 }
          537 
          538 static int
          539 trace(char *fmt, ...)
          540 {
          541         va_list arg;
          542         int n;
          543 
          544         va_start(arg, fmt);
          545         n = vfprint(2, fmt, arg);
          546         va_end(arg);
          547         return n;
          548 }
          549 
          550 static int
          551 stlscmd(char*)
          552 {
          553         int fd;
          554         TLSconn conn;
          555 
          556         if(didtls)
          557                 return senderr("tls already started");
          558         if(!tlscert)
          559                 return senderr("don't have any tls credentials");
          560         sendok("");
          561         Bflush(&out);
          562 
          563         memset(&conn, 0, sizeof conn);
          564         conn.cert = tlscert;
          565         conn.certlen = ntlscert;
          566         if(debug)
          567                 conn.trace = trace;
          568         fd = tlsServer(0, &conn);
          569         if(fd < 0)
          570                 sysfatal("tlsServer: %r");
          571         dup(fd, 0);
          572         dup(fd, 1);
          573         close(fd);
          574         Binit(&in, 0, OREAD);
          575         Binit(&out, 1, OWRITE);
          576         didtls = 1;
          577         return 0;
          578 }
          579 
          580 static int
          581 topcmd(char *arg)
          582 {
          583         int done, i, lines, n;
          584         char buf[40], *p;
          585         Biobuf *b;
          586 
          587         if(*arg == 0)
          588                 return senderr("TOP requires a message number");
          589         n = atoi(arg)-1;
          590         if(n < 0 || n >= nmsg || msg[n].deleted)
          591                 return senderr("no such message");
          592         arg = nextarg(arg);
          593         if(*arg == 0)
          594                 return senderr("TOP requires a line count");
          595         lines = atoi(arg);
          596         if(lines < 0)
          597                 return senderr("bad args to TOP");
          598         snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
          599         if((b = Bopen(buf, OREAD)) == nil)
          600                 return senderr("message disappeared");
          601         sendok("");
          602         while(p = Brdstr(b, '\n', 1)){
          603                 if(p[0]=='.')
          604                         Bputc(&out, '.');
          605                 Bwrite(&out, p, strlen(p));
          606                 Bwrite(&out, "\r\n", 2);
          607                 done = p[0]=='\0';
          608                 free(p);
          609                 if(done)
          610                         break;
          611         }
          612         for(i=0; i<lines; i++){
          613                 p = Brdstr(b, '\n', 1);
          614                 if(p == nil)
          615                         break;
          616                 if(p[0]=='.')
          617                         Bwrite(&out, ".", 1);
          618                 Bwrite(&out, p, strlen(p));
          619                 Bwrite(&out, "\r\n", 2);
          620                 free(p);
          621         }
          622         sendcrnl(".");
          623         Bterm(b);
          624         return 0;
          625 }
          626 
          627 static int
          628 uidlcmd(char *arg)
          629 {
          630         int n;
          631 
          632         if(*arg==0){
          633                 sendok("");
          634                 for(n=0; n<nmsg; n++){
          635                         if(msg[n].deleted)
          636                                 continue;
          637                         sendcrnl("%d %s", n+1, msg[n].digest);
          638                 }
          639                 sendcrnl(".");
          640         }else{
          641                 n = atoi(arg)-1;
          642                 if(n < 0 || n >= nmsg || msg[n].deleted)
          643                         return senderr("no such message");
          644                 sendok("%d %s", n+1, msg[n].digest);
          645         }
          646         return 0;
          647 }
          648 
          649 static char*
          650 nextarg(char *p)
          651 {
          652         while(*p && *p != ' ' && *p != '\t')
          653                 p++;
          654         while(*p == ' ' || *p == '\t')
          655                 *p++ = 0;
          656         return p;
          657 }
          658 
          659 /*
          660  * authentication
          661  */
          662 Chalstate *chs;
          663 char user[256];
          664 char box[256];
          665 char cbox[256];
          666 
          667 static void
          668 hello(void)
          669 {
          670         fmtinstall('H', encodefmt);
          671         if((chs = auth_challenge("proto=apop role=server")) == nil){
          672                 senderr("auth server not responding, try later");
          673                 exits(nil);
          674         }
          675 
          676         sendok("POP3 server ready %s", chs->chal);
          677 }
          678 
          679 static int
          680 setuser(char *arg)
          681 {
          682         char *p;
          683 
          684         strcpy(box, "/mail/box/");
          685         strecpy(box+strlen(box), box+sizeof box-7, arg);
          686         strcpy(cbox, box);
          687         cleanname(cbox);
          688         if(strcmp(cbox, box) != 0)
          689                 return senderr("bad mailbox name");
          690         strcat(box, "/mbox");
          691 
          692         strecpy(user, user+sizeof user, arg);
          693         if(p = strchr(user, '/'))
          694                 *p = '\0';
          695         return 0;
          696 }
          697 
          698 static int
          699 usercmd(char *arg)
          700 {
          701         if(loggedin)
          702                 return senderr("already authenticated");
          703         if(*arg == 0)
          704                 return senderr("USER requires argument");
          705         if(setuser(arg) < 0)
          706                 return -1;
          707         return sendok("");
          708 }
          709 
          710 static void
          711 enableaddr(void)
          712 {
          713         int fd;
          714         char buf[64];
          715 
          716         /* hide the peer IP address under a rock in the ratifier FS */
          717         if(peeraddr == 0 || *peeraddr == 0)
          718                 return;
          719 
          720         sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
          721 
          722         /*
          723          * if the address is already there and the user owns it,
          724          * remove it and recreate it to give him a new time quanta.
          725          */
          726         if(access(buf, 0) >= 0  && remove(buf) < 0)
          727                 return;
          728 
          729         fd = create(buf, OREAD, 0666);
          730         if(fd >= 0){
          731                 close(fd);
          732 /*                syslog(0, "pop3", "ratified %s", peeraddr); */
          733         }
          734 }
          735 
          736 static int
          737 dologin(char *response)
          738 {
          739         AuthInfo *ai;
          740         static int tries;
          741 
          742         chs->user = user;
          743         chs->resp = response;
          744         chs->nresp = strlen(response);
          745         if((ai = auth_response(chs)) == nil){
          746                 if(tries++ >= 5){
          747                         senderr("authentication failed: %r; server exiting");
          748                         exits(nil);
          749                 }
          750                 return senderr("authentication failed");
          751         }
          752 
          753         if(auth_chuid(ai, nil) < 0){
          754                 senderr("chuid failed: %r; server exiting");
          755                 exits(nil);
          756         }
          757         auth_freeAI(ai);
          758         auth_freechal(chs);
          759         chs = nil;
          760 
          761         loggedin = 1;
          762         if(newns(user, 0) < 0){
          763                 senderr("newns failed: %r; server exiting");
          764                 exits(nil);
          765         }
          766 
          767         enableaddr();
          768         if(readmbox(box) < 0)
          769                 exits(nil);
          770         return sendok("mailbox is %s", box);
          771 }
          772 
          773 static int
          774 passcmd(char *arg)
          775 {
          776         DigestState *s;
          777         uchar digest[MD5dlen];
          778         char response[2*MD5dlen+1];
          779 
          780         if(passwordinclear==0 && didtls==0)
          781                 return senderr("password in the clear disallowed");
          782 
          783         /* use password to encode challenge */
          784         if((chs = auth_challenge("proto=apop role=server")) == nil)
          785                 return senderr("couldn't get apop challenge");
          786 
          787         /* hash challenge with secret and convert to ascii */
          788         s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
          789         md5((uchar*)arg, strlen(arg), digest, s);
          790         snprint(response, sizeof response, "%.*H", MD5dlen, digest);
          791         return dologin(response);
          792 }
          793 
          794 static int
          795 apopcmd(char *arg)
          796 {
          797         char *resp;
          798 
          799         resp = nextarg(arg);
          800         if(setuser(arg) < 0)
          801                 return -1;
          802         return dologin(resp);
          803 }