tpop3.c - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tpop3.c (13496B)
       ---
            1 #include "common.h"
            2 #include <ctype.h>
            3 #include <plumb.h>
            4 #include <libsec.h>
            5 #include <auth.h>
            6 #include <thread.h>
            7 #include "dat.h"
            8 
            9 #pragma varargck type "M" uchar*
           10 #pragma varargck argpos pop3cmd 2
           11 
           12 typedef struct Pop Pop;
           13 struct Pop {
           14         char *freep;        /* free this to free the strings below */
           15 
           16         char *host;
           17         char *user;
           18         char *port;
           19 
           20         int ppop;
           21         int refreshtime;
           22         int debug;
           23         int pipeline;
           24         int encrypted;
           25         int needtls;
           26         int notls;
           27         int needssl;
           28 
           29         /* open network connection */
           30         Biobuf bin;
           31         Biobuf bout;
           32         int fd;
           33         char *lastline;        /* from Brdstr */
           34 
           35         Thumbprint *thumb;
           36 };
           37 
           38 char*
           39 geterrstr(void)
           40 {
           41         static char err[64];
           42 
           43         err[0] = '\0';
           44         errstr(err, sizeof(err));
           45         return err;
           46 }
           47 
           48 /* */
           49 /* get pop3 response line , without worrying */
           50 /* about multiline responses; the clients */
           51 /* will deal with that. */
           52 /* */
           53 static int
           54 isokay(char *s)
           55 {
           56         return s!=nil && strncmp(s, "+OK", 3)==0;
           57 }
           58 
           59 static void
           60 pop3cmd(Pop *pop, char *fmt, ...)
           61 {
           62         char buf[128], *p;
           63         va_list va;
           64 
           65         va_start(va, fmt);
           66         vseprint(buf, buf+sizeof(buf), fmt, va);
           67         va_end(va);
           68 
           69         p = buf+strlen(buf);
           70         if(p > (buf+sizeof(buf)-3))
           71                 sysfatal("pop3 command too long");
           72 
           73         if(pop->debug)
           74                 fprint(2, "<- %s\n", buf);
           75         strcpy(p, "\r\n");
           76         Bwrite(&pop->bout, buf, strlen(buf));
           77         Bflush(&pop->bout);
           78 }
           79 
           80 static char*
           81 pop3resp(Pop *pop)
           82 {
           83         char *s;
           84         char *p;
           85 
           86         alarm(60*1000);
           87         if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
           88                 close(pop->fd);
           89                 pop->fd = -1;
           90                 alarm(0);
           91                 return "unexpected eof";
           92         }
           93         alarm(0);
           94 
           95         p = s+strlen(s)-1;
           96         while(p >= s && (*p == '\r' || *p == '\n'))
           97                 *p-- = '\0';
           98 
           99         if(pop->debug)
          100                 fprint(2, "-> %s\n", s);
          101         free(pop->lastline);
          102         pop->lastline = s;
          103         return s;
          104 }
          105 
          106 #if 0 /* jpc */
          107 static int
          108 pop3log(char *fmt, ...)
          109 {
          110         va_list ap;
          111 
          112         va_start(ap,fmt);
          113         syslog(0, "/sys/log/pop3", fmt, ap);
          114         va_end(ap);
          115         return 0;
          116 }
          117 #endif
          118 
          119 static char*
          120 pop3pushtls(Pop *pop)
          121 {
          122         int fd;
          123         uchar digest[SHA1dlen];
          124         TLSconn conn;
          125 
          126         memset(&conn, 0, sizeof conn);
          127         /* conn.trace = pop3log; */
          128         fd = tlsClient(pop->fd, &conn);
          129         if(fd < 0)
          130                 return "tls error";
          131         if(conn.cert==nil || conn.certlen <= 0){
          132                 close(fd);
          133                 return "server did not provide TLS certificate";
          134         }
          135         sha1(conn.cert, conn.certlen, digest, nil);
          136         if(!pop->thumb || !okThumbprint(digest, pop->thumb)){
          137                 fmtinstall('H', encodefmt);
          138                 close(fd);
          139                 free(conn.cert);
          140                 fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
          141                 return "bad server certificate";
          142         }
          143         free(conn.cert);
          144         close(pop->fd);
          145         pop->fd = fd;
          146         pop->encrypted = 1;
          147         Binit(&pop->bin, pop->fd, OREAD);
          148         Binit(&pop->bout, pop->fd, OWRITE);
          149         return nil;
          150 }
          151 
          152 /* */
          153 /* get capability list, possibly start tls */
          154 /* */
          155 static char*
          156 pop3capa(Pop *pop)
          157 {
          158         char *s;
          159         int hastls;
          160 
          161         pop3cmd(pop, "CAPA");
          162         if(!isokay(pop3resp(pop)))
          163                 return nil;
          164 
          165         hastls = 0;
          166         for(;;){
          167                 s = pop3resp(pop);
          168                 if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
          169                         break;
          170                 if(strcmp(s, "STLS") == 0)
          171                         hastls = 1;
          172                 if(strcmp(s, "PIPELINING") == 0)
          173                         pop->pipeline = 1;
          174         }
          175 
          176         if(hastls && !pop->notls){
          177                 pop3cmd(pop, "STLS");
          178                 if(!isokay(s = pop3resp(pop)))
          179                         return s;
          180                 if((s = pop3pushtls(pop)) != nil)
          181                         return s;
          182         }
          183         return nil;
          184 }
          185 
          186 /* */
          187 /* log in using APOP if possible, password if allowed by user */
          188 /* */
          189 static char*
          190 pop3login(Pop *pop)
          191 {
          192         int n;
          193         char *s, *p, *q;
          194         char ubuf[128], user[128];
          195         char buf[500];
          196         UserPasswd *up;
          197 
          198         s = pop3resp(pop);
          199         if(!isokay(s))
          200                 return "error in initial handshake";
          201 
          202         if(pop->user)
          203                 snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
          204         else
          205                 ubuf[0] = '\0';
          206 
          207         /* look for apop banner */
          208         if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
          209                 *++q = '\0';
          210                 if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
          211                         pop->host, ubuf)) < 0)
          212                         return "factotum failed";
          213                 if(user[0]=='\0')
          214                         return "factotum did not return a user name";
          215 
          216                 if(s = pop3capa(pop))
          217                         return s;
          218 
          219                 pop3cmd(pop, "APOP %s %.*s", user, n, buf);
          220                 if(!isokay(s = pop3resp(pop)))
          221                         return s;
          222 
          223                 return nil;
          224         } else {
          225                 if(pop->ppop == 0)
          226                         return "no APOP hdr from server";
          227 
          228                 if(s = pop3capa(pop))
          229                         return s;
          230 
          231                 if(pop->needtls && !pop->encrypted)
          232                         return "could not negotiate TLS";
          233 
          234                 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=pop dom=%q%s",
          235                         pop->host, ubuf);
          236                 if(up == nil)
          237                         return "no usable keys found";
          238 
          239                 pop3cmd(pop, "USER %s", up->user);
          240                 if(!isokay(s = pop3resp(pop))){
          241                         free(up);
          242                         return s;
          243                 }
          244                 pop3cmd(pop, "PASS %s", up->passwd);
          245                 free(up);
          246                 if(!isokay(s = pop3resp(pop)))
          247                         return s;
          248 
          249                 return nil;
          250         }
          251 }
          252 
          253 /* */
          254 /* dial and handshake with pop server */
          255 /* */
          256 static char*
          257 pop3dial(Pop *pop)
          258 {
          259         char *err;
          260 
          261         if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
          262                 return geterrstr();
          263 
          264         if(pop->needssl){
          265                 if((err = pop3pushtls(pop)) != nil)
          266                         return err;
          267         }else{
          268                 Binit(&pop->bin, pop->fd, OREAD);
          269                 Binit(&pop->bout, pop->fd, OWRITE);
          270         }
          271 
          272         if(err = pop3login(pop)) {
          273                 close(pop->fd);
          274                 return err;
          275         }
          276 
          277         return nil;
          278 }
          279 
          280 /* */
          281 /* close connection */
          282 /* */
          283 static void
          284 pop3hangup(Pop *pop)
          285 {
          286         pop3cmd(pop, "QUIT");
          287         pop3resp(pop);
          288         close(pop->fd);
          289 }
          290 
          291 /* */
          292 /* download a single message */
          293 /* */
          294 static char*
          295 pop3download(Pop *pop, Message *m)
          296 {
          297         char *s, *f[3], *wp, *ep;
          298         char sdigest[SHA1dlen*2+1];
          299         int i, l, sz;
          300 
          301         if(!pop->pipeline)
          302                 pop3cmd(pop, "LIST %d", m->mesgno);
          303         if(!isokay(s = pop3resp(pop)))
          304                 return s;
          305 
          306         if(tokenize(s, f, 3) != 3)
          307                 return "syntax error in LIST response";
          308 
          309         if(atoi(f[1]) != m->mesgno)
          310                 return "out of sync with pop3 server";
          311 
          312         sz = atoi(f[2])+200;        /* 200 because the plan9 pop3 server lies */
          313         if(sz == 0)
          314                 return "invalid size in LIST response";
          315 
          316         m->start = wp = emalloc(sz+1);
          317         ep = wp+sz;
          318 
          319         if(!pop->pipeline)
          320                 pop3cmd(pop, "RETR %d", m->mesgno);
          321         if(!isokay(s = pop3resp(pop))) {
          322                 m->start = nil;
          323                 free(wp);
          324                 return s;
          325         }
          326 
          327         s = nil;
          328         while(wp <= ep) {
          329                 s = pop3resp(pop);
          330                 if(strcmp(s, "unexpected eof") == 0) {
          331                         free(m->start);
          332                         m->start = nil;
          333                         return "unexpected end of conversation";
          334                 }
          335                 if(strcmp(s, ".") == 0)
          336                         break;
          337 
          338                 l = strlen(s)+1;
          339                 if(s[0] == '.') {
          340                         s++;
          341                         l--;
          342                 }
          343                 /*
          344                  * grow by 10%/200bytes - some servers
          345                  *  lie about message sizes
          346                  */
          347                 if(wp+l > ep) {
          348                         int pos = wp - m->start;
          349                         sz += ((sz / 10) < 200)? 200: sz/10;
          350                         m->start = erealloc(m->start, sz+1);
          351                         wp = m->start+pos;
          352                         ep = m->start+sz;
          353                 }
          354                 memmove(wp, s, l-1);
          355                 wp[l-1] = '\n';
          356                 wp += l;
          357         }
          358 
          359         if(s == nil || strcmp(s, ".") != 0)
          360                 return "out of sync with pop3 server";
          361 
          362         m->end = wp;
          363 
          364         /* make sure there's a trailing null */
          365         /* (helps in body searches) */
          366         *m->end = 0;
          367         m->bend = m->rbend = m->end;
          368         m->header = m->start;
          369 
          370         /* digest message */
          371         sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
          372         for(i = 0; i < SHA1dlen; i++)
          373                 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
          374         m->sdigest = s_copy(sdigest);
          375 
          376         return nil;
          377 }
          378 
          379 /* */
          380 /* check for new messages on pop server */
          381 /* UIDL is not required by RFC 1939, but  */
          382 /* netscape requires it, so almost every server supports it. */
          383 /* we'll use it to make our lives easier. */
          384 /* */
          385 static char*
          386 pop3read(Pop *pop, Mailbox *mb, int doplumb)
          387 {
          388         char *s, *p, *uidl, *f[2];
          389         int mesgno, ignore, nnew;
          390         Message *m, *next, **l;
          391 
          392         /* Some POP servers disallow UIDL if the maildrop is empty. */
          393         pop3cmd(pop, "STAT");
          394         if(!isokay(s = pop3resp(pop)))
          395                 return s;
          396 
          397         /* fetch message listing; note messages to grab */
          398         l = &mb->root->part;
          399         if(strncmp(s, "+OK 0 ", 6) != 0) {
          400                 pop3cmd(pop, "UIDL");
          401                 if(!isokay(s = pop3resp(pop)))
          402                         return s;
          403 
          404                 for(;;){
          405                         p = pop3resp(pop);
          406                         if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
          407                                 break;
          408 
          409                         if(tokenize(p, f, 2) != 2)
          410                                 continue;
          411 
          412                         mesgno = atoi(f[0]);
          413                         uidl = f[1];
          414                         if(strlen(uidl) > 75)        /* RFC 1939 says 70 characters max */
          415                                 continue;
          416 
          417                         ignore = 0;
          418                         while(*l != nil) {
          419                                 if(strcmp((*l)->uidl, uidl) == 0) {
          420                                         /* matches mail we already have, note mesgno for deletion */
          421                                         (*l)->mesgno = mesgno;
          422                                         ignore = 1;
          423                                         l = &(*l)->next;
          424                                         break;
          425                                 } else {
          426                                         /* old mail no longer in box mark deleted */
          427                                         if(doplumb)
          428                                                 mailplumb(mb, *l, 1);
          429                                         (*l)->inmbox = 0;
          430                                         (*l)->deleted = 1;
          431                                         l = &(*l)->next;
          432                                 }
          433                         }
          434                         if(ignore)
          435                                 continue;
          436 
          437                         m = newmessage(mb->root);
          438                         m->mallocd = 1;
          439                         m->inmbox = 1;
          440                         m->mesgno = mesgno;
          441                         strcpy(m->uidl, uidl);
          442 
          443                         /* chain in; will fill in message later */
          444                         *l = m;
          445                         l = &m->next;
          446                 }
          447         }
          448 
          449         /* whatever is left has been removed from the mbox, mark as deleted */
          450         while(*l != nil) {
          451                 if(doplumb)
          452                         mailplumb(mb, *l, 1);
          453                 (*l)->inmbox = 0;
          454                 (*l)->deleted = 1;
          455                 l = &(*l)->next;
          456         }
          457 
          458         /* download new messages */
          459         nnew = 0;
          460         if(pop->pipeline){
          461                 switch(rfork(RFPROC|RFMEM)){
          462                 case -1:
          463                         fprint(2, "rfork: %r\n");
          464                         pop->pipeline = 0;
          465 
          466                 default:
          467                         break;
          468 
          469                 case 0:
          470                         for(m = mb->root->part; m != nil; m = m->next){
          471                                 if(m->start != nil)
          472                                         continue;
          473                                 Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
          474                         }
          475                         Bflush(&pop->bout);
          476                         threadexits(nil);
          477                         /* _exits(nil); jpc */
          478                 }
          479         }
          480 
          481         for(m = mb->root->part; m != nil; m = next) {
          482                 next = m->next;
          483 
          484                 if(m->start != nil)
          485                         continue;
          486 
          487                 if(s = pop3download(pop, m)) {
          488                         /* message disappeared? unchain */
          489                         fprint(2, "download %d: %s\n", m->mesgno, s);
          490                         delmessage(mb, m);
          491                         mb->root->subname--;
          492                         continue;
          493                 }
          494                 nnew++;
          495                 parse(m, 0, mb, 1);
          496 
          497                 if(doplumb)
          498                         mailplumb(mb, m, 0);
          499         }
          500         if(pop->pipeline)
          501                 waitpid();
          502 
          503         if(nnew || mb->vers == 0) {
          504                 mb->vers++;
          505                 henter(PATH(0, Qtop), mb->name,
          506                         (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
          507         }
          508 
          509         return nil;
          510 }
          511 
          512 /* */
          513 /* delete marked messages */
          514 /* */
          515 static void
          516 pop3purge(Pop *pop, Mailbox *mb)
          517 {
          518         Message *m, *next;
          519 
          520         if(pop->pipeline){
          521                 switch(rfork(RFPROC|RFMEM)){
          522                 case -1:
          523                         fprint(2, "rfork: %r\n");
          524                         pop->pipeline = 0;
          525 
          526                 default:
          527                         break;
          528 
          529                 case 0:
          530                         for(m = mb->root->part; m != nil; m = next){
          531                                 next = m->next;
          532                                 if(m->deleted && m->refs == 0){
          533                                         if(m->inmbox)
          534                                                 Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
          535                                 }
          536                         }
          537                         Bflush(&pop->bout);
          538                         /* _exits(nil); jpc */
          539                         threadexits(nil);
          540                 }
          541         }
          542         for(m = mb->root->part; m != nil; m = next) {
          543                 next = m->next;
          544                 if(m->deleted && m->refs == 0) {
          545                         if(m->inmbox) {
          546                                 if(!pop->pipeline)
          547                                         pop3cmd(pop, "DELE %d", m->mesgno);
          548                                 if(isokay(pop3resp(pop)))
          549                                         delmessage(mb, m);
          550                         } else
          551                                 delmessage(mb, m);
          552                 }
          553         }
          554 }
          555 
          556 
          557 /* connect to pop3 server, sync mailbox */
          558 static char*
          559 pop3sync(Mailbox *mb, int doplumb)
          560 {
          561         char *err;
          562         Pop *pop;
          563 
          564         pop = mb->aux;
          565 
          566         if(err = pop3dial(pop)) {
          567                 mb->waketime = time(0) + pop->refreshtime;
          568                 return err;
          569         }
          570 
          571         if((err = pop3read(pop, mb, doplumb)) == nil){
          572                 pop3purge(pop, mb);
          573                 mb->d->atime = mb->d->mtime = time(0);
          574         }
          575         pop3hangup(pop);
          576         mb->waketime = time(0) + pop->refreshtime;
          577         return err;
          578 }
          579 
          580 static char Epop3ctl[] = "bad pop3 control message";
          581 
          582 static char*
          583 pop3ctl(Mailbox *mb, int argc, char **argv)
          584 {
          585         int n;
          586         Pop *pop;
          587         char *m, *me;
          588 
          589         pop = mb->aux;
          590         if(argc < 1)
          591                 return Epop3ctl;
          592 
          593         if(argc==1 && strcmp(argv[0], "debug")==0){
          594                 pop->debug = 1;
          595                 return nil;
          596         }
          597 
          598         if(argc==1 && strcmp(argv[0], "nodebug")==0){
          599                 pop->debug = 0;
          600                 return nil;
          601         }
          602 
          603         if(argc==1 && strcmp(argv[0], "thumbprint")==0){
          604                 if(pop->thumb)
          605                         freeThumbprints(pop->thumb);
          606                 /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
          607                 m = unsharp("#9/sys/lib/tls/mail");
          608                 me = unsharp("#9/sys/lib/tls/mail.exclude");
          609                 pop->thumb = initThumbprints(m, me);
          610         }
          611         if(strcmp(argv[0], "refresh")==0){
          612                 if(argc==1){
          613                         pop->refreshtime = 60;
          614                         return nil;
          615                 }
          616                 if(argc==2){
          617                         n = atoi(argv[1]);
          618                         if(n < 15)
          619                                 return Epop3ctl;
          620                         pop->refreshtime = n;
          621                         return nil;
          622                 }
          623         }
          624 
          625         return Epop3ctl;
          626 }
          627 
          628 /* free extra memory associated with mb */
          629 static void
          630 pop3close(Mailbox *mb)
          631 {
          632         Pop *pop;
          633 
          634         pop = mb->aux;
          635         free(pop->freep);
          636         free(pop);
          637 }
          638 
          639 /* */
          640 /* open mailboxes of the form /pop/host/user or /apop/host/user */
          641 /* */
          642 char*
          643 pop3mbox(Mailbox *mb, char *path)
          644 {
          645         char *f[10];
          646         int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
          647         Pop *pop;
          648         char *m, *me;
          649 
          650         quotefmtinstall();
          651         popssl = strncmp(path, "/pops/", 6) == 0;
          652         apopssl = strncmp(path, "/apops/", 7) == 0;
          653         poptls = strncmp(path, "/poptls/", 8) == 0;
          654         popnotls = strncmp(path, "/popnotls/", 10) == 0;
          655         ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
          656         apoptls = strncmp(path, "/apoptls/", 9) == 0;
          657         apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
          658         apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
          659 
          660         if(!ppop && !apop)
          661                 return Enotme;
          662 
          663         path = strdup(path);
          664         if(path == nil)
          665                 return "out of memory";
          666 
          667         nf = getfields(path, f, nelem(f), 0, "/");
          668         if(nf != 3 && nf != 4) {
          669                 free(path);
          670                 return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
          671         }
          672 
          673         pop = emalloc(sizeof(*pop));
          674         pop->freep = path;
          675         pop->host = f[2];
          676         if(nf < 4)
          677                 pop->user = nil;
          678         else
          679                 pop->user = f[3];
          680         pop->ppop = ppop;
          681         pop->needssl = popssl || apopssl;
          682         pop->needtls = poptls || apoptls;
          683         pop->refreshtime = 60;
          684         pop->notls = popnotls || apopnotls;
          685         /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
          686                 m = unsharp("#9/sys/lib/tls/mail");
          687                 me = unsharp("#9/sys/lib/tls/mail.exclude");
          688                 pop->thumb = initThumbprints(m, me);
          689 
          690         mb->aux = pop;
          691         mb->sync = pop3sync;
          692         mb->close = pop3close;
          693         mb->ctl = pop3ctl;
          694         mb->d = emalloc(sizeof(*mb->d));
          695 
          696         return nil;
          697 }