plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

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 = &in;
    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 }