// // 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; }
// // read in the mailbox and parse into messages. // static char* _readmbox(Mailbox *mb, int doplumb, Mlock *lk) { int fd, n; String *tmp; Dir *d; static char err[128]; Message *m, **l; Inbuf *inb; char *x; l = &mb->root->part; /* * open the mailbox. If it doesn't exist, try the temporary one. */ n = 0; retry: fd = open(mb->path, OREAD); if(fd < 0){ rerrstr(err, sizeof(err)); if(strstr(err, "exclusive lock") != 0 && n++ < 20){ sleep(500); /* wait for lock to go away */ goto retry; } if(strstr(err, "exist") != 0){ tmp = s_copy(mb->path); s_append(tmp, ".tmp"); if(sysrename(s_to_c(tmp), mb->path) == 0){ s_free(tmp); goto retry; } s_free(tmp); } return err; } /* * a new qid.path means reread the mailbox, while * a new qid.vers means read any new messages */ d = dirfstat(fd); if(d == nil){ close(fd); errstr(err, sizeof(err)); return err; } if(mb->d != nil){ if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){ close(fd); free(d); return nil; } if(d->qid.path == mb->d->qid.path){ while(*l != nil) l = &(*l)->next; seek(fd, mb->d->length, 0); } free(mb->d); } mb->d = d; mb->vers++; henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); inb = emalloc(sizeof(Inbuf)); inb->rptr = inb->wptr = inb->data; inb->fd = fd; // read new messages snprint(err, sizeof err, "reading '%s'", mb->path); logmsg(err, nil); for(;;){ if(lk != nil) syslockrefresh(lk); m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; if(readmessage(m, inb) < 0){ delmessage(mb, m); mb->root->subname--; break; } // merge mailbox versions while(*l != nil){ if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ // matches mail we already read, discard logmsg("duplicate", *l); delmessage(mb, m); mb->root->subname--; m = nil; l = &(*l)->next; break; } else { // old mail no longer in box, mark deleted logmsg("disappeared", *l); if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } } if(m == nil) continue; x = strchr(m->start, '\n'); if(x == nil) m->header = m->end; else m->header = x + 1; m->mheader = m->mhend = m->header; parseunix(m); parse(m, 0, mb, 0); logmsg("new", m); /* chain in */ *l = m; l = &m->next; if(doplumb) mailplumb(mb, m, 0); } logmsg("mbox read", nil); // whatever is left has been removed from the mbox, mark deleted while(*l != nil){ if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } close(fd); free(inb); 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; }