xref: /plan9/sys/src/cmd/upas/pop3/pop3.c (revision 21887c0b3fa083db468c32f3247dbe932cf708df)
1 #include "common.h"
2 #include <ctype.h>
3 #include <auth.h>
4 #include <libsec.h>
5 
6 typedef struct Cmd Cmd;
7 struct Cmd
8 {
9 	char *name;
10 	int needauth;
11 	int (*f)(char*);
12 };
13 
14 static void hello(void);
15 static int apopcmd(char*);
16 static int capacmd(char*);
17 static int delecmd(char*);
18 static int listcmd(char*);
19 static int noopcmd(char*);
20 static int passcmd(char*);
21 static int quitcmd(char*);
22 static int rsetcmd(char*);
23 static int retrcmd(char*);
24 static int statcmd(char*);
25 static int stlscmd(char*);
26 static int topcmd(char*);
27 static int synccmd(char*);
28 static int uidlcmd(char*);
29 static int usercmd(char*);
30 static char *nextarg(char*);
31 static int getcrnl(char*, int);
32 static int readmbox(char*);
33 static void sendcrnl(char*, ...);
34 static int senderr(char*, ...);
35 static int sendok(char*, ...);
36 #pragma varargck argpos sendcrnl 1
37 #pragma varargck argpos senderr 1
38 #pragma varargck argpos sendok 1
39 
40 Cmd cmdtab[] =
41 {
42 	"apop", 0, apopcmd,
43 	"capa", 0, capacmd,
44 	"dele", 1, delecmd,
45 	"list", 1, listcmd,
46 	"noop", 0, noopcmd,
47 	"pass", 0, passcmd,
48 	"quit", 0, quitcmd,
49 	"rset", 0, rsetcmd,
50 	"retr", 1, retrcmd,
51 	"stat", 1, statcmd,
52 	"stls", 0, stlscmd,
53 	"sync", 1, synccmd,
54 	"top", 1, topcmd,
55 	"uidl", 1, uidlcmd,
56 	"user", 0, usercmd,
57 	0, 0, 0,
58 };
59 
60 static Biobuf in;
61 static Biobuf out;
62 static int passwordinclear;
63 static int didtls;
64 
65 typedef struct Msg Msg;
66 struct Msg
67 {
68 	int upasnum;
69 	char digest[64];
70 	int bytes;
71 	int deleted;
72 };
73 
74 static int totalbytes;
75 static int totalmsgs;
76 static Msg *msg;
77 static int nmsg;
78 static int loggedin;
79 static int debug;
80 static uchar *tlscert;
81 static int ntlscert;
82 static char *peeraddr;
83 static char tmpaddr[64];
84 
85 void
usage(void)86 usage(void)
87 {
88 	fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p] "
89 		"[-r remote] [-t cert]\n");
90 	exits("usage");
91 }
92 
93 void
main(int argc,char ** argv)94 main(int argc, char **argv)
95 {
96 	int fd;
97 	char *arg, cmdbuf[1024];
98 	Cmd *c;
99 
100 	rfork(RFNAMEG);
101 	Binit(&in, 0, OREAD);
102 	Binit(&out, 1, OWRITE);
103 
104 	ARGBEGIN{
105 	case 'a':
106 		loggedin = 1;
107 		if(readmbox(EARGF(usage())) < 0)
108 			exits(nil);
109 		break;
110 	case 'd':
111 		debug++;
112 		if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
113 			dup(fd, 2);
114 			close(fd);
115 		}
116 		break;
117 	case 'p':
118 		passwordinclear = 1;
119 		break;
120 	case 'r':
121 		strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
122 		if(arg = strchr(tmpaddr, '!'))
123 			*arg = '\0';
124 		peeraddr = tmpaddr;
125 		break;
126 	case 't':
127 		tlscert = readcert(EARGF(usage()), &ntlscert);
128 		if(tlscert == nil){
129 			senderr("cannot read TLS certificate: %r");
130 			exits(nil);
131 		}
132 		break;
133 	}ARGEND
134 
135 	/* do before TLS */
136 	if(peeraddr == nil)
137 		peeraddr = remoteaddr(0,0);
138 
139 	hello();
140 
141 	while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
142 		arg = nextarg(cmdbuf);
143 		for(c=cmdtab; c->name; c++)
144 			if(cistrcmp(c->name, cmdbuf) == 0)
145 				break;
146 		if(c->name == 0){
147 			senderr("unknown command %s", cmdbuf);
148 			continue;
149 		}
150 		if(c->needauth && !loggedin){
151 			senderr("%s requires authentication", cmdbuf);
152 			continue;
153 		}
154 		(*c->f)(arg);
155 	}
156 	exits(nil);
157 }
158 
159 /* sort directories in increasing message number order */
160 static int
dircmp(void * a,void * b)161 dircmp(void *a, void *b)
162 {
163 	return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
164 }
165 
166 static int
readmbox(char * box)167 readmbox(char *box)
168 {
169 	int fd, i, n, nd, lines, pid;
170 	char buf[100], err[Errlen];
171 	char *p;
172 	Biobuf *b;
173 	Dir *d, *draw;
174 	Msg *m;
175 	Waitmsg *w;
176 
177 	unmount(nil, "/mail/fs");
178 	switch(pid = fork()){
179 	case -1:
180 		return senderr("can't fork to start upas/fs");
181 
182 	case 0:
183 		close(0);
184 		close(1);
185 		open("/dev/null", OREAD);
186 		open("/dev/null", OWRITE);
187 		execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
188 		snprint(err, sizeof err, "upas/fs: %r");
189 		_exits(err);
190 		break;
191 
192 	default:
193 		break;
194 	}
195 
196 	if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
197 		if(w && w->pid==pid)
198 			return senderr("%s", w->msg);
199 		else
200 			return senderr("can't initialize upas/fs");
201 	}
202 	free(w);
203 
204 	if(chdir("/mail/fs/mbox") < 0)
205 		return senderr("can't initialize upas/fs: %r");
206 
207 	if((fd = open(".", OREAD)) < 0)
208 		return senderr("cannot open /mail/fs/mbox: %r");
209 	nd = dirreadall(fd, &d);
210 	close(fd);
211 	if(nd < 0)
212 		return senderr("cannot read from /mail/fs/mbox: %r");
213 
214 	msg = mallocz(sizeof(Msg)*nd, 1);
215 	if(msg == nil)
216 		return senderr("out of memory");
217 
218 	if(nd == 0)
219 		return 0;
220 	qsort(d, nd, sizeof(d[0]), dircmp);
221 
222 	for(i=0; i<nd; i++){
223 		m = &msg[nmsg];
224 		m->upasnum = atoi(d[i].name);
225 		sprint(buf, "%d/digest", m->upasnum);
226 		if((fd = open(buf, OREAD)) < 0)
227 			continue;
228 		n = readn(fd, m->digest, sizeof m->digest - 1);
229 		close(fd);
230 		if(n < 0)
231 			continue;
232 		m->digest[n] = '\0';
233 
234 		/*
235 		 * We need the number of message lines so that we
236 		 * can adjust the byte count to include \r's.
237 		 * Upas/fs gives us the number of lines in the raw body
238 		 * in the lines file, but we have to count rawheader ourselves.
239 		 * There is one blank line between raw header and raw body.
240 		 */
241 		sprint(buf, "%d/rawheader", m->upasnum);
242 		if((b = Bopen(buf, OREAD)) == nil)
243 			continue;
244 		lines = 0;
245 		for(;;){
246 			p = Brdline(b, '\n');
247 			if(p == nil){
248 				if((n = Blinelen(b)) == 0)
249 					break;
250 				Bseek(b, n, 1);
251 			}else
252 				lines++;
253 		}
254 		Bterm(b);
255 		lines++;
256 		sprint(buf, "%d/lines", m->upasnum);
257 		if((fd = open(buf, OREAD)) < 0)
258 			continue;
259 		n = readn(fd, buf, sizeof buf - 1);
260 		close(fd);
261 		if(n < 0)
262 			continue;
263 		buf[n] = '\0';
264 		lines += atoi(buf);
265 
266 		sprint(buf, "%d/raw", m->upasnum);
267 		if((draw = dirstat(buf)) == nil)
268 			continue;
269 		m->bytes = lines+draw->length;
270 		free(draw);
271 		nmsg++;
272 		totalmsgs++;
273 		totalbytes += m->bytes;
274 	}
275 	return 0;
276 }
277 
278 /*
279  *  get a line that ends in crnl or cr, turn terminating crnl into a nl
280  *
281  *  return 0 on EOF
282  */
283 static int
getcrnl(char * buf,int n)284 getcrnl(char *buf, int n)
285 {
286 	int c;
287 	char *ep;
288 	char *bp;
289 	Biobuf *fp = &in;
290 
291 	Bflush(&out);
292 
293 	bp = buf;
294 	ep = bp + n - 1;
295 	while(bp != ep){
296 		c = Bgetc(fp);
297 		if(debug) {
298 			seek(2, 0, 2);
299 			fprint(2, "%c", c);
300 		}
301 		switch(c){
302 		case -1:
303 			*bp = 0;
304 			if(bp==buf)
305 				return 0;
306 			else
307 				return bp-buf;
308 		case '\r':
309 			c = Bgetc(fp);
310 			if(c == '\n'){
311 				if(debug) {
312 					seek(2, 0, 2);
313 					fprint(2, "%c", c);
314 				}
315 				*bp = 0;
316 				return bp-buf;
317 			}
318 			Bungetc(fp);
319 			c = '\r';
320 			break;
321 		case '\n':
322 			*bp = 0;
323 			return bp-buf;
324 		}
325 		*bp++ = c;
326 	}
327 	*bp = 0;
328 	return bp-buf;
329 }
330 
331 static void
sendcrnl(char * fmt,...)332 sendcrnl(char *fmt, ...)
333 {
334 	char buf[1024];
335 	va_list arg;
336 
337 	va_start(arg, fmt);
338 	vseprint(buf, buf+sizeof(buf), fmt, arg);
339 	va_end(arg);
340 	if(debug)
341 		fprint(2, "-> %s\n", buf);
342 	Bprint(&out, "%s\r\n", buf);
343 }
344 
345 static int
senderr(char * fmt,...)346 senderr(char *fmt, ...)
347 {
348 	char buf[1024];
349 	va_list arg;
350 
351 	va_start(arg, fmt);
352 	vseprint(buf, buf+sizeof(buf), fmt, arg);
353 	va_end(arg);
354 	if(debug)
355 		fprint(2, "-> -ERR %s\n", buf);
356 	Bprint(&out, "-ERR %s\r\n", buf);
357 	return -1;
358 }
359 
360 static int
sendok(char * fmt,...)361 sendok(char *fmt, ...)
362 {
363 	char buf[1024];
364 	va_list arg;
365 
366 	va_start(arg, fmt);
367 	vseprint(buf, buf+sizeof(buf), fmt, arg);
368 	va_end(arg);
369 	if(*buf){
370 		if(debug)
371 			fprint(2, "-> +OK %s\n", buf);
372 		Bprint(&out, "+OK %s\r\n", buf);
373 	} else {
374 		if(debug)
375 			fprint(2, "-> +OK\n");
376 		Bprint(&out, "+OK\r\n");
377 	}
378 	return 0;
379 }
380 
381 static int
capacmd(char *)382 capacmd(char*)
383 {
384 	sendok("");
385 	sendcrnl("TOP");
386 	if(passwordinclear || didtls)
387 		sendcrnl("USER");
388 	sendcrnl("PIPELINING");
389 	sendcrnl("UIDL");
390 	sendcrnl("STLS");
391 	sendcrnl(".");
392 	return 0;
393 }
394 
395 static int
delecmd(char * arg)396 delecmd(char *arg)
397 {
398 	int n;
399 
400 	if(*arg==0)
401 		return senderr("DELE requires a message number");
402 
403 	n = atoi(arg)-1;
404 	if(n < 0 || n >= nmsg || msg[n].deleted)
405 		return senderr("no such message");
406 
407 	msg[n].deleted = 1;
408 	totalmsgs--;
409 	totalbytes -= msg[n].bytes;
410 	sendok("message %d deleted", n+1);
411 	return 0;
412 }
413 
414 static int
listcmd(char * arg)415 listcmd(char *arg)
416 {
417 	int i, n;
418 
419 	if(*arg == 0){
420 		sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
421 		for(i=0; i<nmsg; i++){
422 			if(msg[i].deleted)
423 				continue;
424 			sendcrnl("%d %d", i+1, msg[i].bytes);
425 		}
426 		sendcrnl(".");
427 	}else{
428 		n = atoi(arg)-1;
429 		if(n < 0 || n >= nmsg || msg[n].deleted)
430 			return senderr("no such message");
431 		sendok("%d %d", n+1, msg[n].bytes);
432 	}
433 	return 0;
434 }
435 
436 static int
noopcmd(char * arg)437 noopcmd(char *arg)
438 {
439 	USED(arg);
440 	sendok("");
441 	return 0;
442 }
443 
444 static void
_synccmd(char *)445 _synccmd(char*)
446 {
447 	int i, fd;
448 	char *s;
449 	Fmt f;
450 
451 	if(!loggedin){
452 		sendok("");
453 		return;
454 	}
455 
456 	fmtstrinit(&f);
457 	fmtprint(&f, "delete mbox");
458 	for(i=0; i<nmsg; i++)
459 		if(msg[i].deleted)
460 			fmtprint(&f, " %d", msg[i].upasnum);
461 	s = fmtstrflush(&f);
462 	if(strcmp(s, "delete mbox") != 0){	/* must have something to delete */
463 		if((fd = open("../ctl", OWRITE)) < 0){
464 			senderr("open ctl to delete messages: %r");
465 			return;
466 		}
467 		if(write(fd, s, strlen(s)) < 0){
468 			senderr("error deleting messages: %r");
469 			return;
470 		}
471 	}
472 	sendok("");
473 }
474 
475 static int
synccmd(char *)476 synccmd(char*)
477 {
478 	_synccmd(nil);
479 	return 0;
480 }
481 
482 static int
quitcmd(char *)483 quitcmd(char*)
484 {
485 	synccmd(nil);
486 	exits(nil);
487 	return 0;
488 }
489 
490 static int
retrcmd(char * arg)491 retrcmd(char *arg)
492 {
493 	int n;
494 	Biobuf *b;
495 	char buf[40], *p;
496 
497 	if(*arg == 0)
498 		return senderr("RETR requires a message number");
499 	n = atoi(arg)-1;
500 	if(n < 0 || n >= nmsg || msg[n].deleted)
501 		return senderr("no such message");
502 	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
503 	if((b = Bopen(buf, OREAD)) == nil)
504 		return senderr("message disappeared");
505 	sendok("");
506 	while((p = Brdstr(b, '\n', 1)) != nil){
507 		if(p[0]=='.')
508 			Bwrite(&out, ".", 1);
509 		Bwrite(&out, p, strlen(p));
510 		Bwrite(&out, "\r\n", 2);
511 		free(p);
512 	}
513 	Bterm(b);
514 	sendcrnl(".");
515 	return 0;
516 }
517 
518 static int
rsetcmd(char *)519 rsetcmd(char*)
520 {
521 	int i;
522 
523 	for(i=0; i<nmsg; i++){
524 		if(msg[i].deleted){
525 			msg[i].deleted = 0;
526 			totalmsgs++;
527 			totalbytes += msg[i].bytes;
528 		}
529 	}
530 	return sendok("");
531 }
532 
533 static int
statcmd(char *)534 statcmd(char*)
535 {
536 	return sendok("%d %d", totalmsgs, totalbytes);
537 }
538 
539 static int
trace(char * fmt,...)540 trace(char *fmt, ...)
541 {
542 	va_list arg;
543 	int n;
544 
545 	va_start(arg, fmt);
546 	n = vfprint(2, fmt, arg);
547 	va_end(arg);
548 	return n;
549 }
550 
551 static int
stlscmd(char *)552 stlscmd(char*)
553 {
554 	int fd;
555 	TLSconn conn;
556 
557 	if(didtls)
558 		return senderr("tls already started");
559 	if(!tlscert)
560 		return senderr("don't have any tls credentials");
561 	sendok("");
562 	Bflush(&out);
563 
564 	memset(&conn, 0, sizeof conn);
565 	conn.cert = tlscert;
566 	conn.certlen = ntlscert;
567 	if(debug)
568 		conn.trace = trace;
569 	fd = tlsServer(0, &conn);
570 	if(fd < 0)
571 		sysfatal("tlsServer: %r");
572 	dup(fd, 0);
573 	dup(fd, 1);
574 	close(fd);
575 	Binit(&in, 0, OREAD);
576 	Binit(&out, 1, OWRITE);
577 	didtls = 1;
578 	return 0;
579 }
580 
581 static int
topcmd(char * arg)582 topcmd(char *arg)
583 {
584 	int done, i, lines, n;
585 	char buf[40], *p;
586 	Biobuf *b;
587 
588 	if(*arg == 0)
589 		return senderr("TOP requires a message number");
590 	n = atoi(arg)-1;
591 	if(n < 0 || n >= nmsg || msg[n].deleted)
592 		return senderr("no such message");
593 	arg = nextarg(arg);
594 	if(*arg == 0)
595 		return senderr("TOP requires a line count");
596 	lines = atoi(arg);
597 	if(lines < 0)
598 		return senderr("bad args to TOP");
599 	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
600 	if((b = Bopen(buf, OREAD)) == nil)
601 		return senderr("message disappeared");
602 	sendok("");
603 	while(p = Brdstr(b, '\n', 1)){
604 		if(p[0]=='.')
605 			Bputc(&out, '.');
606 		Bwrite(&out, p, strlen(p));
607 		Bwrite(&out, "\r\n", 2);
608 		done = p[0]=='\0';
609 		free(p);
610 		if(done)
611 			break;
612 	}
613 	for(i=0; i<lines; i++){
614 		p = Brdstr(b, '\n', 1);
615 		if(p == nil)
616 			break;
617 		if(p[0]=='.')
618 			Bwrite(&out, ".", 1);
619 		Bwrite(&out, p, strlen(p));
620 		Bwrite(&out, "\r\n", 2);
621 		free(p);
622 	}
623 	sendcrnl(".");
624 	Bterm(b);
625 	return 0;
626 }
627 
628 static int
uidlcmd(char * arg)629 uidlcmd(char *arg)
630 {
631 	int n;
632 
633 	if(*arg==0){
634 		sendok("");
635 		for(n=0; n<nmsg; n++){
636 			if(msg[n].deleted)
637 				continue;
638 			sendcrnl("%d %s", n+1, msg[n].digest);
639 		}
640 		sendcrnl(".");
641 	}else{
642 		n = atoi(arg)-1;
643 		if(n < 0 || n >= nmsg || msg[n].deleted)
644 			return senderr("no such message");
645 		sendok("%d %s", n+1, msg[n].digest);
646 	}
647 	return 0;
648 }
649 
650 static char*
nextarg(char * p)651 nextarg(char *p)
652 {
653 	while(*p && *p != ' ' && *p != '\t')
654 		p++;
655 	while(*p == ' ' || *p == '\t')
656 		*p++ = 0;
657 	return p;
658 }
659 
660 /*
661  * authentication
662  */
663 Chalstate *chs;
664 char user[256];
665 char box[256];
666 char cbox[256];
667 
668 static void
hello(void)669 hello(void)
670 {
671 	fmtinstall('H', encodefmt);
672 	if((chs = auth_challenge("proto=apop role=server")) == nil){
673 		senderr("auth server not responding, try later");
674 		exits(nil);
675 	}
676 
677 	sendok("POP3 server ready %s", chs->chal);
678 }
679 
680 static int
setuser(char * arg)681 setuser(char *arg)
682 {
683 	char *p;
684 
685 	strcpy(box, "/mail/box/");
686 	strecpy(box+strlen(box), box+sizeof box-7, arg);
687 	strcpy(cbox, box);
688 	cleanname(cbox);
689 	if(strcmp(cbox, box) != 0)
690 		return senderr("bad mailbox name");
691 	strcat(box, "/mbox");
692 
693 	strecpy(user, user+sizeof user, arg);
694 	if(p = strchr(user, '/'))
695 		*p = '\0';
696 	return 0;
697 }
698 
699 static int
usercmd(char * arg)700 usercmd(char *arg)
701 {
702 	if(loggedin)
703 		return senderr("already authenticated");
704 	if(*arg == 0)
705 		return senderr("USER requires argument");
706 	if(setuser(arg) < 0)
707 		return -1;
708 	return sendok("");
709 }
710 
711 static void
enableaddr(void)712 enableaddr(void)
713 {
714 	int fd;
715 	char buf[64];
716 
717 	/* hide the peer IP address under a rock in the ratifier FS */
718 	if(peeraddr == 0 || *peeraddr == 0)
719 		return;
720 
721 	sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
722 
723 	/*
724 	 * if the address is already there and the user owns it,
725 	 * remove it and recreate it to give him a new time quanta.
726 	 */
727 	if(access(buf, 0) >= 0  && remove(buf) < 0)
728 		return;
729 
730 	fd = create(buf, OREAD, 0666);
731 	if(fd >= 0){
732 		close(fd);
733 //		syslog(0, "pop3", "ratified %s", peeraddr);
734 	}
735 }
736 
737 static int
dologin(char * response)738 dologin(char *response)
739 {
740 	AuthInfo *ai;
741 	static int tries;
742 	static ulong delaysecs = 5;
743 
744 	chs->user = user;
745 	chs->resp = response;
746 	chs->nresp = strlen(response);
747 	if((ai = auth_response(chs)) == nil){
748 		if(tries >= 20){
749 			senderr("authentication failed: %r; server exiting");
750 			exits(nil);
751 		}
752 		if(++tries == 3)
753 			syslog(0, "pop3", "likely password guesser from %s",
754 				peeraddr);
755 		delaysecs *= 2;
756 		if (delaysecs > 30*60)
757 			delaysecs = 30*60;		/* half-hour max. */
758 		sleep(delaysecs * 1000); /* prevent beating on our auth server */
759 		return senderr("authentication failed");
760 	}
761 
762 	if(auth_chuid(ai, nil) < 0){
763 		senderr("chuid failed: %r; server exiting");
764 		exits(nil);
765 	}
766 	auth_freeAI(ai);
767 	auth_freechal(chs);
768 	chs = nil;
769 
770 	loggedin = 1;
771 	if(newns(user, 0) < 0){
772 		senderr("newns failed: %r; server exiting");
773 		exits(nil);
774 	}
775 	syslog(0, "pop3", "user %s logged in", user);
776 	enableaddr();
777 	if(readmbox(box) < 0)
778 		exits(nil);
779 	return sendok("mailbox is %s", box);
780 }
781 
782 static int
passcmd(char * arg)783 passcmd(char *arg)
784 {
785 	DigestState *s;
786 	uchar digest[MD5dlen];
787 	char response[2*MD5dlen+1];
788 
789 	if(passwordinclear==0 && didtls==0)
790 		return senderr("password in the clear disallowed");
791 
792 	/* use password to encode challenge */
793 	if((chs = auth_challenge("proto=apop role=server")) == nil)
794 		return senderr("couldn't get apop challenge");
795 
796 	/* hash challenge with secret and convert to ascii */
797 	s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
798 	md5((uchar*)arg, strlen(arg), digest, s);
799 	snprint(response, sizeof response, "%.*H", MD5dlen, digest);
800 	return dologin(response);
801 }
802 
803 static int
apopcmd(char * arg)804 apopcmd(char *arg)
805 {
806 	char *resp;
807 
808 	resp = nextarg(arg);
809 	if(setuser(arg) < 0)
810 		return -1;
811 	return dologin(resp);
812 }
813 
814