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