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 = ∈
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