xref: /plan9/sys/src/cmd/upas/fs/pop3.c (revision 27acba7cf6d37c65abba4ecf8ad572a5980447ad)
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