xref: /plan9/sys/src/cmd/upas/fs/pop3.c (revision 27acba7cf6d37c65abba4ecf8ad572a5980447ad)
180ee5cbfSDavid du Colombier #include "common.h"
280ee5cbfSDavid du Colombier #include <ctype.h>
380ee5cbfSDavid du Colombier #include <plumb.h>
480ee5cbfSDavid du Colombier #include <libsec.h>
59a747e4fSDavid du Colombier #include <auth.h>
680ee5cbfSDavid du Colombier #include "dat.h"
780ee5cbfSDavid du Colombier 
880ee5cbfSDavid du Colombier #pragma varargck type "M" uchar*
980ee5cbfSDavid du Colombier #pragma varargck argpos pop3cmd 2
1080ee5cbfSDavid du Colombier 
1180ee5cbfSDavid du Colombier typedef struct Pop Pop;
1280ee5cbfSDavid du Colombier struct Pop {
1380ee5cbfSDavid du Colombier 	char *freep;	// free this to free the strings below
1480ee5cbfSDavid du Colombier 
1580ee5cbfSDavid du Colombier 	char *host;
1680ee5cbfSDavid du Colombier 	char *user;
17b27b55e2SDavid du Colombier 	char *port;
1880ee5cbfSDavid du Colombier 
1980ee5cbfSDavid du Colombier 	int ppop;
2080ee5cbfSDavid du Colombier 	int refreshtime;
2180ee5cbfSDavid du Colombier 	int debug;
229a747e4fSDavid du Colombier 	int pipeline;
23b27b55e2SDavid du Colombier 	int encrypted;
249a747e4fSDavid du Colombier 	int needtls;
2539734e7eSDavid du Colombier 	int notls;
26b27b55e2SDavid du Colombier 	int needssl;
2780ee5cbfSDavid du Colombier 
289a747e4fSDavid du Colombier 	// open network connection
299a747e4fSDavid du Colombier 	Biobuf bin;
309a747e4fSDavid du Colombier 	Biobuf bout;
3180ee5cbfSDavid du Colombier 	int fd;
323b6af982SDavid du Colombier 	char *lastline;	// from Brdstr
339a747e4fSDavid du Colombier 
349a747e4fSDavid du Colombier 	Thumbprint *thumb;
3580ee5cbfSDavid du Colombier };
3680ee5cbfSDavid du Colombier 
3780ee5cbfSDavid du Colombier char*
geterrstr(void)3880ee5cbfSDavid du Colombier geterrstr(void)
3980ee5cbfSDavid du Colombier {
4021887c0bSDavid du Colombier 	static char err[Errlen];
4180ee5cbfSDavid du Colombier 
4280ee5cbfSDavid du Colombier 	err[0] = '\0';
439a747e4fSDavid du Colombier 	errstr(err, sizeof(err));
4480ee5cbfSDavid du Colombier 	return err;
4580ee5cbfSDavid du Colombier }
4680ee5cbfSDavid du Colombier 
4780ee5cbfSDavid du Colombier //
4880ee5cbfSDavid du Colombier // get pop3 response line , without worrying
4980ee5cbfSDavid du Colombier // about multiline responses; the clients
5080ee5cbfSDavid du Colombier // will deal with that.
5180ee5cbfSDavid du Colombier //
5280ee5cbfSDavid du Colombier static int
isokay(char * s)5380ee5cbfSDavid du Colombier isokay(char *s)
5480ee5cbfSDavid du Colombier {
5580ee5cbfSDavid du Colombier 	return s!=nil && strncmp(s, "+OK", 3)==0;
5680ee5cbfSDavid du Colombier }
5780ee5cbfSDavid du Colombier 
5880ee5cbfSDavid du Colombier static void
pop3cmd(Pop * pop,char * fmt,...)5980ee5cbfSDavid du Colombier pop3cmd(Pop *pop, char *fmt, ...)
6080ee5cbfSDavid du Colombier {
6180ee5cbfSDavid du Colombier 	char buf[128], *p;
6280ee5cbfSDavid du Colombier 	va_list va;
6380ee5cbfSDavid du Colombier 
6480ee5cbfSDavid du Colombier 	va_start(va, fmt);
659a747e4fSDavid du Colombier 	vseprint(buf, buf+sizeof(buf), fmt, va);
6680ee5cbfSDavid du Colombier 	va_end(va);
6780ee5cbfSDavid du Colombier 
6880ee5cbfSDavid du Colombier 	p = buf+strlen(buf);
6980ee5cbfSDavid du Colombier 	if(p > (buf+sizeof(buf)-3))
7080ee5cbfSDavid du Colombier 		sysfatal("pop3 command too long");
7180ee5cbfSDavid du Colombier 
7280ee5cbfSDavid du Colombier 	if(pop->debug)
7380ee5cbfSDavid du Colombier 		fprint(2, "<- %s\n", buf);
7480ee5cbfSDavid du Colombier 	strcpy(p, "\r\n");
759a747e4fSDavid du Colombier 	Bwrite(&pop->bout, buf, strlen(buf));
769a747e4fSDavid du Colombier 	Bflush(&pop->bout);
7780ee5cbfSDavid du Colombier }
7880ee5cbfSDavid du Colombier 
7980ee5cbfSDavid du Colombier static char*
pop3resp(Pop * pop)8080ee5cbfSDavid du Colombier pop3resp(Pop *pop)
8180ee5cbfSDavid du Colombier {
8280ee5cbfSDavid du Colombier 	char *s;
8380ee5cbfSDavid du Colombier 	char *p;
8480ee5cbfSDavid du Colombier 
8539734e7eSDavid du Colombier 	alarm(60*1000);
863b6af982SDavid du Colombier 	if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
8739734e7eSDavid du Colombier 		close(pop->fd);
8839734e7eSDavid du Colombier 		pop->fd = -1;
8939734e7eSDavid du Colombier 		alarm(0);
90fb7f0c93SDavid du Colombier 		return "unexpected eof";
9139734e7eSDavid du Colombier 	}
9239734e7eSDavid du Colombier 	alarm(0);
9380ee5cbfSDavid du Colombier 
943b6af982SDavid du Colombier 	p = s+strlen(s)-1;
9580ee5cbfSDavid du Colombier 	while(p >= s && (*p == '\r' || *p == '\n'))
9680ee5cbfSDavid du Colombier 		*p-- = '\0';
9780ee5cbfSDavid du Colombier 
9880ee5cbfSDavid du Colombier 	if(pop->debug)
9980ee5cbfSDavid du Colombier 		fprint(2, "-> %s\n", s);
1003b6af982SDavid du Colombier 	free(pop->lastline);
1013b6af982SDavid du Colombier 	pop->lastline = s;
10280ee5cbfSDavid du Colombier 	return s;
10380ee5cbfSDavid du Colombier }
10480ee5cbfSDavid du Colombier 
1057a02f3c0SDavid du Colombier static int
pop3log(char * fmt,...)1067a02f3c0SDavid du Colombier pop3log(char *fmt, ...)
1077a02f3c0SDavid du Colombier {
1087a02f3c0SDavid du Colombier 	va_list ap;
1097a02f3c0SDavid du Colombier 
1107a02f3c0SDavid du Colombier 	va_start(ap,fmt);
1117a02f3c0SDavid du Colombier 	syslog(0, "/sys/log/pop3", fmt, ap);
1127a02f3c0SDavid du Colombier 	va_end(ap);
1137a02f3c0SDavid du Colombier 	return 0;
1147a02f3c0SDavid du Colombier }
1157a02f3c0SDavid du Colombier 
116b27b55e2SDavid du Colombier static char*
pop3pushtls(Pop * pop)117b27b55e2SDavid du Colombier pop3pushtls(Pop *pop)
118b27b55e2SDavid du Colombier {
119b27b55e2SDavid du Colombier 	int fd;
120b27b55e2SDavid du Colombier 	uchar digest[SHA1dlen];
121b27b55e2SDavid du Colombier 	TLSconn conn;
122b27b55e2SDavid du Colombier 
123b27b55e2SDavid du Colombier 	memset(&conn, 0, sizeof conn);
124b27b55e2SDavid du Colombier 	// conn.trace = pop3log;
125b27b55e2SDavid du Colombier 	fd = tlsClient(pop->fd, &conn);
126b27b55e2SDavid du Colombier 	if(fd < 0)
127b27b55e2SDavid du Colombier 		return "tls error";
128b27b55e2SDavid du Colombier 	if(conn.cert==nil || conn.certlen <= 0){
129b27b55e2SDavid du Colombier 		close(fd);
130b27b55e2SDavid du Colombier 		return "server did not provide TLS certificate";
131b27b55e2SDavid du Colombier 	}
132b27b55e2SDavid du Colombier 	sha1(conn.cert, conn.certlen, digest, nil);
133*27acba7cSDavid du Colombier 	/*
134*27acba7cSDavid du Colombier 	 * don't do this any more.  our local it people are rotating their
135*27acba7cSDavid du Colombier 	 * certificates faster than we can keep up.
136*27acba7cSDavid du Colombier 	 */
137*27acba7cSDavid du Colombier 	if(0 && (!pop->thumb || !okThumbprint(digest, pop->thumb))){
138b27b55e2SDavid du Colombier 		fmtinstall('H', encodefmt);
139b27b55e2SDavid du Colombier 		close(fd);
140b27b55e2SDavid du Colombier 		free(conn.cert);
141b27b55e2SDavid du Colombier 		fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
142b27b55e2SDavid du Colombier 		return "bad server certificate";
143b27b55e2SDavid du Colombier 	}
144b27b55e2SDavid du Colombier 	free(conn.cert);
145b27b55e2SDavid du Colombier 	close(pop->fd);
146b27b55e2SDavid du Colombier 	pop->fd = fd;
147b27b55e2SDavid du Colombier 	pop->encrypted = 1;
148b27b55e2SDavid du Colombier 	Binit(&pop->bin, pop->fd, OREAD);
149b27b55e2SDavid du Colombier 	Binit(&pop->bout, pop->fd, OWRITE);
150b27b55e2SDavid du Colombier 	return nil;
151b27b55e2SDavid du Colombier }
1527a02f3c0SDavid du Colombier 
15380ee5cbfSDavid du Colombier //
1549a747e4fSDavid du Colombier // get capability list, possibly start tls
1559a747e4fSDavid du Colombier //
1569a747e4fSDavid du Colombier static char*
pop3capa(Pop * pop)1579a747e4fSDavid du Colombier pop3capa(Pop *pop)
1589a747e4fSDavid du Colombier {
1599a747e4fSDavid du Colombier 	char *s;
1609a747e4fSDavid du Colombier 	int hastls;
1619a747e4fSDavid du Colombier 
1629a747e4fSDavid du Colombier 	pop3cmd(pop, "CAPA");
1639a747e4fSDavid du Colombier 	if(!isokay(pop3resp(pop)))
1649a747e4fSDavid du Colombier 		return nil;
1659a747e4fSDavid du Colombier 
1669a747e4fSDavid du Colombier 	hastls = 0;
167dc5a79c1SDavid du Colombier 	for(;;){
168dc5a79c1SDavid du Colombier 		s = pop3resp(pop);
169fb7f0c93SDavid du Colombier 		if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
1709a747e4fSDavid du Colombier 			break;
1719a747e4fSDavid du Colombier 		if(strcmp(s, "STLS") == 0)
1729a747e4fSDavid du Colombier 			hastls = 1;
1739a747e4fSDavid du Colombier 		if(strcmp(s, "PIPELINING") == 0)
1749a747e4fSDavid du Colombier 			pop->pipeline = 1;
1758e5b7f3bSDavid du Colombier 		if(strcmp(s, "EXPIRE 0") == 0)
1768e5b7f3bSDavid du Colombier 			return "server does not allow mail to be left on server";
1779a747e4fSDavid du Colombier 	}
1789a747e4fSDavid du Colombier 
17939734e7eSDavid du Colombier 	if(hastls && !pop->notls){
1809a747e4fSDavid du Colombier 		pop3cmd(pop, "STLS");
1819a747e4fSDavid du Colombier 		if(!isokay(s = pop3resp(pop)))
1829a747e4fSDavid du Colombier 			return s;
183b27b55e2SDavid du Colombier 		if((s = pop3pushtls(pop)) != nil)
184b27b55e2SDavid du Colombier 			return s;
1859a747e4fSDavid du Colombier 	}
1869a747e4fSDavid du Colombier 	return nil;
1879a747e4fSDavid du Colombier }
1889a747e4fSDavid du Colombier 
1899a747e4fSDavid du Colombier //
19080ee5cbfSDavid du Colombier // log in using APOP if possible, password if allowed by user
19180ee5cbfSDavid du Colombier //
19280ee5cbfSDavid du Colombier static char*
pop3login(Pop * pop)19380ee5cbfSDavid du Colombier pop3login(Pop *pop)
19480ee5cbfSDavid du Colombier {
1959a747e4fSDavid du Colombier 	int n;
19680ee5cbfSDavid du Colombier 	char *s, *p, *q;
1979a747e4fSDavid du Colombier 	char ubuf[128], user[128];
19880ee5cbfSDavid du Colombier 	char buf[500];
1999a747e4fSDavid du Colombier 	UserPasswd *up;
20080ee5cbfSDavid du Colombier 
20180ee5cbfSDavid du Colombier 	s = pop3resp(pop);
20280ee5cbfSDavid du Colombier 	if(!isokay(s))
20380ee5cbfSDavid du Colombier 		return "error in initial handshake";
20480ee5cbfSDavid du Colombier 
2059a747e4fSDavid du Colombier 	if(pop->user)
2069a747e4fSDavid du Colombier 		snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
2079a747e4fSDavid du Colombier 	else
2089a747e4fSDavid du Colombier 		ubuf[0] = '\0';
2099a747e4fSDavid du Colombier 
21080ee5cbfSDavid du Colombier 	// look for apop banner
2119a747e4fSDavid du Colombier 	if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
21280ee5cbfSDavid du Colombier 		*++q = '\0';
2139a747e4fSDavid du Colombier 		if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
2149a747e4fSDavid du Colombier 			pop->host, ubuf)) < 0)
2159a747e4fSDavid du Colombier 			return "factotum failed";
2169a747e4fSDavid du Colombier 		if(user[0]=='\0')
2179a747e4fSDavid du Colombier 			return "factotum did not return a user name";
21880ee5cbfSDavid du Colombier 
2199a747e4fSDavid du Colombier 		if(s = pop3capa(pop))
2209a747e4fSDavid du Colombier 			return s;
22180ee5cbfSDavid du Colombier 
2229a747e4fSDavid du Colombier 		pop3cmd(pop, "APOP %s %.*s", user, n, buf);
22380ee5cbfSDavid du Colombier 		if(!isokay(s = pop3resp(pop)))
22480ee5cbfSDavid du Colombier 			return s;
22580ee5cbfSDavid du Colombier 
22680ee5cbfSDavid du Colombier 		return nil;
22780ee5cbfSDavid du Colombier 	} else {
22880ee5cbfSDavid du Colombier 		if(pop->ppop == 0)
22980ee5cbfSDavid du Colombier 			return "no APOP hdr from server";
23080ee5cbfSDavid du Colombier 
2319a747e4fSDavid du Colombier 		if(s = pop3capa(pop))
23280ee5cbfSDavid du Colombier 			return s;
23380ee5cbfSDavid du Colombier 
234b27b55e2SDavid du Colombier 		if(pop->needtls && !pop->encrypted)
2359a747e4fSDavid du Colombier 			return "could not negotiate TLS";
23680ee5cbfSDavid du Colombier 
2379a747e4fSDavid du Colombier 		up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
2389a747e4fSDavid du Colombier 			pop->host, ubuf);
2399a747e4fSDavid du Colombier 		if(up == nil)
2409a747e4fSDavid du Colombier 			return "no usable keys found";
2419a747e4fSDavid du Colombier 
2429a747e4fSDavid du Colombier 		pop3cmd(pop, "USER %s", up->user);
2439a747e4fSDavid du Colombier 		if(!isokay(s = pop3resp(pop))){
2449a747e4fSDavid du Colombier 			free(up);
2459a747e4fSDavid du Colombier 			return s;
2469a747e4fSDavid du Colombier 		}
2479a747e4fSDavid du Colombier 		pop3cmd(pop, "PASS %s", up->passwd);
2489a747e4fSDavid du Colombier 		free(up);
24980ee5cbfSDavid du Colombier 		if(!isokay(s = pop3resp(pop)))
25080ee5cbfSDavid du Colombier 			return s;
25180ee5cbfSDavid du Colombier 
25280ee5cbfSDavid du Colombier 		return nil;
25380ee5cbfSDavid du Colombier 	}
25480ee5cbfSDavid du Colombier }
25580ee5cbfSDavid du Colombier 
25680ee5cbfSDavid du Colombier //
25780ee5cbfSDavid du Colombier // dial and handshake with pop server
25880ee5cbfSDavid du Colombier //
25980ee5cbfSDavid du Colombier static char*
pop3dial(Pop * pop)26080ee5cbfSDavid du Colombier pop3dial(Pop *pop)
26180ee5cbfSDavid du Colombier {
26280ee5cbfSDavid du Colombier 	char *err;
26380ee5cbfSDavid du Colombier 
264b27b55e2SDavid du Colombier 	if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
26580ee5cbfSDavid du Colombier 		return geterrstr();
26680ee5cbfSDavid du Colombier 
267b27b55e2SDavid du Colombier 	if(pop->needssl){
268b27b55e2SDavid du Colombier 		if((err = pop3pushtls(pop)) != nil)
269b27b55e2SDavid du Colombier 			return err;
270b27b55e2SDavid du Colombier 	}else{
2719a747e4fSDavid du Colombier 		Binit(&pop->bin, pop->fd, OREAD);
2729a747e4fSDavid du Colombier 		Binit(&pop->bout, pop->fd, OWRITE);
273b27b55e2SDavid du Colombier 	}
27480ee5cbfSDavid du Colombier 
27580ee5cbfSDavid du Colombier 	if(err = pop3login(pop)) {
27680ee5cbfSDavid du Colombier 		close(pop->fd);
27780ee5cbfSDavid du Colombier 		return err;
27880ee5cbfSDavid du Colombier 	}
27980ee5cbfSDavid du Colombier 
28080ee5cbfSDavid du Colombier 	return nil;
28180ee5cbfSDavid du Colombier }
28280ee5cbfSDavid du Colombier 
28380ee5cbfSDavid du Colombier //
28480ee5cbfSDavid du Colombier // close connection
28580ee5cbfSDavid du Colombier //
28680ee5cbfSDavid du Colombier static void
pop3hangup(Pop * pop)28780ee5cbfSDavid du Colombier pop3hangup(Pop *pop)
28880ee5cbfSDavid du Colombier {
28980ee5cbfSDavid du Colombier 	pop3cmd(pop, "QUIT");
29080ee5cbfSDavid du Colombier 	pop3resp(pop);
29180ee5cbfSDavid du Colombier 	close(pop->fd);
29280ee5cbfSDavid du Colombier }
29380ee5cbfSDavid du Colombier 
29480ee5cbfSDavid du Colombier //
29580ee5cbfSDavid du Colombier // download a single message
29680ee5cbfSDavid du Colombier //
29780ee5cbfSDavid du Colombier static char*
pop3download(Pop * pop,Message * m)29880ee5cbfSDavid du Colombier pop3download(Pop *pop, Message *m)
29980ee5cbfSDavid du Colombier {
30080ee5cbfSDavid du Colombier 	char *s, *f[3], *wp, *ep;
30180ee5cbfSDavid du Colombier 	char sdigest[SHA1dlen*2+1];
30280ee5cbfSDavid du Colombier 	int i, l, sz;
30380ee5cbfSDavid du Colombier 
3049a747e4fSDavid du Colombier 	if(!pop->pipeline)
30580ee5cbfSDavid du Colombier 		pop3cmd(pop, "LIST %d", m->mesgno);
30680ee5cbfSDavid du Colombier 	if(!isokay(s = pop3resp(pop)))
30780ee5cbfSDavid du Colombier 		return s;
30880ee5cbfSDavid du Colombier 
30980ee5cbfSDavid du Colombier 	if(tokenize(s, f, 3) != 3)
31080ee5cbfSDavid du Colombier 		return "syntax error in LIST response";
31180ee5cbfSDavid du Colombier 
31280ee5cbfSDavid du Colombier 	if(atoi(f[1]) != m->mesgno)
31380ee5cbfSDavid du Colombier 		return "out of sync with pop3 server";
31480ee5cbfSDavid du Colombier 
31580ee5cbfSDavid du Colombier 	sz = atoi(f[2])+200;	/* 200 because the plan9 pop3 server lies */
31680ee5cbfSDavid du Colombier 	if(sz == 0)
31780ee5cbfSDavid du Colombier 		return "invalid size in LIST response";
31880ee5cbfSDavid du Colombier 
31980ee5cbfSDavid du Colombier 	m->start = wp = emalloc(sz+1);
32080ee5cbfSDavid du Colombier 	ep = wp+sz;
32180ee5cbfSDavid du Colombier 
3229a747e4fSDavid du Colombier 	if(!pop->pipeline)
32380ee5cbfSDavid du Colombier 		pop3cmd(pop, "RETR %d", m->mesgno);
32480ee5cbfSDavid du Colombier 	if(!isokay(s = pop3resp(pop))) {
32580ee5cbfSDavid du Colombier 		m->start = nil;
32680ee5cbfSDavid du Colombier 		free(wp);
32780ee5cbfSDavid du Colombier 		return s;
32880ee5cbfSDavid du Colombier 	}
32980ee5cbfSDavid du Colombier 
33080ee5cbfSDavid du Colombier 	s = nil;
33180ee5cbfSDavid du Colombier 	while(wp <= ep) {
33280ee5cbfSDavid du Colombier 		s = pop3resp(pop);
333dc5a79c1SDavid du Colombier 		if(strcmp(s, "unexpected eof") == 0) {
33480ee5cbfSDavid du Colombier 			free(m->start);
33580ee5cbfSDavid du Colombier 			m->start = nil;
33680ee5cbfSDavid du Colombier 			return "unexpected end of conversation";
33780ee5cbfSDavid du Colombier 		}
33880ee5cbfSDavid du Colombier 		if(strcmp(s, ".") == 0)
33980ee5cbfSDavid du Colombier 			break;
34080ee5cbfSDavid du Colombier 
34180ee5cbfSDavid du Colombier 		l = strlen(s)+1;
34280ee5cbfSDavid du Colombier 		if(s[0] == '.') {
34380ee5cbfSDavid du Colombier 			s++;
34480ee5cbfSDavid du Colombier 			l--;
34580ee5cbfSDavid du Colombier 		}
34615b88db3SDavid du Colombier 		/*
34715b88db3SDavid du Colombier 		 * grow by 10%/200bytes - some servers
34815b88db3SDavid du Colombier 		 *  lie about message sizes
34915b88db3SDavid du Colombier 		 */
35080ee5cbfSDavid du Colombier 		if(wp+l > ep) {
35115b88db3SDavid du Colombier 			int pos = wp - m->start;
35215b88db3SDavid du Colombier 			sz += ((sz / 10) < 200)? 200: sz/10;
35315b88db3SDavid du Colombier 			m->start = erealloc(m->start, sz+1);
35415b88db3SDavid du Colombier 			wp = m->start+pos;
35515b88db3SDavid du Colombier 			ep = m->start+sz;
35680ee5cbfSDavid du Colombier 		}
35780ee5cbfSDavid du Colombier 		memmove(wp, s, l-1);
35880ee5cbfSDavid du Colombier 		wp[l-1] = '\n';
35980ee5cbfSDavid du Colombier 		wp += l;
36080ee5cbfSDavid du Colombier 	}
36180ee5cbfSDavid du Colombier 
36280ee5cbfSDavid du Colombier 	if(s == nil || strcmp(s, ".") != 0)
36380ee5cbfSDavid du Colombier 		return "out of sync with pop3 server";
36480ee5cbfSDavid du Colombier 
36580ee5cbfSDavid du Colombier 	m->end = wp;
36680ee5cbfSDavid du Colombier 
36780ee5cbfSDavid du Colombier 	// make sure there's a trailing null
36880ee5cbfSDavid du Colombier 	// (helps in body searches)
36980ee5cbfSDavid du Colombier 	*m->end = 0;
37080ee5cbfSDavid du Colombier 	m->bend = m->rbend = m->end;
37180ee5cbfSDavid du Colombier 	m->header = m->start;
37280ee5cbfSDavid du Colombier 
37380ee5cbfSDavid du Colombier 	// digest message
37480ee5cbfSDavid du Colombier 	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
37580ee5cbfSDavid du Colombier 	for(i = 0; i < SHA1dlen; i++)
37680ee5cbfSDavid du Colombier 		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
37780ee5cbfSDavid du Colombier 	m->sdigest = s_copy(sdigest);
37880ee5cbfSDavid du Colombier 
37980ee5cbfSDavid du Colombier 	return nil;
38080ee5cbfSDavid du Colombier }
38180ee5cbfSDavid du Colombier 
38280ee5cbfSDavid du Colombier //
38380ee5cbfSDavid du Colombier // check for new messages on pop server
38480ee5cbfSDavid du Colombier // UIDL is not required by RFC 1939, but
38580ee5cbfSDavid du Colombier // netscape requires it, so almost every server supports it.
38680ee5cbfSDavid du Colombier // we'll use it to make our lives easier.
38780ee5cbfSDavid du Colombier //
38880ee5cbfSDavid du Colombier static char*
pop3read(Pop * pop,Mailbox * mb,int doplumb)38980ee5cbfSDavid du Colombier pop3read(Pop *pop, Mailbox *mb, int doplumb)
39080ee5cbfSDavid du Colombier {
39180ee5cbfSDavid du Colombier 	char *s, *p, *uidl, *f[2];
39280ee5cbfSDavid du Colombier 	int mesgno, ignore, nnew;
39380ee5cbfSDavid du Colombier 	Message *m, *next, **l;
39480ee5cbfSDavid du Colombier 
3959a747e4fSDavid du Colombier 	// Some POP servers disallow UIDL if the maildrop is empty.
3969a747e4fSDavid du Colombier 	pop3cmd(pop, "STAT");
39780ee5cbfSDavid du Colombier 	if(!isokay(s = pop3resp(pop)))
39880ee5cbfSDavid du Colombier 		return s;
39980ee5cbfSDavid du Colombier 
40080ee5cbfSDavid du Colombier 	// fetch message listing; note messages to grab
40180ee5cbfSDavid du Colombier 	l = &mb->root->part;
4029a747e4fSDavid du Colombier 	if(strncmp(s, "+OK 0 ", 6) != 0) {
4039a747e4fSDavid du Colombier 		pop3cmd(pop, "UIDL");
4049a747e4fSDavid du Colombier 		if(!isokay(s = pop3resp(pop)))
4059a747e4fSDavid du Colombier 			return s;
4069a747e4fSDavid du Colombier 
407dc5a79c1SDavid du Colombier 		for(;;){
408dc5a79c1SDavid du Colombier 			p = pop3resp(pop);
409dc5a79c1SDavid du Colombier 			if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
41080ee5cbfSDavid du Colombier 				break;
41180ee5cbfSDavid du Colombier 
41280ee5cbfSDavid du Colombier 			if(tokenize(p, f, 2) != 2)
41380ee5cbfSDavid du Colombier 				continue;
41480ee5cbfSDavid du Colombier 
41580ee5cbfSDavid du Colombier 			mesgno = atoi(f[0]);
41680ee5cbfSDavid du Colombier 			uidl = f[1];
41780ee5cbfSDavid du Colombier 			if(strlen(uidl) > 75)	// RFC 1939 says 70 characters max
41880ee5cbfSDavid du Colombier 				continue;
41980ee5cbfSDavid du Colombier 
42080ee5cbfSDavid du Colombier 			ignore = 0;
42180ee5cbfSDavid du Colombier 			while(*l != nil) {
42280ee5cbfSDavid du Colombier 				if(strcmp((*l)->uidl, uidl) == 0) {
42380ee5cbfSDavid du Colombier 					// matches mail we already have, note mesgno for deletion
42480ee5cbfSDavid du Colombier 					(*l)->mesgno = mesgno;
42580ee5cbfSDavid du Colombier 					ignore = 1;
42680ee5cbfSDavid du Colombier 					l = &(*l)->next;
42780ee5cbfSDavid du Colombier 					break;
42880ee5cbfSDavid du Colombier 				} else {
42980ee5cbfSDavid du Colombier 					// old mail no longer in box mark deleted
43080ee5cbfSDavid du Colombier 					if(doplumb)
43180ee5cbfSDavid du Colombier 						mailplumb(mb, *l, 1);
43280ee5cbfSDavid du Colombier 					(*l)->inmbox = 0;
43380ee5cbfSDavid du Colombier 					(*l)->deleted = 1;
43480ee5cbfSDavid du Colombier 					l = &(*l)->next;
43580ee5cbfSDavid du Colombier 				}
43680ee5cbfSDavid du Colombier 			}
43780ee5cbfSDavid du Colombier 			if(ignore)
43880ee5cbfSDavid du Colombier 				continue;
43980ee5cbfSDavid du Colombier 
44080ee5cbfSDavid du Colombier 			m = newmessage(mb->root);
44180ee5cbfSDavid du Colombier 			m->mallocd = 1;
44280ee5cbfSDavid du Colombier 			m->inmbox = 1;
44380ee5cbfSDavid du Colombier 			m->mesgno = mesgno;
44480ee5cbfSDavid du Colombier 			strcpy(m->uidl, uidl);
44580ee5cbfSDavid du Colombier 
44680ee5cbfSDavid du Colombier 			// chain in; will fill in message later
44780ee5cbfSDavid du Colombier 			*l = m;
44880ee5cbfSDavid du Colombier 			l = &m->next;
44980ee5cbfSDavid du Colombier 		}
4509a747e4fSDavid du Colombier 	}
45180ee5cbfSDavid du Colombier 
45280ee5cbfSDavid du Colombier 	// whatever is left has been removed from the mbox, mark as deleted
45380ee5cbfSDavid du Colombier 	while(*l != nil) {
45480ee5cbfSDavid du Colombier 		if(doplumb)
45580ee5cbfSDavid du Colombier 			mailplumb(mb, *l, 1);
45680ee5cbfSDavid du Colombier 		(*l)->inmbox = 0;
45780ee5cbfSDavid du Colombier 		(*l)->deleted = 1;
45880ee5cbfSDavid du Colombier 		l = &(*l)->next;
45980ee5cbfSDavid du Colombier 	}
46080ee5cbfSDavid du Colombier 
46180ee5cbfSDavid du Colombier 	// download new messages
46280ee5cbfSDavid du Colombier 	nnew = 0;
4639a747e4fSDavid du Colombier 	if(pop->pipeline){
4649a747e4fSDavid du Colombier 		switch(rfork(RFPROC|RFMEM)){
4659a747e4fSDavid du Colombier 		case -1:
4669a747e4fSDavid du Colombier 			fprint(2, "rfork: %r\n");
4679a747e4fSDavid du Colombier 			pop->pipeline = 0;
4689a747e4fSDavid du Colombier 
4699a747e4fSDavid du Colombier 		default:
4709a747e4fSDavid du Colombier 			break;
4719a747e4fSDavid du Colombier 
4729a747e4fSDavid du Colombier 		case 0:
4739a747e4fSDavid du Colombier 			for(m = mb->root->part; m != nil; m = m->next){
4749a747e4fSDavid du Colombier 				if(m->start != nil)
4759a747e4fSDavid du Colombier 					continue;
4769a747e4fSDavid du Colombier 				Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
4779a747e4fSDavid du Colombier 			}
4789a747e4fSDavid du Colombier 			Bflush(&pop->bout);
4799a747e4fSDavid du Colombier 			_exits(nil);
4809a747e4fSDavid du Colombier 		}
4819a747e4fSDavid du Colombier 	}
4829a747e4fSDavid du Colombier 
48380ee5cbfSDavid du Colombier 	for(m = mb->root->part; m != nil; m = next) {
48480ee5cbfSDavid du Colombier 		next = m->next;
48580ee5cbfSDavid du Colombier 
48680ee5cbfSDavid du Colombier 		if(m->start != nil)
48780ee5cbfSDavid du Colombier 			continue;
48880ee5cbfSDavid du Colombier 
48980ee5cbfSDavid du Colombier 		if(s = pop3download(pop, m)) {
49080ee5cbfSDavid du Colombier 			// message disappeared? unchain
49180ee5cbfSDavid du Colombier 			fprint(2, "download %d: %s\n", m->mesgno, s);
49280ee5cbfSDavid du Colombier 			delmessage(mb, m);
49380ee5cbfSDavid du Colombier 			mb->root->subname--;
49480ee5cbfSDavid du Colombier 			continue;
49580ee5cbfSDavid du Colombier 		}
49680ee5cbfSDavid du Colombier 		nnew++;
4977a02f3c0SDavid du Colombier 		parse(m, 0, mb, 1);
49880ee5cbfSDavid du Colombier 
49980ee5cbfSDavid du Colombier 		if(doplumb)
50080ee5cbfSDavid du Colombier 			mailplumb(mb, m, 0);
50180ee5cbfSDavid du Colombier 	}
5029a747e4fSDavid du Colombier 	if(pop->pipeline)
5039a747e4fSDavid du Colombier 		waitpid();
50480ee5cbfSDavid du Colombier 
505fa1160edSDavid du Colombier 	if(nnew || mb->vers == 0) {
50680ee5cbfSDavid du Colombier 		mb->vers++;
5079a747e4fSDavid du Colombier 		henter(PATH(0, Qtop), mb->name,
5089a747e4fSDavid du Colombier 			(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
50980ee5cbfSDavid du Colombier 	}
51080ee5cbfSDavid du Colombier 
51180ee5cbfSDavid du Colombier 	return nil;
51280ee5cbfSDavid du Colombier }
51380ee5cbfSDavid du Colombier 
51480ee5cbfSDavid du Colombier //
51580ee5cbfSDavid du Colombier // delete marked messages
51680ee5cbfSDavid du Colombier //
51780ee5cbfSDavid du Colombier static void
pop3purge(Pop * pop,Mailbox * mb)51880ee5cbfSDavid du Colombier pop3purge(Pop *pop, Mailbox *mb)
51980ee5cbfSDavid du Colombier {
52080ee5cbfSDavid du Colombier 	Message *m, *next;
52180ee5cbfSDavid du Colombier 
52239734e7eSDavid du Colombier 	if(pop->pipeline){
52339734e7eSDavid du Colombier 		switch(rfork(RFPROC|RFMEM)){
52439734e7eSDavid du Colombier 		case -1:
52539734e7eSDavid du Colombier 			fprint(2, "rfork: %r\n");
52639734e7eSDavid du Colombier 			pop->pipeline = 0;
52739734e7eSDavid du Colombier 
52839734e7eSDavid du Colombier 		default:
52939734e7eSDavid du Colombier 			break;
53039734e7eSDavid du Colombier 
53139734e7eSDavid du Colombier 		case 0:
53239734e7eSDavid du Colombier 			for(m = mb->root->part; m != nil; m = next){
53339734e7eSDavid du Colombier 				next = m->next;
53439734e7eSDavid du Colombier 				if(m->deleted && m->refs == 0){
53539734e7eSDavid du Colombier 					if(m->inmbox)
53639734e7eSDavid du Colombier 						Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
53739734e7eSDavid du Colombier 				}
53839734e7eSDavid du Colombier 			}
53939734e7eSDavid du Colombier 			Bflush(&pop->bout);
54039734e7eSDavid du Colombier 			_exits(nil);
54139734e7eSDavid du Colombier 		}
54239734e7eSDavid du Colombier 	}
54380ee5cbfSDavid du Colombier 	for(m = mb->root->part; m != nil; m = next) {
54480ee5cbfSDavid du Colombier 		next = m->next;
54580ee5cbfSDavid du Colombier 		if(m->deleted && m->refs == 0) {
54680ee5cbfSDavid du Colombier 			if(m->inmbox) {
54739734e7eSDavid du Colombier 				if(!pop->pipeline)
54880ee5cbfSDavid du Colombier 					pop3cmd(pop, "DELE %d", m->mesgno);
54980ee5cbfSDavid du Colombier 				if(isokay(pop3resp(pop)))
55080ee5cbfSDavid du Colombier 					delmessage(mb, m);
55180ee5cbfSDavid du Colombier 			} else
55280ee5cbfSDavid du Colombier 				delmessage(mb, m);
55380ee5cbfSDavid du Colombier 		}
55480ee5cbfSDavid du Colombier 	}
55580ee5cbfSDavid du Colombier }
55680ee5cbfSDavid du Colombier 
55780ee5cbfSDavid du Colombier 
55880ee5cbfSDavid du Colombier // connect to pop3 server, sync mailbox
55980ee5cbfSDavid du Colombier static char*
pop3sync(Mailbox * mb,int doplumb)56080ee5cbfSDavid du Colombier pop3sync(Mailbox *mb, int doplumb)
56180ee5cbfSDavid du Colombier {
56280ee5cbfSDavid du Colombier 	char *err;
56380ee5cbfSDavid du Colombier 	Pop *pop;
56480ee5cbfSDavid du Colombier 
56580ee5cbfSDavid du Colombier 	pop = mb->aux;
56680ee5cbfSDavid du Colombier 
56780ee5cbfSDavid du Colombier 	if(err = pop3dial(pop)) {
56880ee5cbfSDavid du Colombier 		mb->waketime = time(0) + pop->refreshtime;
56980ee5cbfSDavid du Colombier 		return err;
57080ee5cbfSDavid du Colombier 	}
57180ee5cbfSDavid du Colombier 
57280ee5cbfSDavid du Colombier 	if((err = pop3read(pop, mb, doplumb)) == nil){
57380ee5cbfSDavid du Colombier 		pop3purge(pop, mb);
5749a747e4fSDavid du Colombier 		mb->d->atime = mb->d->mtime = time(0);
57580ee5cbfSDavid du Colombier 	}
57680ee5cbfSDavid du Colombier 	pop3hangup(pop);
57780ee5cbfSDavid du Colombier 	mb->waketime = time(0) + pop->refreshtime;
57880ee5cbfSDavid du Colombier 	return err;
57980ee5cbfSDavid du Colombier }
58080ee5cbfSDavid du Colombier 
58180ee5cbfSDavid du Colombier static char Epop3ctl[] = "bad pop3 control message";
58280ee5cbfSDavid du Colombier 
58380ee5cbfSDavid du Colombier static char*
pop3ctl(Mailbox * mb,int argc,char ** argv)58480ee5cbfSDavid du Colombier pop3ctl(Mailbox *mb, int argc, char **argv)
58580ee5cbfSDavid du Colombier {
58680ee5cbfSDavid du Colombier 	int n;
58780ee5cbfSDavid du Colombier 	Pop *pop;
58880ee5cbfSDavid du Colombier 
58980ee5cbfSDavid du Colombier 	pop = mb->aux;
59080ee5cbfSDavid du Colombier 	if(argc < 1)
59180ee5cbfSDavid du Colombier 		return Epop3ctl;
59280ee5cbfSDavid du Colombier 
59380ee5cbfSDavid du Colombier 	if(argc==1 && strcmp(argv[0], "debug")==0){
59480ee5cbfSDavid du Colombier 		pop->debug = 1;
59580ee5cbfSDavid du Colombier 		return nil;
59680ee5cbfSDavid du Colombier 	}
59780ee5cbfSDavid du Colombier 
59880ee5cbfSDavid du Colombier 	if(argc==1 && strcmp(argv[0], "nodebug")==0){
59980ee5cbfSDavid du Colombier 		pop->debug = 0;
60080ee5cbfSDavid du Colombier 		return nil;
60180ee5cbfSDavid du Colombier 	}
60280ee5cbfSDavid du Colombier 
6039a747e4fSDavid du Colombier 	if(argc==1 && strcmp(argv[0], "thumbprint")==0){
6049a747e4fSDavid du Colombier 		if(pop->thumb)
6059a747e4fSDavid du Colombier 			freeThumbprints(pop->thumb);
6069a747e4fSDavid du Colombier 		pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
6079a747e4fSDavid du Colombier 	}
60880ee5cbfSDavid du Colombier 	if(strcmp(argv[0], "refresh")==0){
60980ee5cbfSDavid du Colombier 		if(argc==1){
61080ee5cbfSDavid du Colombier 			pop->refreshtime = 60;
61180ee5cbfSDavid du Colombier 			return nil;
61280ee5cbfSDavid du Colombier 		}
61380ee5cbfSDavid du Colombier 		if(argc==2){
61480ee5cbfSDavid du Colombier 			n = atoi(argv[1]);
61580ee5cbfSDavid du Colombier 			if(n < 15)
61680ee5cbfSDavid du Colombier 				return Epop3ctl;
61780ee5cbfSDavid du Colombier 			pop->refreshtime = n;
61880ee5cbfSDavid du Colombier 			return nil;
61980ee5cbfSDavid du Colombier 		}
62080ee5cbfSDavid du Colombier 	}
62180ee5cbfSDavid du Colombier 
62280ee5cbfSDavid du Colombier 	return Epop3ctl;
62380ee5cbfSDavid du Colombier }
62480ee5cbfSDavid du Colombier 
62580ee5cbfSDavid du Colombier // free extra memory associated with mb
62680ee5cbfSDavid du Colombier static void
pop3close(Mailbox * mb)62780ee5cbfSDavid du Colombier pop3close(Mailbox *mb)
62880ee5cbfSDavid du Colombier {
62980ee5cbfSDavid du Colombier 	Pop *pop;
63080ee5cbfSDavid du Colombier 
63180ee5cbfSDavid du Colombier 	pop = mb->aux;
63280ee5cbfSDavid du Colombier 	free(pop->freep);
63380ee5cbfSDavid du Colombier 	free(pop);
63480ee5cbfSDavid du Colombier }
63580ee5cbfSDavid du Colombier 
63680ee5cbfSDavid du Colombier //
63780ee5cbfSDavid du Colombier // open mailboxes of the form /pop/host/user or /apop/host/user
63880ee5cbfSDavid du Colombier //
63980ee5cbfSDavid du Colombier char*
pop3mbox(Mailbox * mb,char * path)64080ee5cbfSDavid du Colombier pop3mbox(Mailbox *mb, char *path)
64180ee5cbfSDavid du Colombier {
64280ee5cbfSDavid du Colombier 	char *f[10];
643b27b55e2SDavid du Colombier 	int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
64480ee5cbfSDavid du Colombier 	Pop *pop;
64580ee5cbfSDavid du Colombier 
6469a747e4fSDavid du Colombier 	quotefmtinstall();
647b27b55e2SDavid du Colombier 	popssl = strncmp(path, "/pops/", 6) == 0;
648b27b55e2SDavid du Colombier 	apopssl = strncmp(path, "/apops/", 7) == 0;
6499a747e4fSDavid du Colombier 	poptls = strncmp(path, "/poptls/", 8) == 0;
65039734e7eSDavid du Colombier 	popnotls = strncmp(path, "/popnotls/", 10) == 0;
651b27b55e2SDavid du Colombier 	ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
6529a747e4fSDavid du Colombier 	apoptls = strncmp(path, "/apoptls/", 9) == 0;
65339734e7eSDavid du Colombier 	apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
654b27b55e2SDavid du Colombier 	apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
6559a747e4fSDavid du Colombier 
6569a747e4fSDavid du Colombier 	if(!ppop && !apop)
65780ee5cbfSDavid du Colombier 		return Enotme;
65880ee5cbfSDavid du Colombier 
65980ee5cbfSDavid du Colombier 	path = strdup(path);
66080ee5cbfSDavid du Colombier 	if(path == nil)
66180ee5cbfSDavid du Colombier 		return "out of memory";
66280ee5cbfSDavid du Colombier 
66380ee5cbfSDavid du Colombier 	nf = getfields(path, f, nelem(f), 0, "/");
6649a747e4fSDavid du Colombier 	if(nf != 3 && nf != 4) {
66580ee5cbfSDavid du Colombier 		free(path);
666b27b55e2SDavid du Colombier 		return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
66780ee5cbfSDavid du Colombier 	}
66880ee5cbfSDavid du Colombier 
66980ee5cbfSDavid du Colombier 	pop = emalloc(sizeof(*pop));
67080ee5cbfSDavid du Colombier 	pop->freep = path;
67180ee5cbfSDavid du Colombier 	pop->host = f[2];
6729a747e4fSDavid du Colombier 	if(nf < 4)
6739a747e4fSDavid du Colombier 		pop->user = nil;
6749a747e4fSDavid du Colombier 	else
67580ee5cbfSDavid du Colombier 		pop->user = f[3];
6769a747e4fSDavid du Colombier 	pop->ppop = ppop;
677b27b55e2SDavid du Colombier 	pop->needssl = popssl || apopssl;
6789a747e4fSDavid du Colombier 	pop->needtls = poptls || apoptls;
6799a747e4fSDavid du Colombier 	pop->refreshtime = 60;
68039734e7eSDavid du Colombier 	pop->notls = popnotls || apopnotls;
6819a747e4fSDavid du Colombier 	pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
68280ee5cbfSDavid du Colombier 
68380ee5cbfSDavid du Colombier 	mb->aux = pop;
68480ee5cbfSDavid du Colombier 	mb->sync = pop3sync;
68580ee5cbfSDavid du Colombier 	mb->close = pop3close;
68680ee5cbfSDavid du Colombier 	mb->ctl = pop3ctl;
6879a747e4fSDavid du Colombier 	mb->d = emalloc(sizeof(*mb->d));
68880ee5cbfSDavid du Colombier 
68980ee5cbfSDavid du Colombier 	return nil;
69080ee5cbfSDavid du Colombier }
69180ee5cbfSDavid du Colombier 
692