// // get capability list, possibly start tls // static char* pop3capa(Pop *pop) { char *s; int hastls; pop3cmd(pop, "CAPA"); if(!isokay(pop3resp(pop))) return nil; hastls = 0; for(;;){ s = pop3resp(pop); if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0) break; if(strcmp(s, "STLS") == 0) hastls = 1; if(strcmp(s, "PIPELINING") == 0) pop->pipeline = 1; if(strcmp(s, "EXPIRE 0") == 0) return "server does not allow mail to be left on server"; } if(hastls && !pop->notls){ pop3cmd(pop, "STLS"); if(!isokay(s = pop3resp(pop))) return s; if((s = pop3pushtls(pop)) != nil) return s; } return nil; }
// // log in to IMAP4 server, select mailbox, no SSL at the moment // static char* imap4login(Imap *imap) { char *s; UserPasswd *up; imap->tag = 0; s = imap4resp(imap); if(!isokay(s)) return "error in initial IMAP handshake"; if(imap->user != nil) up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user); else up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host); if(up == nil) return "cannot find IMAP password"; imap->tag = 1; imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd); free(up); if(!isokay(s = imap4resp(imap))) return s; imap4cmd(imap, "SELECT %Z", imap->mbox); if(!isokay(s = imap4resp(imap))) return s; return nil; }
// // dial and handshake with the imap server // static char* imap4dial(Imap *imap) { char *err, *port; int sfd; TLSconn conn; if(imap->fd >= 0) { imap4cmd(imap, "noop"); if(isokay(imap4resp(imap))) return nil; close(imap->fd); imap->fd = -1; } if(imap->mustssl) port = "imaps"; else port = "imap"; if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0) return imaperrstr(imap->host, port); if(imap->mustssl) { sfd = starttls(imap, &conn); if (sfd < 0) { free(conn.cert); return imaperrstr(imap->host, port); } if(imap->debug) { char fn[128]; int fd; snprint(fn, sizeof fn, "%s/ctl", conn.dir); fd = open(fn, ORDWR); if(fd < 0) fprint(2, "opening ctl: %r\n"); if(fprint(fd, "debug") < 0) fprint(2, "writing ctl: %r\n"); close(fd); } } Binit(&imap->bin, imap->fd, OREAD); Binit(&imap->bout, imap->fd, OWRITE); if(err = imap4login(imap)) { close(imap->fd); return err; } return nil; }
// // sync mailbox // static void imap4purge(Imap *imap, Mailbox *mb) { int ndel; Message *m, *next; ndel = 0; for(m=mb->root->part; m!=nil; m=next) { next = m->next; if(m->deleted && m->refs==0) { if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity) { imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid); if(isokay(imap4resp(imap))) { ndel++; delmessage(mb, m); } } else delmessage(mb, m); } }
// // delete marked messages // static void pop3purge(Pop *pop, Mailbox *mb) { Message *m, *next; if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: fprint(2, "rfork: %r\n"); pop->pipeline = 0; default: break; case 0: for(m = mb->root->part; m != nil; m = next){ next = m->next; if(m->deleted && m->refs == 0){ if(m->inmbox) Bprint(&pop->bout, "DELE %d\r\n", m->mesgno); } } Bflush(&pop->bout); _exits(nil); } } for(m = mb->root->part; m != nil; m = next) { next = m->next; if(m->deleted && m->refs == 0) { if(m->inmbox) { if(!pop->pipeline) pop3cmd(pop, "DELE %d", m->mesgno); if(isokay(pop3resp(pop))) delmessage(mb, m); } else delmessage(mb, m); } } }
// // download a single message // static char* imap4fetch(Mailbox *mb, Message *m) { int i; char *p, *s, sdigest[2*SHA1dlen+1]; Imap *imap; imap = mb->aux; imap->size = 0; if(!isokay(s = imap4resp(imap))) return s; p = imap->base; if(p == nil) return "did not get message body"; removecr(p); free(m->start); m->start = p; m->end = p+strlen(p); m->bend = m->rbend = m->end; m->header = m->start; imap->base = nil; imap->data = nil; parse(m, 0, mb, 1); // digest headers sha1((uchar*)m->start, m->end - m->start, m->digest, nil); for(i = 0; i < SHA1dlen; i++) sprint(sdigest+2*i, "%2.2ux", m->digest[i]); m->sdigest = s_copy(sdigest); return nil; }
// // check for new messages on pop server // UIDL is not required by RFC 1939, but // netscape requires it, so almost every server supports it. // we'll use it to make our lives easier. // static char* pop3read(Pop *pop, Mailbox *mb, int doplumb) { char *s, *p, *uidl, *f[2]; int mesgno, ignore, nnew; Message *m, *next, **l; // Some POP servers disallow UIDL if the maildrop is empty. pop3cmd(pop, "STAT"); if(!isokay(s = pop3resp(pop))) return s; // fetch message listing; note messages to grab l = &mb->root->part; if(strncmp(s, "+OK 0 ", 6) != 0) { pop3cmd(pop, "UIDL"); if(!isokay(s = pop3resp(pop))) return s; for(;;){ p = pop3resp(pop); if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0) break; if(tokenize(p, f, 2) != 2) continue; mesgno = atoi(f[0]); uidl = f[1]; if(strlen(uidl) > 75) // RFC 1939 says 70 characters max continue; ignore = 0; while(*l != nil) { if(strcmp((*l)->uidl, uidl) == 0) { // matches mail we already have, note mesgno for deletion (*l)->mesgno = mesgno; ignore = 1; l = &(*l)->next; break; } else { // old mail no longer in box mark deleted if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } } if(ignore) continue; m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; m->mesgno = mesgno; strcpy(m->uidl, uidl); // chain in; will fill in message later *l = m; l = &m->next; } } // whatever is left has been removed from the mbox, mark as deleted while(*l != nil) { if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } // download new messages nnew = 0; if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: fprint(2, "rfork: %r\n"); pop->pipeline = 0; default: break; case 0: for(m = mb->root->part; m != nil; m = m->next){ if(m->start != nil) continue; Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno); } Bflush(&pop->bout); _exits(nil); } } for(m = mb->root->part; m != nil; m = next) { next = m->next; if(m->start != nil) continue; if(s = pop3download(pop, m)) { // message disappeared? unchain fprint(2, "download %d: %s\n", m->mesgno, s); delmessage(mb, m); mb->root->subname--; continue; } nnew++; parse(m, 0, mb, 1); if(doplumb) mailplumb(mb, m, 0); } if(pop->pipeline) waitpid(); if(nnew || mb->vers == 0) { mb->vers++; henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); } return nil; }
// // download a single message // static char* pop3download(Pop *pop, Message *m) { char *s, *f[3], *wp, *ep; char sdigest[SHA1dlen*2+1]; int i, l, sz; if(!pop->pipeline) pop3cmd(pop, "LIST %d", m->mesgno); if(!isokay(s = pop3resp(pop))) return s; if(tokenize(s, f, 3) != 3) return "syntax error in LIST response"; if(atoi(f[1]) != m->mesgno) return "out of sync with pop3 server"; sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */ if(sz == 0) return "invalid size in LIST response"; m->start = wp = emalloc(sz+1); ep = wp+sz; if(!pop->pipeline) pop3cmd(pop, "RETR %d", m->mesgno); if(!isokay(s = pop3resp(pop))) { m->start = nil; free(wp); return s; } s = nil; while(wp <= ep) { s = pop3resp(pop); if(strcmp(s, "unexpected eof") == 0) { free(m->start); m->start = nil; return "unexpected end of conversation"; } if(strcmp(s, ".") == 0) break; l = strlen(s)+1; if(s[0] == '.') { s++; l--; } /* * grow by 10%/200bytes - some servers * lie about message sizes */ if(wp+l > ep) { int pos = wp - m->start; sz += ((sz / 10) < 200)? 200: sz/10; m->start = erealloc(m->start, sz+1); wp = m->start+pos; ep = m->start+sz; } memmove(wp, s, l-1); wp[l-1] = '\n'; wp += l; } if(s == nil || strcmp(s, ".") != 0) return "out of sync with pop3 server"; m->end = wp; // make sure there's a trailing null // (helps in body searches) *m->end = 0; m->bend = m->rbend = m->end; m->header = m->start; // digest message sha1((uchar*)m->start, m->end - m->start, m->digest, nil); for(i = 0; i < SHA1dlen; i++) sprint(sdigest+2*i, "%2.2ux", m->digest[i]); m->sdigest = s_copy(sdigest); return nil; }
// // log in using APOP if possible, password if allowed by user // static char* pop3login(Pop *pop) { int n; char *s, *p, *q; char ubuf[128], user[128]; char buf[500]; UserPasswd *up; s = pop3resp(pop); if(!isokay(s)) return "error in initial handshake"; if(pop->user) snprint(ubuf, sizeof ubuf, " user=%q", pop->user); else ubuf[0] = '\0'; // look for apop banner if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) { *++q = '\0'; if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s", pop->host, ubuf)) < 0) return "factotum failed"; if(user[0]=='\0') return "factotum did not return a user name"; if(s = pop3capa(pop)) return s; pop3cmd(pop, "APOP %s %.*s", user, n, buf); if(!isokay(s = pop3resp(pop))) return s; return nil; } else { if(pop->ppop == 0) return "no APOP hdr from server"; if(s = pop3capa(pop)) return s; if(pop->needtls && !pop->encrypted) return "could not negotiate TLS"; up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s", pop->host, ubuf); if(up == nil) return "no usable keys found"; pop3cmd(pop, "USER %s", up->user); if(!isokay(s = pop3resp(pop))){ free(up); return s; } pop3cmd(pop, "PASS %s", up->passwd); free(up); if(!isokay(s = pop3resp(pop))) return s; return nil; } }
// // check for new messages on imap4 server // download new messages, mark deleted messages // static char* imap4read(Imap *imap, Mailbox *mb, int doplumb) { char *s; int i, ignore, nnew, t; Message *m, *next, **l; imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox); if(!isokay(s = imap4resp(imap))) return s; imap->nuid = 0; imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0])); imap->muid = imap->nmsg; if(imap->nmsg > 0) { imap4cmd(imap, "UID FETCH 1:* UID"); if(!isokay(s = imap4resp(imap))) return s; } l = &mb->root->part; for(i=0; i<imap->nuid; i++) { ignore = 0; while(*l != nil) { if((*l)->imapuid == imap->uid[i]) { ignore = 1; l = &(*l)->next; break; } else { // old mail, we don't have it anymore if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } } if(ignore) continue; // new message m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; m->imapuid = imap->uid[i]; // add to chain, will download soon *l = m; l = &m->next; } // whatever is left at the end of the chain is gone while(*l != nil) { if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } // download new messages t = imap->tag; if(pipeline) switch(rfork(RFPROC|RFMEM)) { case -1: sysfatal("rfork: %r"); default: break; case 0: for(m = mb->root->part; m != nil; m = m->next) { if(m->start != nil) continue; if(imap->debug) fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", t, (ulong)m->imapuid); Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", t++, (ulong)m->imapuid); } Bflush(&imap->bout); _exits(nil); } nnew = 0; for(m=mb->root->part; m!=nil; m=next) { next = m->next; if(m->start != nil) continue; if(!pipeline) { Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", (ulong)imap->tag, (ulong)m->imapuid); Bflush(&imap->bout); } if(s = imap4fetch(mb, m)) { // message disappeared? unchain fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s); delmessage(mb, m); mb->root->subname--; continue; } nnew++; if(doplumb) mailplumb(mb, m, 0); } if(pipeline) waitpid(); if(nnew || mb->vers == 0) { mb->vers++; henter(PATH(0, Qtop), mb->name, (Qid) { PATH(mb->id, Qmbox), mb->vers, QTDIR }, nil, mb); } return nil; }