plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

pop3.c (13496B)


      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"
      8 
      9 #pragma varargck type "M" uchar*
     10 #pragma varargck argpos pop3cmd 2
     11 
     12 typedef struct Pop Pop;
     13 struct Pop {
     14 	char *freep;	/* free this to free the strings below */
     15 
     16 	char *host;
     17 	char *user;
     18 	char *port;
     19 
     20 	int ppop;
     21 	int refreshtime;
     22 	int debug;
     23 	int pipeline;
     24 	int encrypted;
     25 	int needtls;
     26 	int notls;
     27 	int needssl;
     28 
     29 	/* open network connection */
     30 	Biobuf bin;
     31 	Biobuf bout;
     32 	int fd;
     33 	char *lastline;	/* from Brdstr */
     34 
     35 	Thumbprint *thumb;
     36 };
     37 
     38 char*
     39 geterrstr(void)
     40 {
     41 	static char err[64];
     42 
     43 	err[0] = '\0';
     44 	errstr(err, sizeof(err));
     45 	return err;
     46 }
     47 
     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 }
     58 
     59 static void
     60 pop3cmd(Pop *pop, char *fmt, ...)
     61 {
     62 	char buf[128], *p;
     63 	va_list va;
     64 
     65 	va_start(va, fmt);
     66 	vseprint(buf, buf+sizeof(buf), fmt, va);
     67 	va_end(va);
     68 
     69 	p = buf+strlen(buf);
     70 	if(p > (buf+sizeof(buf)-3))
     71 		sysfatal("pop3 command too long");
     72 
     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 }
     79 
     80 static char*
     81 pop3resp(Pop *pop)
     82 {
     83 	char *s;
     84 	char *p;
     85 
     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);
     94 
     95 	p = s+strlen(s)-1;
     96 	while(p >= s && (*p == '\r' || *p == '\n'))
     97 		*p-- = '\0';
     98 
     99 	if(pop->debug)
    100 		fprint(2, "-> %s\n", s);
    101 	free(pop->lastline);
    102 	pop->lastline = s;
    103 	return s;
    104 }
    105 
    106 #if 0 /* jpc */
    107 static int
    108 pop3log(char *fmt, ...)
    109 {
    110 	va_list ap;
    111 
    112 	va_start(ap,fmt);
    113 	syslog(0, "/sys/log/pop3", fmt, ap);
    114 	va_end(ap);
    115 	return 0;
    116 }
    117 #endif
    118 
    119 static char*
    120 pop3pushtls(Pop *pop)
    121 {
    122 	int fd;
    123 	uchar digest[SHA1dlen];
    124 	TLSconn conn;
    125 
    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";
    134 	}
    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";
    142 	}
    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;
    150 }
    151 
    152 /* */
    153 /* get capability list, possibly start tls */
    154 /* */
    155 static char*
    156 pop3capa(Pop *pop)
    157 {
    158 	char *s;
    159 	int hastls;
    160 
    161 	pop3cmd(pop, "CAPA");
    162 	if(!isokay(pop3resp(pop)))
    163 		return nil;
    164 
    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;
    174 	}
    175 
    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;
    182 	}
    183 	return nil;
    184 }
    185 
    186 /* */
    187 /* log in using APOP if possible, password if allowed by user */
    188 /* */
    189 static char*
    190 pop3login(Pop *pop)
    191 {
    192 	int n;
    193 	char *s, *p, *q;
    194 	char ubuf[128], user[128];
    195 	char buf[500];
    196 	UserPasswd *up;
    197 
    198 	s = pop3resp(pop);
    199 	if(!isokay(s))
    200 		return "error in initial handshake";
    201 
    202 	if(pop->user)
    203 		snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
    204 	else
    205 		ubuf[0] = '\0';
    206 
    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";
    215 
    216 		if(s = pop3capa(pop))
    217 			return s;
    218 
    219 		pop3cmd(pop, "APOP %s %.*s", user, n, buf);
    220 		if(!isokay(s = pop3resp(pop)))
    221 			return s;
    222 
    223 		return nil;
    224 	} else {
    225 		if(pop->ppop == 0)
    226 			return "no APOP hdr from server";
    227 
    228 		if(s = pop3capa(pop))
    229 			return s;
    230 
    231 		if(pop->needtls && !pop->encrypted)
    232 			return "could not negotiate TLS";
    233 
    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";
    238 
    239 		pop3cmd(pop, "USER %s", up->user);
    240 		if(!isokay(s = pop3resp(pop))){
    241 			free(up);
    242 			return s;
    243 		}
    244 		pop3cmd(pop, "PASS %s", up->passwd);
    245 		free(up);
    246 		if(!isokay(s = pop3resp(pop)))
    247 			return s;
    248 
    249 		return nil;
    250 	}
    251 }
    252 
    253 /* */
    254 /* dial and handshake with pop server */
    255 /* */
    256 static char*
    257 pop3dial(Pop *pop)
    258 {
    259 	char *err;
    260 
    261 	if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
    262 		return geterrstr();
    263 
    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);
    270 	}
    271 
    272 	if(err = pop3login(pop)) {
    273 		close(pop->fd);
    274 		return err;
    275 	}
    276 
    277 	return nil;
    278 }
    279 
    280 /* */
    281 /* close connection */
    282 /* */
    283 static void
    284 pop3hangup(Pop *pop)
    285 {
    286 	pop3cmd(pop, "QUIT");
    287 	pop3resp(pop);
    288 	close(pop->fd);
    289 }
    290 
    291 /* */
    292 /* download a single message */
    293 /* */
    294 static char*
    295 pop3download(Pop *pop, Message *m)
    296 {
    297 	char *s, *f[3], *wp, *ep;
    298 	char sdigest[SHA1dlen*2+1];
    299 	int i, l, sz;
    300 
    301 	if(!pop->pipeline)
    302 		pop3cmd(pop, "LIST %d", m->mesgno);
    303 	if(!isokay(s = pop3resp(pop)))
    304 		return s;
    305 
    306 	if(tokenize(s, f, 3) != 3)
    307 		return "syntax error in LIST response";
    308 
    309 	if(atoi(f[1]) != m->mesgno)
    310 		return "out of sync with pop3 server";
    311 
    312 	sz = atoi(f[2])+200;	/* 200 because the plan9 pop3 server lies */
    313 	if(sz == 0)
    314 		return "invalid size in LIST response";
    315 
    316 	m->start = wp = emalloc(sz+1);
    317 	ep = wp+sz;
    318 
    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;
    325 	}
    326 
    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";
    334 		}
    335 		if(strcmp(s, ".") == 0)
    336 			break;
    337 
    338 		l = strlen(s)+1;
    339 		if(s[0] == '.') {
    340 			s++;
    341 			l--;
    342 		}
    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;
    353 		}
    354 		memmove(wp, s, l-1);
    355 		wp[l-1] = '\n';
    356 		wp += l;
    357 	}
    358 
    359 	if(s == nil || strcmp(s, ".") != 0)
    360 		return "out of sync with pop3 server";
    361 
    362 	m->end = wp;
    363 
    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;
    369 
    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);
    375 
    376 	return nil;
    377 }
    378 
    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)
    387 {
    388 	char *s, *p, *uidl, *f[2];
    389 	int mesgno, ignore, nnew;
    390 	Message *m, *next, **l;
    391 
    392 	/* Some POP servers disallow UIDL if the maildrop is empty. */
    393 	pop3cmd(pop, "STAT");
    394 	if(!isokay(s = pop3resp(pop)))
    395 		return s;
    396 
    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;
    403 
    404 		for(;;){
    405 			p = pop3resp(pop);
    406 			if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
    407 				break;
    408 
    409 			if(tokenize(p, f, 2) != 2)
    410 				continue;
    411 
    412 			mesgno = atoi(f[0]);
    413 			uidl = f[1];
    414 			if(strlen(uidl) > 75)	/* RFC 1939 says 70 characters max */
    415 				continue;
    416 
    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;
    432 				}
    433 			}
    434 			if(ignore)
    435 				continue;
    436 
    437 			m = newmessage(mb->root);
    438 			m->mallocd = 1;
    439 			m->inmbox = 1;
    440 			m->mesgno = mesgno;
    441 			strcpy(m->uidl, uidl);
    442 
    443 			/* chain in; will fill in message later */
    444 			*l = m;
    445 			l = &m->next;
    446 		}
    447 	}
    448 
    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;
    456 	}
    457 
    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;
    465 
    466 		default:
    467 			break;
    468 
    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);
    474 			}
    475 			Bflush(&pop->bout);
    476 			threadexits(nil);
    477 			/* _exits(nil); jpc */
    478 		}
    479 	}
    480 
    481 	for(m = mb->root->part; m != nil; m = next) {
    482 		next = m->next;
    483 
    484 		if(m->start != nil)
    485 			continue;
    486 
    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;
    493 		}
    494 		nnew++;
    495 		parse(m, 0, mb, 1);
    496 
    497 		if(doplumb)
    498 			mailplumb(mb, m, 0);
    499 	}
    500 	if(pop->pipeline)
    501 		waitpid();
    502 
    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);
    507 	}
    508 
    509 	return nil;
    510 }
    511 
    512 /* */
    513 /* delete marked messages */
    514 /* */
    515 static void
    516 pop3purge(Pop *pop, Mailbox *mb)
    517 {
    518 	Message *m, *next;
    519 
    520 	if(pop->pipeline){
    521 		switch(rfork(RFPROC|RFMEM)){
    522 		case -1:
    523 			fprint(2, "rfork: %r\n");
    524 			pop->pipeline = 0;
    525 
    526 		default:
    527 			break;
    528 
    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);
    535 				}
    536 			}
    537 			Bflush(&pop->bout);
    538 			/* _exits(nil); jpc */
    539 			threadexits(nil);
    540 		}
    541 	}
    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);
    552 		}
    553 	}
    554 }
    555 
    556 
    557 /* connect to pop3 server, sync mailbox */
    558 static char*
    559 pop3sync(Mailbox *mb, int doplumb)
    560 {
    561 	char *err;
    562 	Pop *pop;
    563 
    564 	pop = mb->aux;
    565 
    566 	if(err = pop3dial(pop)) {
    567 		mb->waketime = time(0) + pop->refreshtime;
    568 		return err;
    569 	}
    570 
    571 	if((err = pop3read(pop, mb, doplumb)) == nil){
    572 		pop3purge(pop, mb);
    573 		mb->d->atime = mb->d->mtime = time(0);
    574 	}
    575 	pop3hangup(pop);
    576 	mb->waketime = time(0) + pop->refreshtime;
    577 	return err;
    578 }
    579 
    580 static char Epop3ctl[] = "bad pop3 control message";
    581 
    582 static char*
    583 pop3ctl(Mailbox *mb, int argc, char **argv)
    584 {
    585 	int n;
    586 	Pop *pop;
    587 	char *m, *me;
    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"); jpc */
    607 		m = unsharp("#9/sys/lib/tls/mail");
    608 		me = unsharp("#9/sys/lib/tls/mail.exclude");
    609 		pop->thumb = initThumbprints(m, me);
    610 	}
    611 	if(strcmp(argv[0], "refresh")==0){
    612 		if(argc==1){
    613 			pop->refreshtime = 60;
    614 			return nil;
    615 		}
    616 		if(argc==2){
    617 			n = atoi(argv[1]);
    618 			if(n < 15)
    619 				return Epop3ctl;
    620 			pop->refreshtime = n;
    621 			return nil;
    622 		}
    623 	}
    624 
    625 	return Epop3ctl;
    626 }
    627 
    628 /* free extra memory associated with mb */
    629 static void
    630 pop3close(Mailbox *mb)
    631 {
    632 	Pop *pop;
    633 
    634 	pop = mb->aux;
    635 	free(pop->freep);
    636 	free(pop);
    637 }
    638 
    639 /* */
    640 /* open mailboxes of the form /pop/host/user or /apop/host/user */
    641 /* */
    642 char*
    643 pop3mbox(Mailbox *mb, char *path)
    644 {
    645 	char *f[10];
    646 	int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
    647 	Pop *pop;
    648 	char *m, *me;
    649 
    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;
    659 
    660 	if(!ppop && !apop)
    661 		return Enotme;
    662 
    663 	path = strdup(path);
    664 	if(path == nil)
    665 		return "out of memory";
    666 
    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]";
    671 	}
    672 
    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);
    689 
    690 	mb->aux = pop;
    691 	mb->sync = pop3sync;
    692 	mb->close = pop3close;
    693 	mb->ctl = pop3ctl;
    694 	mb->d = emalloc(sizeof(*mb->d));
    695 
    696 	return nil;
    697 }