pop3.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  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((n = Blinelen(b)) == 0)
  223. break;
  224. Bseek(b, n, 1);
  225. }else
  226. lines++;
  227. }
  228. Bterm(b);
  229. lines++;
  230. sprint(buf, "%d/lines", m->upasnum);
  231. if((fd = open(buf, OREAD)) < 0)
  232. continue;
  233. n = readn(fd, buf, sizeof buf - 1);
  234. close(fd);
  235. if(n < 0)
  236. continue;
  237. buf[n] = '\0';
  238. lines += atoi(buf);
  239. sprint(buf, "%d/raw", m->upasnum);
  240. if((draw = dirstat(buf)) == nil)
  241. continue;
  242. m->bytes = lines+draw->length;
  243. free(draw);
  244. nmsg++;
  245. totalmsgs++;
  246. totalbytes += m->bytes;
  247. }
  248. return 0;
  249. }
  250. /*
  251. * get a line that ends in crnl or cr, turn terminating crnl into a nl
  252. *
  253. * return 0 on EOF
  254. */
  255. static int
  256. getcrnl(char *buf, int n)
  257. {
  258. int c;
  259. char *ep;
  260. char *bp;
  261. Biobuf *fp = &in;
  262. Bflush(&out);
  263. bp = buf;
  264. ep = bp + n - 1;
  265. while(bp != ep){
  266. c = Bgetc(fp);
  267. if(debug) {
  268. seek(2, 0, 2);
  269. fprint(2, "%c", c);
  270. }
  271. switch(c){
  272. case -1:
  273. *bp = 0;
  274. if(bp==buf)
  275. return 0;
  276. else
  277. return bp-buf;
  278. case '\r':
  279. c = Bgetc(fp);
  280. if(c == '\n'){
  281. if(debug) {
  282. seek(2, 0, 2);
  283. fprint(2, "%c", c);
  284. }
  285. *bp = 0;
  286. return bp-buf;
  287. }
  288. Bungetc(fp);
  289. c = '\r';
  290. break;
  291. case '\n':
  292. *bp = 0;
  293. return bp-buf;
  294. }
  295. *bp++ = c;
  296. }
  297. *bp = 0;
  298. return bp-buf;
  299. }
  300. static void
  301. sendcrnl(char *fmt, ...)
  302. {
  303. char buf[1024];
  304. va_list arg;
  305. va_start(arg, fmt);
  306. vseprint(buf, buf+sizeof(buf), fmt, arg);
  307. va_end(arg);
  308. if(debug)
  309. fprint(2, "-> %s\n", buf);
  310. Bprint(&out, "%s\r\n", buf);
  311. }
  312. static int
  313. senderr(char *fmt, ...)
  314. {
  315. char buf[1024];
  316. va_list arg;
  317. va_start(arg, fmt);
  318. vseprint(buf, buf+sizeof(buf), fmt, arg);
  319. va_end(arg);
  320. if(debug)
  321. fprint(2, "-> -ERR %s\n", buf);
  322. Bprint(&out, "-ERR %s\r\n", buf);
  323. return -1;
  324. }
  325. static int
  326. sendok(char *fmt, ...)
  327. {
  328. char buf[1024];
  329. va_list arg;
  330. va_start(arg, fmt);
  331. vseprint(buf, buf+sizeof(buf), fmt, arg);
  332. va_end(arg);
  333. if(*buf){
  334. if(debug)
  335. fprint(2, "-> +OK %s\n", buf);
  336. Bprint(&out, "+OK %s\r\n", buf);
  337. } else {
  338. if(debug)
  339. fprint(2, "-> +OK\n");
  340. Bprint(&out, "+OK\r\n");
  341. }
  342. return 0;
  343. }
  344. static int
  345. capacmd(char*)
  346. {
  347. sendok("");
  348. sendcrnl("TOP");
  349. if(passwordinclear || didtls)
  350. sendcrnl("USER");
  351. sendcrnl("PIPELINING");
  352. sendcrnl("UIDL");
  353. sendcrnl("STLS");
  354. sendcrnl(".");
  355. return 0;
  356. }
  357. static int
  358. delecmd(char *arg)
  359. {
  360. int n;
  361. if(*arg==0)
  362. return senderr("DELE requires a message number");
  363. n = atoi(arg)-1;
  364. if(n < 0 || n >= nmsg || msg[n].deleted)
  365. return senderr("no such message");
  366. msg[n].deleted = 1;
  367. totalmsgs--;
  368. totalbytes -= msg[n].bytes;
  369. sendok("message %d deleted", n+1);
  370. return 0;
  371. }
  372. static int
  373. listcmd(char *arg)
  374. {
  375. int i, n;
  376. if(*arg == 0){
  377. sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
  378. for(i=0; i<nmsg; i++){
  379. if(msg[i].deleted)
  380. continue;
  381. sendcrnl("%d %d", i+1, msg[i].bytes);
  382. }
  383. sendcrnl(".");
  384. }else{
  385. n = atoi(arg)-1;
  386. if(n < 0 || n >= nmsg || msg[n].deleted)
  387. return senderr("no such message");
  388. sendok("%d %d", n+1, msg[n].bytes);
  389. }
  390. return 0;
  391. }
  392. static int
  393. noopcmd(char *arg)
  394. {
  395. USED(arg);
  396. sendok("");
  397. return 0;
  398. }
  399. static void
  400. _synccmd(char*)
  401. {
  402. int i, fd;
  403. char *s;
  404. Fmt f;
  405. if(!loggedin){
  406. sendok("");
  407. return;
  408. }
  409. fmtstrinit(&f);
  410. fmtprint(&f, "delete mbox");
  411. for(i=0; i<nmsg; i++)
  412. if(msg[i].deleted)
  413. fmtprint(&f, " %d", msg[i].upasnum);
  414. s = fmtstrflush(&f);
  415. if(strcmp(s, "delete mbox") != 0){ /* must have something to delete */
  416. if((fd = open("../ctl", OWRITE)) < 0){
  417. senderr("open ctl to delete messages: %r");
  418. return;
  419. }
  420. if(write(fd, s, strlen(s)) < 0){
  421. senderr("error deleting messages: %r");
  422. return;
  423. }
  424. }
  425. sendok("");
  426. }
  427. static int
  428. synccmd(char*)
  429. {
  430. _synccmd(nil);
  431. return 0;
  432. }
  433. static int
  434. quitcmd(char*)
  435. {
  436. synccmd(nil);
  437. exits(nil);
  438. return 0;
  439. }
  440. static int
  441. retrcmd(char *arg)
  442. {
  443. int n;
  444. Biobuf *b;
  445. char buf[40], *p;
  446. if(*arg == 0)
  447. return senderr("RETR requires a message number");
  448. n = atoi(arg)-1;
  449. if(n < 0 || n >= nmsg || msg[n].deleted)
  450. return senderr("no such message");
  451. snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
  452. if((b = Bopen(buf, OREAD)) == nil)
  453. return senderr("message disappeared");
  454. sendok("");
  455. while((p = Brdstr(b, '\n', 1)) != nil){
  456. if(p[0]=='.')
  457. Bwrite(&out, ".", 1);
  458. Bwrite(&out, p, strlen(p));
  459. Bwrite(&out, "\r\n", 2);
  460. free(p);
  461. }
  462. Bterm(b);
  463. sendcrnl(".");
  464. return 0;
  465. }
  466. static int
  467. rsetcmd(char*)
  468. {
  469. int i;
  470. for(i=0; i<nmsg; i++){
  471. if(msg[i].deleted){
  472. msg[i].deleted = 0;
  473. totalmsgs++;
  474. totalbytes += msg[i].bytes;
  475. }
  476. }
  477. return sendok("");
  478. }
  479. static int
  480. statcmd(char*)
  481. {
  482. return sendok("%d %d", totalmsgs, totalbytes);
  483. }
  484. static int
  485. trace(char *fmt, ...)
  486. {
  487. va_list arg;
  488. int n;
  489. va_start(arg, fmt);
  490. n = vfprint(2, fmt, arg);
  491. va_end(arg);
  492. return n;
  493. }
  494. static int
  495. stlscmd(char*)
  496. {
  497. int fd;
  498. TLSconn conn;
  499. if(didtls)
  500. return senderr("tls already started");
  501. if(!tlscert)
  502. return senderr("don't have any tls credentials");
  503. sendok("");
  504. Bflush(&out);
  505. memset(&conn, 0, sizeof conn);
  506. conn.cert = tlscert;
  507. conn.certlen = ntlscert;
  508. if(debug)
  509. conn.trace = trace;
  510. fd = tlsServer(0, &conn);
  511. if(fd < 0)
  512. sysfatal("tlsServer: %r");
  513. dup(fd, 0);
  514. dup(fd, 1);
  515. close(fd);
  516. Binit(&in, 0, OREAD);
  517. Binit(&out, 1, OWRITE);
  518. didtls = 1;
  519. return 0;
  520. }
  521. static int
  522. topcmd(char *arg)
  523. {
  524. int done, i, lines, n;
  525. char buf[40], *p;
  526. Biobuf *b;
  527. if(*arg == 0)
  528. return senderr("TOP requires a message number");
  529. n = atoi(arg)-1;
  530. if(n < 0 || n >= nmsg || msg[n].deleted)
  531. return senderr("no such message");
  532. arg = nextarg(arg);
  533. if(*arg == 0)
  534. return senderr("TOP requires a line count");
  535. lines = atoi(arg);
  536. if(lines < 0)
  537. return senderr("bad args to TOP");
  538. snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
  539. if((b = Bopen(buf, OREAD)) == nil)
  540. return senderr("message disappeared");
  541. sendok("");
  542. while(p = Brdstr(b, '\n', 1)){
  543. if(p[0]=='.')
  544. Bputc(&out, '.');
  545. Bwrite(&out, p, strlen(p));
  546. Bwrite(&out, "\r\n", 2);
  547. done = p[0]=='\0';
  548. free(p);
  549. if(done)
  550. break;
  551. }
  552. for(i=0; i<lines; i++){
  553. p = Brdstr(b, '\n', 1);
  554. if(p == nil)
  555. break;
  556. if(p[0]=='.')
  557. Bwrite(&out, ".", 1);
  558. Bwrite(&out, p, strlen(p));
  559. Bwrite(&out, "\r\n", 2);
  560. free(p);
  561. }
  562. sendcrnl(".");
  563. Bterm(b);
  564. return 0;
  565. }
  566. static int
  567. uidlcmd(char *arg)
  568. {
  569. int n;
  570. if(*arg==0){
  571. sendok("");
  572. for(n=0; n<nmsg; n++){
  573. if(msg[n].deleted)
  574. continue;
  575. sendcrnl("%d %s", n+1, msg[n].digest);
  576. }
  577. sendcrnl(".");
  578. }else{
  579. n = atoi(arg)-1;
  580. if(n < 0 || n >= nmsg || msg[n].deleted)
  581. return senderr("no such message");
  582. sendok("%d %s", n+1, msg[n].digest);
  583. }
  584. return 0;
  585. }
  586. static char*
  587. nextarg(char *p)
  588. {
  589. while(*p && *p != ' ' && *p != '\t')
  590. p++;
  591. while(*p == ' ' || *p == '\t')
  592. *p++ = 0;
  593. return p;
  594. }
  595. /*
  596. * authentication
  597. */
  598. Chalstate *chs;
  599. char user[256];
  600. char box[256];
  601. char cbox[256];
  602. static void
  603. hello(void)
  604. {
  605. fmtinstall('H', encodefmt);
  606. if((chs = auth_challenge("proto=apop role=server")) == nil){
  607. senderr("auth server not responding, try later");
  608. exits(nil);
  609. }
  610. sendok("POP3 server ready %s", chs->chal);
  611. }
  612. static int
  613. setuser(char *arg)
  614. {
  615. char *p;
  616. strcpy(box, "/mail/box/");
  617. strecpy(box+strlen(box), box+sizeof box-7, arg);
  618. strcpy(cbox, box);
  619. cleanname(cbox);
  620. if(strcmp(cbox, box) != 0)
  621. return senderr("bad mailbox name");
  622. strcat(box, "/mbox");
  623. strecpy(user, user+sizeof user, arg);
  624. if(p = strchr(user, '/'))
  625. *p = '\0';
  626. return 0;
  627. }
  628. static int
  629. usercmd(char *arg)
  630. {
  631. if(loggedin)
  632. return senderr("already authenticated");
  633. if(*arg == 0)
  634. return senderr("USER requires argument");
  635. if(setuser(arg) < 0)
  636. return -1;
  637. return sendok("");
  638. }
  639. static void
  640. enableaddr(void)
  641. {
  642. int fd;
  643. char buf[64];
  644. /* hide the peer IP address under a rock in the ratifier FS */
  645. if(peeraddr == 0 || *peeraddr == 0)
  646. return;
  647. sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
  648. /*
  649. * if the address is already there and the user owns it,
  650. * remove it and recreate it to give him a new time quanta.
  651. */
  652. if(access(buf, 0) >= 0 && remove(buf) < 0)
  653. return;
  654. fd = create(buf, OREAD, 0666);
  655. if(fd >= 0){
  656. close(fd);
  657. // syslog(0, "pop3", "ratified %s", peeraddr);
  658. }
  659. }
  660. static int
  661. dologin(char *response)
  662. {
  663. AuthInfo *ai;
  664. static int tries;
  665. chs->user = user;
  666. chs->resp = response;
  667. chs->nresp = strlen(response);
  668. if((ai = auth_response(chs)) == nil){
  669. if(tries++ >= 5){
  670. senderr("authentication failed: %r; server exiting");
  671. exits(nil);
  672. }
  673. return senderr("authentication failed");
  674. }
  675. if(auth_chuid(ai, nil) < 0){
  676. senderr("chuid failed: %r; server exiting");
  677. exits(nil);
  678. }
  679. auth_freeAI(ai);
  680. auth_freechal(chs);
  681. chs = nil;
  682. loggedin = 1;
  683. if(newns(user, 0) < 0){
  684. senderr("newns failed: %r; server exiting");
  685. exits(nil);
  686. }
  687. syslog(0, "pop3", "user %s logged in", user);
  688. enableaddr();
  689. if(readmbox(box) < 0)
  690. exits(nil);
  691. return sendok("mailbox is %s", box);
  692. }
  693. static int
  694. passcmd(char *arg)
  695. {
  696. DigestState *s;
  697. uchar digest[MD5dlen];
  698. char response[2*MD5dlen+1];
  699. if(passwordinclear==0 && didtls==0)
  700. return senderr("password in the clear disallowed");
  701. /* use password to encode challenge */
  702. if((chs = auth_challenge("proto=apop role=server")) == nil)
  703. return senderr("couldn't get apop challenge");
  704. // hash challenge with secret and convert to ascii
  705. s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
  706. md5((uchar*)arg, strlen(arg), digest, s);
  707. snprint(response, sizeof response, "%.*H", MD5dlen, digest);
  708. return dologin(response);
  709. }
  710. static int
  711. apopcmd(char *arg)
  712. {
  713. char *resp;
  714. resp = nextarg(arg);
  715. if(setuser(arg) < 0)
  716. return -1;
  717. return dologin(resp);
  718. }