pop3.c (14326B)
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 86 usage(void) 87 { 88 fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n"); 89 exits("usage"); 90 } 91 92 void 93 main(int argc, char **argv) 94 { 95 int fd; 96 char *arg, cmdbuf[1024]; 97 Cmd *c; 98 99 rfork(RFNAMEG); 100 Binit(&in, 0, OREAD); 101 Binit(&out, 1, OWRITE); 102 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); 114 } 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); 127 } 128 break; 129 case 'p': 130 passwordinclear = 1; 131 break; 132 }ARGEND 133 134 /* do before TLS */ 135 if(peeraddr == nil) 136 peeraddr = remoteaddr(0,0); 137 138 hello(); 139 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; 148 } 149 if(c->needauth && !loggedin){ 150 senderr("%s requires authentication", cmdbuf); 151 continue; 152 } 153 (*c->f)(arg); 154 } 155 exits(nil); 156 } 157 158 /* sort directories in increasing message number order */ 159 static int 160 dircmp(void *a, void *b) 161 { 162 return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name); 163 } 164 165 static int 166 readmbox(char *box) 167 { 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; 175 176 unmount(nil, "/mail/fs"); 177 switch(pid = fork()){ 178 case -1: 179 return senderr("can't fork to start upas/fs"); 180 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; 190 191 default: 192 break; 193 } 194 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"); 200 } 201 free(w); 202 203 if(chdir("/mail/fs/mbox") < 0) 204 return senderr("can't initialize upas/fs: %r"); 205 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"); 212 213 msg = mallocz(sizeof(Msg)*nd, 1); 214 if(msg == nil) 215 return senderr("out of memory"); 216 217 if(nd == 0) 218 return 0; 219 qsort(d, nd, sizeof(d[0]), dircmp); 220 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'; 232 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++; 252 } 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); 264 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; 273 } 274 return 0; 275 } 276 277 /* 278 * get a line that ends in crnl or cr, turn terminating crnl into a nl 279 * 280 * return 0 on EOF 281 */ 282 static int 283 getcrnl(char *buf, int n) 284 { 285 int c; 286 char *ep; 287 char *bp; 288 Biobuf *fp = ∈ 289 290 Bflush(&out); 291 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); 299 } 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); 313 } 314 *bp = 0; 315 return bp-buf; 316 } 317 Bungetc(fp); 318 c = '\r'; 319 break; 320 case '\n': 321 *bp = 0; 322 return bp-buf; 323 } 324 *bp++ = c; 325 } 326 *bp = 0; 327 return bp-buf; 328 } 329 330 static void 331 sendcrnl(char *fmt, ...) 332 { 333 char buf[1024]; 334 va_list arg; 335 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); 342 } 343 344 static int 345 senderr(char *fmt, ...) 346 { 347 char buf[1024]; 348 va_list arg; 349 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; 357 } 358 359 static int 360 sendok(char *fmt, ...) 361 { 362 char buf[1024]; 363 va_list arg; 364 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"); 376 } 377 return 0; 378 } 379 380 static int 381 capacmd(char*) 382 { 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; 392 } 393 394 static int 395 delecmd(char *arg) 396 { 397 int n; 398 399 if(*arg==0) 400 return senderr("DELE requires a message number"); 401 402 n = atoi(arg)-1; 403 if(n < 0 || n >= nmsg || msg[n].deleted) 404 return senderr("no such message"); 405 406 msg[n].deleted = 1; 407 totalmsgs--; 408 totalbytes -= msg[n].bytes; 409 sendok("message %d deleted", n+1); 410 return 0; 411 } 412 413 static int 414 listcmd(char *arg) 415 { 416 int i, n; 417 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); 424 } 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); 431 } 432 return 0; 433 } 434 435 static int 436 noopcmd(char *arg) 437 { 438 USED(arg); 439 sendok(""); 440 return 0; 441 } 442 443 static void 444 _synccmd(char*) 445 { 446 int i, fd; 447 char *s; 448 Fmt f; 449 450 if(!loggedin){ 451 sendok(""); 452 return; 453 } 454 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; 465 } 466 if(write(fd, s, strlen(s)) < 0){ 467 senderr("error deleting messages: %r"); 468 return; 469 } 470 } 471 sendok(""); 472 } 473 474 static int 475 synccmd(char*) 476 { 477 _synccmd(nil); 478 return 0; 479 } 480 481 static int 482 quitcmd(char*) 483 { 484 synccmd(nil); 485 exits(nil); 486 return 0; 487 } 488 489 static int 490 retrcmd(char *arg) 491 { 492 int n; 493 Biobuf *b; 494 char buf[40], *p; 495 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); 511 } 512 Bterm(b); 513 sendcrnl("."); 514 return 0; 515 } 516 517 static int 518 rsetcmd(char*) 519 { 520 int i; 521 522 for(i=0; i<nmsg; i++){ 523 if(msg[i].deleted){ 524 msg[i].deleted = 0; 525 totalmsgs++; 526 totalbytes += msg[i].bytes; 527 } 528 } 529 return sendok(""); 530 } 531 532 static int 533 statcmd(char*) 534 { 535 return sendok("%d %d", totalmsgs, totalbytes); 536 } 537 538 static int 539 trace(char *fmt, ...) 540 { 541 va_list arg; 542 int n; 543 544 va_start(arg, fmt); 545 n = vfprint(2, fmt, arg); 546 va_end(arg); 547 return n; 548 } 549 550 static int 551 stlscmd(char*) 552 { 553 int fd; 554 TLSconn conn; 555 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); 562 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; 578 } 579 580 static int 581 topcmd(char *arg) 582 { 583 int done, i, lines, n; 584 char buf[40], *p; 585 Biobuf *b; 586 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; 611 } 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); 621 } 622 sendcrnl("."); 623 Bterm(b); 624 return 0; 625 } 626 627 static int 628 uidlcmd(char *arg) 629 { 630 int n; 631 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); 638 } 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); 645 } 646 return 0; 647 } 648 649 static char* 650 nextarg(char *p) 651 { 652 while(*p && *p != ' ' && *p != '\t') 653 p++; 654 while(*p == ' ' || *p == '\t') 655 *p++ = 0; 656 return p; 657 } 658 659 /* 660 * authentication 661 */ 662 Chalstate *chs; 663 char user[256]; 664 char box[256]; 665 char cbox[256]; 666 667 static void 668 hello(void) 669 { 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); 674 } 675 676 sendok("POP3 server ready %s", chs->chal); 677 } 678 679 static int 680 setuser(char *arg) 681 { 682 char *p; 683 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"); 691 692 strecpy(user, user+sizeof user, arg); 693 if(p = strchr(user, '/')) 694 *p = '\0'; 695 return 0; 696 } 697 698 static int 699 usercmd(char *arg) 700 { 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(""); 708 } 709 710 static void 711 enableaddr(void) 712 { 713 int fd; 714 char buf[64]; 715 716 /* hide the peer IP address under a rock in the ratifier FS */ 717 if(peeraddr == 0 || *peeraddr == 0) 718 return; 719 720 sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr); 721 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; 728 729 fd = create(buf, OREAD, 0666); 730 if(fd >= 0){ 731 close(fd); 732 /* syslog(0, "pop3", "ratified %s", peeraddr); */ 733 } 734 } 735 736 static int 737 dologin(char *response) 738 { 739 AuthInfo *ai; 740 static int tries; 741 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); 749 } 750 return senderr("authentication failed"); 751 } 752 753 if(auth_chuid(ai, nil) < 0){ 754 senderr("chuid failed: %r; server exiting"); 755 exits(nil); 756 } 757 auth_freeAI(ai); 758 auth_freechal(chs); 759 chs = nil; 760 761 loggedin = 1; 762 if(newns(user, 0) < 0){ 763 senderr("newns failed: %r; server exiting"); 764 exits(nil); 765 } 766 767 enableaddr(); 768 if(readmbox(box) < 0) 769 exits(nil); 770 return sendok("mailbox is %s", box); 771 } 772 773 static int 774 passcmd(char *arg) 775 { 776 DigestState *s; 777 uchar digest[MD5dlen]; 778 char response[2*MD5dlen+1]; 779 780 if(passwordinclear==0 && didtls==0) 781 return senderr("password in the clear disallowed"); 782 783 /* use password to encode challenge */ 784 if((chs = auth_challenge("proto=apop role=server")) == nil) 785 return senderr("couldn't get apop challenge"); 786 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); 792 } 793 794 static int 795 apopcmd(char *arg) 796 { 797 char *resp; 798 799 resp = nextarg(arg); 800 if(setuser(arg) < 0) 801 return -1; 802 return dologin(resp); 803 }