Blob


1 #include "common.h"
2 #include <ctype.h>
3 #include <auth.h>
4 #include <libsec.h>
6 typedef struct Cmd Cmd;
7 struct Cmd
8 {
9 char *name;
10 int needauth;
11 int (*f)(char*);
12 };
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
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 };
60 static Biobuf in;
61 static Biobuf out;
62 static int passwordinclear;
63 static int didtls;
65 typedef struct Msg Msg;
66 struct Msg
67 {
68 int upasnum;
69 char digest[64];
70 int bytes;
71 int deleted;
72 };
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];
85 void
86 usage(void)
87 {
88 fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n");
89 exits("usage");
90 }
92 void
93 main(int argc, char **argv)
94 {
95 int fd;
96 char *arg, cmdbuf[1024];
97 Cmd *c;
99 rfork(RFNAMEG);
100 Binit(&in, 0, OREAD);
101 Binit(&out, 1, OWRITE);
103 ARGBEGIN{
104 case 'a':
105 loggedin = 1;
106 if(readmbox(EARGF(usage())) < 0)
107 exits(nil);
108 break;
109 case 'd':
110 debug++;
111 if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
112 dup(fd, 2);
113 close(fd);
115 break;
116 case 'r':
117 strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
118 if(arg = strchr(tmpaddr, '!'))
119 *arg = '\0';
120 peeraddr = tmpaddr;
121 break;
122 case 't':
123 tlscert = readcert(EARGF(usage()), &ntlscert);
124 if(tlscert == nil){
125 senderr("cannot read TLS certificate: %r");
126 exits(nil);
128 break;
129 case 'p':
130 passwordinclear = 1;
131 break;
132 }ARGEND
134 /* do before TLS */
135 if(peeraddr == nil)
136 peeraddr = remoteaddr(0,0);
138 hello();
140 while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
141 arg = nextarg(cmdbuf);
142 for(c=cmdtab; c->name; c++)
143 if(cistrcmp(c->name, cmdbuf) == 0)
144 break;
145 if(c->name == 0){
146 senderr("unknown command %s", cmdbuf);
147 continue;
149 if(c->needauth && !loggedin){
150 senderr("%s requires authentication", cmdbuf);
151 continue;
153 (*c->f)(arg);
155 exits(nil);
158 /* sort directories in increasing message number order */
159 static int
160 dircmp(void *a, void *b)
162 return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
165 static int
166 readmbox(char *box)
168 int fd, i, n, nd, lines, pid;
169 char buf[100], err[ERRMAX];
170 char *p;
171 Biobuf *b;
172 Dir *d, *draw;
173 Msg *m;
174 Waitmsg *w;
176 unmount(nil, "/mail/fs");
177 switch(pid = fork()){
178 case -1:
179 return senderr("can't fork to start upas/fs");
181 case 0:
182 close(0);
183 close(1);
184 open("/dev/null", OREAD);
185 open("/dev/null", OWRITE);
186 execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
187 snprint(err, sizeof err, "upas/fs: %r");
188 _exits(err);
189 break;
191 default:
192 break;
195 if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
196 if(w && w->pid==pid)
197 return senderr("%s", w->msg);
198 else
199 return senderr("can't initialize upas/fs");
201 free(w);
203 if(chdir("/mail/fs/mbox") < 0)
204 return senderr("can't initialize upas/fs: %r");
206 if((fd = open(".", OREAD)) < 0)
207 return senderr("cannot open /mail/fs/mbox: %r");
208 nd = dirreadall(fd, &d);
209 close(fd);
210 if(nd < 0)
211 return senderr("cannot read from /mail/fs/mbox: %r");
213 msg = mallocz(sizeof(Msg)*nd, 1);
214 if(msg == nil)
215 return senderr("out of memory");
217 if(nd == 0)
218 return 0;
219 qsort(d, nd, sizeof(d[0]), dircmp);
221 for(i=0; i<nd; i++){
222 m = &msg[nmsg];
223 m->upasnum = atoi(d[i].name);
224 sprint(buf, "%d/digest", m->upasnum);
225 if((fd = open(buf, OREAD)) < 0)
226 continue;
227 n = readn(fd, m->digest, sizeof m->digest - 1);
228 close(fd);
229 if(n < 0)
230 continue;
231 m->digest[n] = '\0';
233 /*
234 * We need the number of message lines so that we
235 * can adjust the byte count to include \r's.
236 * Upas/fs gives us the number of lines in the raw body
237 * in the lines file, but we have to count rawheader ourselves.
238 * There is one blank line between raw header and raw body.
239 */
240 sprint(buf, "%d/rawheader", m->upasnum);
241 if((b = Bopen(buf, OREAD)) == nil)
242 continue;
243 lines = 0;
244 for(;;){
245 p = Brdline(b, '\n');
246 if(p == nil){
247 if((n = Blinelen(b)) == 0)
248 break;
249 Bseek(b, n, 1);
250 }else
251 lines++;
253 Bterm(b);
254 lines++;
255 sprint(buf, "%d/lines", m->upasnum);
256 if((fd = open(buf, OREAD)) < 0)
257 continue;
258 n = readn(fd, buf, sizeof buf - 1);
259 close(fd);
260 if(n < 0)
261 continue;
262 buf[n] = '\0';
263 lines += atoi(buf);
265 sprint(buf, "%d/raw", m->upasnum);
266 if((draw = dirstat(buf)) == nil)
267 continue;
268 m->bytes = lines+draw->length;
269 free(draw);
270 nmsg++;
271 totalmsgs++;
272 totalbytes += m->bytes;
274 return 0;
277 /*
278 * get a line that ends in crnl or cr, turn terminating crnl into a nl
280 * return 0 on EOF
281 */
282 static int
283 getcrnl(char *buf, int n)
285 int c;
286 char *ep;
287 char *bp;
288 Biobuf *fp = &in;
290 Bflush(&out);
292 bp = buf;
293 ep = bp + n - 1;
294 while(bp != ep){
295 c = Bgetc(fp);
296 if(debug) {
297 seek(2, 0, 2);
298 fprint(2, "%c", c);
300 switch(c){
301 case -1:
302 *bp = 0;
303 if(bp==buf)
304 return 0;
305 else
306 return bp-buf;
307 case '\r':
308 c = Bgetc(fp);
309 if(c == '\n'){
310 if(debug) {
311 seek(2, 0, 2);
312 fprint(2, "%c", c);
314 *bp = 0;
315 return bp-buf;
317 Bungetc(fp);
318 c = '\r';
319 break;
320 case '\n':
321 *bp = 0;
322 return bp-buf;
324 *bp++ = c;
326 *bp = 0;
327 return bp-buf;
330 static void
331 sendcrnl(char *fmt, ...)
333 char buf[1024];
334 va_list arg;
336 va_start(arg, fmt);
337 vseprint(buf, buf+sizeof(buf), fmt, arg);
338 va_end(arg);
339 if(debug)
340 fprint(2, "-> %s\n", buf);
341 Bprint(&out, "%s\r\n", buf);
344 static int
345 senderr(char *fmt, ...)
347 char buf[1024];
348 va_list arg;
350 va_start(arg, fmt);
351 vseprint(buf, buf+sizeof(buf), fmt, arg);
352 va_end(arg);
353 if(debug)
354 fprint(2, "-> -ERR %s\n", buf);
355 Bprint(&out, "-ERR %s\r\n", buf);
356 return -1;
359 static int
360 sendok(char *fmt, ...)
362 char buf[1024];
363 va_list arg;
365 va_start(arg, fmt);
366 vseprint(buf, buf+sizeof(buf), fmt, arg);
367 va_end(arg);
368 if(*buf){
369 if(debug)
370 fprint(2, "-> +OK %s\n", buf);
371 Bprint(&out, "+OK %s\r\n", buf);
372 } else {
373 if(debug)
374 fprint(2, "-> +OK\n");
375 Bprint(&out, "+OK\r\n");
377 return 0;
380 static int
381 capacmd(char*)
383 sendok("");
384 sendcrnl("TOP");
385 if(passwordinclear || didtls)
386 sendcrnl("USER");
387 sendcrnl("PIPELINING");
388 sendcrnl("UIDL");
389 sendcrnl("STLS");
390 sendcrnl(".");
391 return 0;
394 static int
395 delecmd(char *arg)
397 int n;
399 if(*arg==0)
400 return senderr("DELE requires a message number");
402 n = atoi(arg)-1;
403 if(n < 0 || n >= nmsg || msg[n].deleted)
404 return senderr("no such message");
406 msg[n].deleted = 1;
407 totalmsgs--;
408 totalbytes -= msg[n].bytes;
409 sendok("message %d deleted", n+1);
410 return 0;
413 static int
414 listcmd(char *arg)
416 int i, n;
418 if(*arg == 0){
419 sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
420 for(i=0; i<nmsg; i++){
421 if(msg[i].deleted)
422 continue;
423 sendcrnl("%d %d", i+1, msg[i].bytes);
425 sendcrnl(".");
426 }else{
427 n = atoi(arg)-1;
428 if(n < 0 || n >= nmsg || msg[n].deleted)
429 return senderr("no such message");
430 sendok("%d %d", n+1, msg[n].bytes);
432 return 0;
435 static int
436 noopcmd(char *arg)
438 USED(arg);
439 sendok("");
440 return 0;
443 static void
444 _synccmd(char*)
446 int i, fd;
447 char *s;
448 Fmt f;
450 if(!loggedin){
451 sendok("");
452 return;
455 fmtstrinit(&f);
456 fmtprint(&f, "delete mbox");
457 for(i=0; i<nmsg; i++)
458 if(msg[i].deleted)
459 fmtprint(&f, " %d", msg[i].upasnum);
460 s = fmtstrflush(&f);
461 if(strcmp(s, "delete mbox") != 0){ /* must have something to delete */
462 if((fd = open("../ctl", OWRITE)) < 0){
463 senderr("open ctl to delete messages: %r");
464 return;
466 if(write(fd, s, strlen(s)) < 0){
467 senderr("error deleting messages: %r");
468 return;
471 sendok("");
474 static int
475 synccmd(char*)
477 _synccmd(nil);
478 return 0;
481 static int
482 quitcmd(char*)
484 synccmd(nil);
485 exits(nil);
486 return 0;
489 static int
490 retrcmd(char *arg)
492 int n;
493 Biobuf *b;
494 char buf[40], *p;
496 if(*arg == 0)
497 return senderr("RETR requires a message number");
498 n = atoi(arg)-1;
499 if(n < 0 || n >= nmsg || msg[n].deleted)
500 return senderr("no such message");
501 snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
502 if((b = Bopen(buf, OREAD)) == nil)
503 return senderr("message disappeared");
504 sendok("");
505 while((p = Brdstr(b, '\n', 1)) != nil){
506 if(p[0]=='.')
507 Bwrite(&out, ".", 1);
508 Bwrite(&out, p, strlen(p));
509 Bwrite(&out, "\r\n", 2);
510 free(p);
512 Bterm(b);
513 sendcrnl(".");
514 return 0;
517 static int
518 rsetcmd(char*)
520 int i;
522 for(i=0; i<nmsg; i++){
523 if(msg[i].deleted){
524 msg[i].deleted = 0;
525 totalmsgs++;
526 totalbytes += msg[i].bytes;
529 return sendok("");
532 static int
533 statcmd(char*)
535 return sendok("%d %d", totalmsgs, totalbytes);
538 static int
539 trace(char *fmt, ...)
541 va_list arg;
542 int n;
544 va_start(arg, fmt);
545 n = vfprint(2, fmt, arg);
546 va_end(arg);
547 return n;
550 static int
551 stlscmd(char*)
553 int fd;
554 TLSconn conn;
556 if(didtls)
557 return senderr("tls already started");
558 if(!tlscert)
559 return senderr("don't have any tls credentials");
560 sendok("");
561 Bflush(&out);
563 memset(&conn, 0, sizeof conn);
564 conn.cert = tlscert;
565 conn.certlen = ntlscert;
566 if(debug)
567 conn.trace = trace;
568 fd = tlsServer(0, &conn);
569 if(fd < 0)
570 sysfatal("tlsServer: %r");
571 dup(fd, 0);
572 dup(fd, 1);
573 close(fd);
574 Binit(&in, 0, OREAD);
575 Binit(&out, 1, OWRITE);
576 didtls = 1;
577 return 0;
580 static int
581 topcmd(char *arg)
583 int done, i, lines, n;
584 char buf[40], *p;
585 Biobuf *b;
587 if(*arg == 0)
588 return senderr("TOP requires a message number");
589 n = atoi(arg)-1;
590 if(n < 0 || n >= nmsg || msg[n].deleted)
591 return senderr("no such message");
592 arg = nextarg(arg);
593 if(*arg == 0)
594 return senderr("TOP requires a line count");
595 lines = atoi(arg);
596 if(lines < 0)
597 return senderr("bad args to TOP");
598 snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
599 if((b = Bopen(buf, OREAD)) == nil)
600 return senderr("message disappeared");
601 sendok("");
602 while(p = Brdstr(b, '\n', 1)){
603 if(p[0]=='.')
604 Bputc(&out, '.');
605 Bwrite(&out, p, strlen(p));
606 Bwrite(&out, "\r\n", 2);
607 done = p[0]=='\0';
608 free(p);
609 if(done)
610 break;
612 for(i=0; i<lines; i++){
613 p = Brdstr(b, '\n', 1);
614 if(p == nil)
615 break;
616 if(p[0]=='.')
617 Bwrite(&out, ".", 1);
618 Bwrite(&out, p, strlen(p));
619 Bwrite(&out, "\r\n", 2);
620 free(p);
622 sendcrnl(".");
623 Bterm(b);
624 return 0;
627 static int
628 uidlcmd(char *arg)
630 int n;
632 if(*arg==0){
633 sendok("");
634 for(n=0; n<nmsg; n++){
635 if(msg[n].deleted)
636 continue;
637 sendcrnl("%d %s", n+1, msg[n].digest);
639 sendcrnl(".");
640 }else{
641 n = atoi(arg)-1;
642 if(n < 0 || n >= nmsg || msg[n].deleted)
643 return senderr("no such message");
644 sendok("%d %s", n+1, msg[n].digest);
646 return 0;
649 static char*
650 nextarg(char *p)
652 while(*p && *p != ' ' && *p != '\t')
653 p++;
654 while(*p == ' ' || *p == '\t')
655 *p++ = 0;
656 return p;
659 /*
660 * authentication
661 */
662 Chalstate *chs;
663 char user[256];
664 char box[256];
665 char cbox[256];
667 static void
668 hello(void)
670 fmtinstall('H', encodefmt);
671 if((chs = auth_challenge("proto=apop role=server")) == nil){
672 senderr("auth server not responding, try later");
673 exits(nil);
676 sendok("POP3 server ready %s", chs->chal);
679 static int
680 setuser(char *arg)
682 char *p;
684 strcpy(box, "/mail/box/");
685 strecpy(box+strlen(box), box+sizeof box-7, arg);
686 strcpy(cbox, box);
687 cleanname(cbox);
688 if(strcmp(cbox, box) != 0)
689 return senderr("bad mailbox name");
690 strcat(box, "/mbox");
692 strecpy(user, user+sizeof user, arg);
693 if(p = strchr(user, '/'))
694 *p = '\0';
695 return 0;
698 static int
699 usercmd(char *arg)
701 if(loggedin)
702 return senderr("already authenticated");
703 if(*arg == 0)
704 return senderr("USER requires argument");
705 if(setuser(arg) < 0)
706 return -1;
707 return sendok("");
710 static void
711 enableaddr(void)
713 int fd;
714 char buf[64];
716 /* hide the peer IP address under a rock in the ratifier FS */
717 if(peeraddr == 0 || *peeraddr == 0)
718 return;
720 sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
722 /*
723 * if the address is already there and the user owns it,
724 * remove it and recreate it to give him a new time quanta.
725 */
726 if(access(buf, 0) >= 0 && remove(buf) < 0)
727 return;
729 fd = create(buf, OREAD, 0666);
730 if(fd >= 0){
731 close(fd);
732 // syslog(0, "pop3", "ratified %s", peeraddr);
736 static int
737 dologin(char *response)
739 AuthInfo *ai;
740 static int tries;
742 chs->user = user;
743 chs->resp = response;
744 chs->nresp = strlen(response);
745 if((ai = auth_response(chs)) == nil){
746 if(tries++ >= 5){
747 senderr("authentication failed: %r; server exiting");
748 exits(nil);
750 return senderr("authentication failed");
753 if(auth_chuid(ai, nil) < 0){
754 senderr("chuid failed: %r; server exiting");
755 exits(nil);
757 auth_freeAI(ai);
758 auth_freechal(chs);
759 chs = nil;
761 loggedin = 1;
762 if(newns(user, 0) < 0){
763 senderr("newns failed: %r; server exiting");
764 exits(nil);
767 enableaddr();
768 if(readmbox(box) < 0)
769 exits(nil);
770 return sendok("mailbox is %s", box);
773 static int
774 passcmd(char *arg)
776 DigestState *s;
777 uchar digest[MD5dlen];
778 char response[2*MD5dlen+1];
780 if(passwordinclear==0 && didtls==0)
781 return senderr("password in the clear disallowed");
783 /* use password to encode challenge */
784 if((chs = auth_challenge("proto=apop role=server")) == nil)
785 return senderr("couldn't get apop challenge");
787 // hash challenge with secret and convert to ascii
788 s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
789 md5((uchar*)arg, strlen(arg), digest, s);
790 snprint(response, sizeof response, "%.*H", MD5dlen, digest);
791 return dologin(response);
794 static int
795 apopcmd(char *arg)
797 char *resp;
799 resp = nextarg(arg);
800 if(setuser(arg) < 0)
801 return -1;
802 return dologin(resp);