Blob


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"
9 #pragma varargck type "M" uchar*
10 #pragma varargck argpos pop3cmd 2
12 typedef struct Pop Pop;
13 struct Pop {
14 char *freep; /* free this to free the strings below */
16 char *host;
17 char *user;
18 char *port;
20 int ppop;
21 int refreshtime;
22 int debug;
23 int pipeline;
24 int encrypted;
25 int needtls;
26 int notls;
27 int needssl;
29 /* open network connection */
30 Biobuf bin;
31 Biobuf bout;
32 int fd;
33 char *lastline; /* from Brdstr */
35 Thumbprint *thumb;
36 };
38 char*
39 geterrstr(void)
40 {
41 static char err[64];
43 err[0] = '\0';
44 errstr(err, sizeof(err));
45 return err;
46 }
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 }
59 static void
60 pop3cmd(Pop *pop, char *fmt, ...)
61 {
62 char buf[128], *p;
63 va_list va;
65 va_start(va, fmt);
66 vseprint(buf, buf+sizeof(buf), fmt, va);
67 va_end(va);
69 p = buf+strlen(buf);
70 if(p > (buf+sizeof(buf)-3))
71 sysfatal("pop3 command too long");
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 }
80 static char*
81 pop3resp(Pop *pop)
82 {
83 char *s;
84 char *p;
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);
95 p = s+strlen(s)-1;
96 while(p >= s && (*p == '\r' || *p == '\n'))
97 *p-- = '\0';
99 if(pop->debug)
100 fprint(2, "-> %s\n", s);
101 free(pop->lastline);
102 pop->lastline = s;
103 return s;
106 #if 0 /* jpc */
107 static int
108 pop3log(char *fmt, ...)
110 va_list ap;
112 va_start(ap,fmt);
113 syslog(0, "/sys/log/pop3", fmt, ap);
114 va_end(ap);
115 return 0;
117 #endif
119 static char*
120 pop3pushtls(Pop *pop)
122 int fd;
123 uchar digest[SHA1dlen];
124 TLSconn conn;
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";
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";
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;
152 /* */
153 /* get capability list, possibly start tls */
154 /* */
155 static char*
156 pop3capa(Pop *pop)
158 char *s;
159 int hastls;
161 pop3cmd(pop, "CAPA");
162 if(!isokay(pop3resp(pop)))
163 return nil;
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;
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;
183 return nil;
186 /* */
187 /* log in using APOP if possible, password if allowed by user */
188 /* */
189 static char*
190 pop3login(Pop *pop)
192 int n;
193 char *s, *p, *q;
194 char ubuf[128], user[128];
195 char buf[500];
196 UserPasswd *up;
198 s = pop3resp(pop);
199 if(!isokay(s))
200 return "error in initial handshake";
202 if(pop->user)
203 snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
204 else
205 ubuf[0] = '\0';
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";
216 if(s = pop3capa(pop))
217 return s;
219 pop3cmd(pop, "APOP %s %.*s", user, n, buf);
220 if(!isokay(s = pop3resp(pop)))
221 return s;
223 return nil;
224 } else {
225 if(pop->ppop == 0)
226 return "no APOP hdr from server";
228 if(s = pop3capa(pop))
229 return s;
231 if(pop->needtls && !pop->encrypted)
232 return "could not negotiate TLS";
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";
239 pop3cmd(pop, "USER %s", up->user);
240 if(!isokay(s = pop3resp(pop))){
241 free(up);
242 return s;
244 pop3cmd(pop, "PASS %s", up->passwd);
245 free(up);
246 if(!isokay(s = pop3resp(pop)))
247 return s;
249 return nil;
253 /* */
254 /* dial and handshake with pop server */
255 /* */
256 static char*
257 pop3dial(Pop *pop)
259 char *err;
261 if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
262 return geterrstr();
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);
272 if(err = pop3login(pop)) {
273 close(pop->fd);
274 return err;
277 return nil;
280 /* */
281 /* close connection */
282 /* */
283 static void
284 pop3hangup(Pop *pop)
286 pop3cmd(pop, "QUIT");
287 pop3resp(pop);
288 close(pop->fd);
291 /* */
292 /* download a single message */
293 /* */
294 static char*
295 pop3download(Pop *pop, Message *m)
297 char *s, *f[3], *wp, *ep;
298 char sdigest[SHA1dlen*2+1];
299 int i, l, sz;
301 if(!pop->pipeline)
302 pop3cmd(pop, "LIST %d", m->mesgno);
303 if(!isokay(s = pop3resp(pop)))
304 return s;
306 if(tokenize(s, f, 3) != 3)
307 return "syntax error in LIST response";
309 if(atoi(f[1]) != m->mesgno)
310 return "out of sync with pop3 server";
312 sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */
313 if(sz == 0)
314 return "invalid size in LIST response";
316 m->start = wp = emalloc(sz+1);
317 ep = wp+sz;
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;
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";
335 if(strcmp(s, ".") == 0)
336 break;
338 l = strlen(s)+1;
339 if(s[0] == '.') {
340 s++;
341 l--;
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;
354 memmove(wp, s, l-1);
355 wp[l-1] = '\n';
356 wp += l;
359 if(s == nil || strcmp(s, ".") != 0)
360 return "out of sync with pop3 server";
362 m->end = wp;
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;
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);
376 return nil;
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)
388 char *s, *p, *uidl, *f[2];
389 int mesgno, ignore, nnew;
390 Message *m, *next, **l;
392 /* Some POP servers disallow UIDL if the maildrop is empty. */
393 pop3cmd(pop, "STAT");
394 if(!isokay(s = pop3resp(pop)))
395 return s;
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;
404 for(;;){
405 p = pop3resp(pop);
406 if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
407 break;
409 if(tokenize(p, f, 2) != 2)
410 continue;
412 mesgno = atoi(f[0]);
413 uidl = f[1];
414 if(strlen(uidl) > 75) /* RFC 1939 says 70 characters max */
415 continue;
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;
434 if(ignore)
435 continue;
437 m = newmessage(mb->root);
438 m->mallocd = 1;
439 m->inmbox = 1;
440 m->mesgno = mesgno;
441 strcpy(m->uidl, uidl);
443 /* chain in; will fill in message later */
444 *l = m;
445 l = &m->next;
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;
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;
466 default:
467 break;
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);
475 Bflush(&pop->bout);
476 threadexits(nil);
477 /* _exits(nil); jpc */
481 for(m = mb->root->part; m != nil; m = next) {
482 next = m->next;
484 if(m->start != nil)
485 continue;
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;
494 nnew++;
495 parse(m, 0, mb, 1);
497 if(doplumb)
498 mailplumb(mb, m, 0);
500 if(pop->pipeline)
501 waitpid();
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);
509 return nil;
512 /* */
513 /* delete marked messages */
514 /* */
515 static void
516 pop3purge(Pop *pop, Mailbox *mb)
518 Message *m, *next;
520 if(pop->pipeline){
521 switch(rfork(RFPROC|RFMEM)){
522 case -1:
523 fprint(2, "rfork: %r\n");
524 pop->pipeline = 0;
526 default:
527 break;
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);
537 Bflush(&pop->bout);
538 /* _exits(nil); jpc */
539 threadexits(nil);
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);
557 /* connect to pop3 server, sync mailbox */
558 static char*
559 pop3sync(Mailbox *mb, int doplumb)
561 char *err;
562 Pop *pop;
564 pop = mb->aux;
566 if(err = pop3dial(pop)) {
567 mb->waketime = time(0) + pop->refreshtime;
568 return err;
571 if((err = pop3read(pop, mb, doplumb)) == nil){
572 pop3purge(pop, mb);
573 mb->d->atime = mb->d->mtime = time(0);
575 pop3hangup(pop);
576 mb->waketime = time(0) + pop->refreshtime;
577 return err;
580 static char Epop3ctl[] = "bad pop3 control message";
582 static char*
583 pop3ctl(Mailbox *mb, int argc, char **argv)
585 int n;
586 Pop *pop;
587 char *m, *me;
589 pop = mb->aux;
590 if(argc < 1)
591 return Epop3ctl;
593 if(argc==1 && strcmp(argv[0], "debug")==0){
594 pop->debug = 1;
595 return nil;
598 if(argc==1 && strcmp(argv[0], "nodebug")==0){
599 pop->debug = 0;
600 return nil;
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);
611 if(strcmp(argv[0], "refresh")==0){
612 if(argc==1){
613 pop->refreshtime = 60;
614 return nil;
616 if(argc==2){
617 n = atoi(argv[1]);
618 if(n < 15)
619 return Epop3ctl;
620 pop->refreshtime = n;
621 return nil;
625 return Epop3ctl;
628 /* free extra memory associated with mb */
629 static void
630 pop3close(Mailbox *mb)
632 Pop *pop;
634 pop = mb->aux;
635 free(pop->freep);
636 free(pop);
639 /* */
640 /* open mailboxes of the form /pop/host/user or /apop/host/user */
641 /* */
642 char*
643 pop3mbox(Mailbox *mb, char *path)
645 char *f[10];
646 int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
647 Pop *pop;
648 char *m, *me;
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;
660 if(!ppop && !apop)
661 return Enotme;
663 path = strdup(path);
664 if(path == nil)
665 return "out of memory";
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]";
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);
690 mb->aux = pop;
691 mb->sync = pop3sync;
692 mb->close = pop3close;
693 mb->ctl = pop3ctl;
694 mb->d = emalloc(sizeof(*mb->d));
696 return nil;