pop3.c 14 KB

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