| /* network login client */ | 
 | #include <u.h> | 
 | #include <libc.h> | 
 | #include <mp.h> | 
 | #include <libsec.h> | 
 | #include <authsrv.h> | 
 | #include "SConn.h" | 
 | #include "secstore.h" | 
 | enum{ CHK = 16, MAXFILES = 100 }; | 
 |  | 
 | typedef struct AuthConn{ | 
 | 	SConn *conn; | 
 | 	char pass[64]; | 
 | 	int passlen; | 
 | } AuthConn; | 
 |  | 
 | int verbose; | 
 | Nvrsafe nvr; | 
 | char *SECSTORE_DIR; | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "usage: secstore [-cin] [-g getfile] [-p putfile] [-r rmfile] [-s tcp!server!5356] [-u user] [-v]\n"); | 
 | 	exits("usage"); | 
 | } | 
 |  | 
 | static int | 
 | getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey) | 
 | { | 
 | 	int fd = -1; | 
 | 	int i, n, nr, nw, len; | 
 | 	char s[Maxmsg+1]; | 
 | 	uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe; | 
 | 	AESstate aes; | 
 | 	DigestState *sha; | 
 |  | 
 | 	if(strchr(gf, '/')){ | 
 | 		fprint(2, "simple filenames, not paths like %s\n", gf); | 
 | 		return -1; | 
 | 	} | 
 | 	memset(&aes, 0, sizeof aes); | 
 |  | 
 | 	snprint(s, Maxmsg, "GET %s\n", gf); | 
 | 	conn->write(conn, (uchar*)s, strlen(s)); | 
 |  | 
 | 	/* get file size */ | 
 | 	s[0] = '\0'; | 
 | 	bufw = bufe = nil; | 
 | 	if(readstr(conn, s) < 0){ | 
 | 		fprint(2, "remote: %s\n", s); | 
 | 		return -1; | 
 | 	} | 
 | 	len = atoi(s); | 
 | 	if(len == -1){ | 
 | 		fprint(2, "remote file %s does not exist\n", gf); | 
 | 		return -1; | 
 | 	}else if(len == -3){ | 
 | 		fprint(2, "implausible filesize for %s\n", gf); | 
 | 		return -1; | 
 | 	}else if(len < 0){ | 
 | 		fprint(2, "GET refused for %s\n", gf); | 
 | 		return -1; | 
 | 	} | 
 | 	if(buf != nil){ | 
 | 		*buflen = len - AESbsize - CHK; | 
 | 		*buf = bufw = emalloc(len); | 
 | 		bufe = bufw + len; | 
 | 	} | 
 |  | 
 | 	/* directory listing */ | 
 | 	if(strcmp(gf,".")==0){ | 
 | 		if(buf != nil) | 
 | 			*buflen = len; | 
 | 		for(i=0; i < len; i += n){ | 
 | 			if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){ | 
 | 				fprint(2, "empty file chunk\n"); | 
 | 				return -1; | 
 | 			} | 
 | 			if(buf == nil) | 
 | 				write(1, s, n); | 
 | 			else | 
 | 				memmove((*buf)+i, s, n); | 
 | 		} | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* conn is already encrypted against wiretappers,  | 
 | 		but gf is also encrypted against server breakin. */ | 
 | 	if(buf == nil && (fd =create(gf, OWRITE, 0600)) < 0){ | 
 | 		fprint(2, "can't open %s: %r\n", gf); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	ibr = ibw = ib; | 
 | 	for(nr=0; nr < len;){ | 
 | 		if((n = conn->read(conn, ibw, Maxmsg)) <= 0){ | 
 | 			fprint(2, "empty file chunk n=%d nr=%d len=%d: %r\n", n, nr, len); | 
 | 			return -1; | 
 | 		} | 
 | 		nr += n; | 
 | 		ibw += n; | 
 | 		if(!aes.setup){ /* first time, read 16 byte IV */ | 
 | 			if(n < AESbsize){ | 
 | 				fprint(2, "no IV in file\n"); | 
 | 				return -1; | 
 | 			} | 
 | 			sha = sha1((uchar*)"aescbc file", 11, nil, nil); | 
 | 			sha1(key, nkey, skey, sha); | 
 | 			setupAESstate(&aes, skey, AESbsize, ibr); | 
 | 			memset(skey, 0, sizeof skey); | 
 | 			ibr += AESbsize; | 
 | 			n -= AESbsize; | 
 | 		} | 
 | 		aesCBCdecrypt(ibw-n, n, &aes); | 
 | 		n = ibw-ibr-CHK; | 
 | 		if(n > 0){ | 
 | 			if(buf == nil){ | 
 | 				nw = write(fd, ibr, n); | 
 | 				if(nw != n){ | 
 | 					fprint(2, "write error on %s", gf); | 
 | 					return -1; | 
 | 				} | 
 | 			}else{ | 
 | 				assert(bufw+n <= bufe); | 
 | 				memmove(bufw, ibr, n); | 
 | 				bufw += n; | 
 | 			} | 
 | 			ibr += n; | 
 | 		} | 
 | 		memmove(ib, ibr, ibw-ibr); | 
 | 		ibw = ib + (ibw-ibr); | 
 | 		ibr = ib; | 
 | 	} | 
 | 	if(buf == nil) | 
 | 		close(fd); | 
 | 	n = ibw-ibr; | 
 | 	if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){ | 
 | 			fprint(2,"decrypted file failed to authenticate!\n"); | 
 | 			return -1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* This sends a file to the secstore disk that can, in an emergency, be */ | 
 | /* decrypted by the program aescbc.c. */ | 
 | static int | 
 | putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey) | 
 | { | 
 | 	int i, n, fd, ivo, bufi, done; | 
 | 	char s[Maxmsg]; | 
 | 	uchar  skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize]; | 
 | 	AESstate aes; | 
 | 	DigestState *sha; | 
 |  | 
 | 	/* create initialization vector */ | 
 | 	srand(time(0));  /* doesn't need to be unpredictable */ | 
 | 	for(i=0; i<AESbsize; i++) | 
 | 		IV[i] = 0xff & rand(); | 
 | 	sha = sha1((uchar*)"aescbc file", 11, nil, nil); | 
 | 	sha1(key, nkey, skey, sha); | 
 | 	setupAESstate(&aes, skey, AESbsize, IV); | 
 | 	memset(skey, 0, sizeof skey); | 
 |  | 
 | 	snprint(s, Maxmsg, "PUT %s\n", pf); | 
 | 	conn->write(conn, (uchar*)s, strlen(s)); | 
 |  | 
 | 	if(buf == nil){ | 
 | 		/* get file size */ | 
 | 		if((fd = open(pf, OREAD)) < 0){ | 
 | 			fprint(2, "can't open %s: %r\n", pf); | 
 | 			return -1; | 
 | 		} | 
 | 		len = seek(fd, 0, 2); | 
 | 		seek(fd, 0, 0); | 
 | 	} else { | 
 | 		fd = -1; | 
 | 	} | 
 | 	if(len > MAXFILESIZE){ | 
 | 		fprint(2, "implausible filesize %ld for %s\n", len, pf); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	/* send file size */ | 
 | 	snprint(s, Maxmsg, "%ld", len+AESbsize+CHK); | 
 | 	conn->write(conn, (uchar*)s, strlen(s)); | 
 |  | 
 | 	/* send IV and file+XXXXX in Maxmsg chunks */ | 
 | 	ivo = AESbsize; | 
 | 	bufi = 0; | 
 | 	memcpy(b, IV, ivo); | 
 | 	for(done = 0; !done; ){ | 
 | 		if(buf == nil){ | 
 | 			n = read(fd, b+ivo, Maxmsg-ivo); | 
 | 			if(n < 0){ | 
 | 				fprint(2, "read error on %s: %r\n", pf); | 
 | 				return -1; | 
 | 			} | 
 | 		}else{ | 
 | 			if((n = len - bufi) > Maxmsg-ivo)	 | 
 | 				n = Maxmsg-ivo; | 
 | 			memcpy(b+ivo, buf+bufi, n); | 
 | 			bufi += n; | 
 | 		} | 
 | 		n += ivo; | 
 | 		ivo = 0; | 
 | 		if(n < Maxmsg){ /* EOF on input; append XX... */ | 
 | 			memset(b+n, 'X', CHK); | 
 | 			n += CHK; /* might push n>Maxmsg */ | 
 | 			done = 1; | 
 | 		} | 
 | 		aesCBCencrypt(b, n, &aes); | 
 | 		if(n > Maxmsg){ | 
 | 			assert(done==1); | 
 | 			conn->write(conn, b, Maxmsg); | 
 | 			n -= Maxmsg; | 
 | 			memmove(b, b+Maxmsg, n); | 
 | 		} | 
 | 		conn->write(conn, b, n); | 
 | 	} | 
 |  | 
 | 	if(buf == nil) | 
 | 		close(fd); | 
 | 	fprint(2, "saved %ld bytes\n", len); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | removefile(SConn *conn, char *rf) | 
 | { | 
 | 	char buf[Maxmsg]; | 
 |  | 
 | 	if(strchr(rf, '/')){ | 
 | 		fprint(2, "simple filenames, not paths like %s\n", rf); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	snprint(buf, Maxmsg, "RM %s\n", rf); | 
 | 	conn->write(conn, (uchar*)buf, strlen(buf)); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf) | 
 | { | 
 | 	ulong len; | 
 | 	int rv = -1; | 
 | 	uchar *memfile, *memcur, *memnext; | 
 |  | 
 | 	while(*gf != nil){ | 
 | 		if(verbose) | 
 | 			fprint(2, "get %s\n", *gf); | 
 | 		if(getfile(c->conn, *gf, *Gflag ? &memfile : nil, &len, (uchar*)c->pass, c->passlen) < 0) | 
 | 			goto Out; | 
 | 		if(*Gflag){ | 
 | 			/* write one line at a time, as required by /mnt/factotum/ctl */ | 
 | 			memcur = memfile; | 
 | 			while(len>0){ | 
 | 				memnext = (uchar*)strchr((char*)memcur, '\n'); | 
 | 				if(memnext){ | 
 | 					write(1, memcur, memnext-memcur+1); | 
 | 					len -= memnext-memcur+1; | 
 | 					memcur = memnext+1; | 
 | 				}else{ | 
 | 					write(1, memcur, len); | 
 | 					break; | 
 | 				} | 
 | 			} | 
 | 			free(memfile); | 
 | 		} | 
 | 		gf++; | 
 | 		Gflag++; | 
 | 	} | 
 | 	while(*pf != nil){ | 
 | 		if(verbose) | 
 | 			fprint(2, "put %s\n", *pf); | 
 | 		if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0) | 
 | 			goto Out; | 
 | 		pf++; | 
 | 	} | 
 | 	while(*rf != nil){ | 
 | 		if(verbose) | 
 | 			fprint(2, "rm  %s\n", *rf); | 
 | 		if(removefile(c->conn, *rf) < 0) | 
 | 			goto Out; | 
 | 		rf++; | 
 | 	} | 
 |  | 
 | 	c->conn->write(c->conn, (uchar*)"BYE", 3); | 
 | 	rv = 0; | 
 |  | 
 | Out: | 
 | 	c->conn->free(c->conn); | 
 | 	return rv; | 
 | } | 
 |  | 
 | static int | 
 | chpasswd(AuthConn *c, char *id) | 
 | { | 
 | 	ulong len; | 
 | 	int rv = -1, newpasslen = 0; | 
 | 	mpint *H, *Hi; | 
 | 	uchar *memfile; | 
 | 	char *newpass, *passck; | 
 | 	char *list, *cur, *next, *hexHi; | 
 | 	char *f[8], prompt[128]; | 
 |  | 
 | 	H = mpnew(0); | 
 | 	Hi = mpnew(0); | 
 | 	/* changing our password is vulnerable to connection failure */ | 
 | 	for(;;){ | 
 | 		snprint(prompt, sizeof(prompt), "new password for %s: ", id); | 
 | 		newpass = readcons(prompt, nil, 1); | 
 | 		if(newpass == nil) | 
 | 			goto Out; | 
 | 		if(strlen(newpass) >= 7) | 
 | 			break; | 
 | 		else if(strlen(newpass) == 0){ | 
 | 			fprint(2, "!password change aborted\n"); | 
 | 			goto Out; | 
 | 		} | 
 | 		print("!password must be at least 7 characters\n"); | 
 | 	} | 
 | 	newpasslen = strlen(newpass); | 
 | 	snprint(prompt, sizeof(prompt), "retype password: "); | 
 | 	passck = readcons(prompt, nil, 1); | 
 | 	if(passck == nil){ | 
 | 		fprint(2, "readcons failed\n"); | 
 | 		goto Out; | 
 | 	} | 
 | 	if(strcmp(passck, newpass) != 0){ | 
 | 		fprint(2, "passwords didn't match\n"); | 
 | 		goto Out; | 
 | 	} | 
 |  | 
 | 	c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS")); | 
 | 	hexHi = PAK_Hi(id, newpass, H, Hi); | 
 | 	c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi)); | 
 | 	free(hexHi); | 
 | 	mpfree(H); | 
 | 	mpfree(Hi); | 
 |  | 
 | 	if(getfile(c->conn, ".", (uchar **)(void*)&list, &len, nil, 0) < 0){ | 
 | 		fprint(2, "directory listing failed.\n"); | 
 | 		goto Out; | 
 | 	} | 
 |  | 
 | 	/* Loop over files and reencrypt them; try to keep going after error */ | 
 | 	for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){ | 
 | 		*next = '\0'; | 
 | 		if(tokenize(cur, f, nelem(f))< 1) | 
 | 			break; | 
 | 		fprint(2, "reencrypting '%s'\n", f[0]); | 
 | 		if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass, c->passlen) < 0){ | 
 | 			fprint(2, "getfile of '%s' failed\n", f[0]); | 
 | 			continue; | 
 | 		} | 
 | 		if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpasslen) < 0) | 
 | 			fprint(2, "putfile of '%s' failed\n", f[0]); | 
 | 		free(memfile); | 
 | 	} | 
 | 	free(list); | 
 | 	c->conn->write(c->conn, (uchar*)"BYE", 3); | 
 | 	rv = 0; | 
 |  | 
 | Out: | 
 | 	if(newpass != nil){ | 
 | 		memset(newpass, 0, newpasslen); | 
 | 		free(newpass); | 
 | 	} | 
 | 	c->conn->free(c->conn); | 
 | 	return rv; | 
 | } | 
 |  | 
 | static AuthConn* | 
 | login(char *id, char *dest, int pass_stdin, int pass_nvram) | 
 | { | 
 | 	AuthConn *c; | 
 | 	int fd, n, ntry = 0; | 
 | 	char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass; | 
 |  | 
 | 	if(dest == nil){ | 
 | 		fprint(2, "tried to login with nil dest\n"); | 
 | 		exits("nil dest"); | 
 | 	} | 
 | 	c = emalloc(sizeof(*c)); | 
 | 	if(pass_nvram){ | 
 | 		/* if(readnvram(&nvr, 0) < 0) */ | 
 | 			exits("readnvram: %r"); | 
 | 		strecpy(c->pass, c->pass+sizeof c->pass, nvr.config); | 
 | 	} | 
 | 	if(pass_stdin){ | 
 | 		n = readn(0, s, Maxmsg-2);  /* so len(PINSTA)<Maxmsg-3 */ | 
 | 		if(n < 1) | 
 | 			exits("no password on standard input"); | 
 | 		s[n] = 0; | 
 | 		nl = strchr(s, '\n'); | 
 | 		if(nl){ | 
 | 			*nl++ = 0; | 
 | 			PINSTA = estrdup(nl); | 
 | 			nl = strchr(PINSTA, '\n'); | 
 | 			if(nl) | 
 | 				*nl = 0; | 
 | 		} | 
 | 		strecpy(c->pass, c->pass+sizeof c->pass, s); | 
 | 	} | 
 | 	while(1){ | 
 | 		if(verbose) | 
 | 			fprint(2, "dialing %s\n", dest); | 
 | 		if((fd = dial(dest, nil, nil, nil)) < 0){ | 
 | 			fprint(2, "can't dial %s\n", dest); | 
 | 			free(c); | 
 | 			return nil; | 
 | 		} | 
 | 		if((c->conn = newSConn(fd)) == nil){ | 
 | 			free(c); | 
 | 			return nil; | 
 | 		} | 
 | 		ntry++; | 
 | 		if(!pass_stdin && !pass_nvram){ | 
 | 			pass = readcons("secstore password", nil, 1); | 
 | 			if(pass == nil) | 
 | 				pass = estrdup(""); | 
 | 			if(strlen(pass) >= sizeof c->pass){ | 
 | 				fprint(2, "password too long, skipping secstore login\n"); | 
 | 				exits("password too long"); | 
 | 			} | 
 | 			strcpy(c->pass, pass); | 
 | 			memset(pass, 0, strlen(pass)); | 
 | 			free(pass); | 
 | 		} | 
 | 		if(c->pass[0]==0){ | 
 | 			fprint(2, "null password, skipping secstore login\n"); | 
 | 			exits("no password"); | 
 | 		} | 
 | 		if(PAKclient(c->conn, id, c->pass, &S) >= 0) | 
 | 			break; | 
 | 		c->conn->free(c->conn); | 
 | 		if(pass_stdin) | 
 | 			exits("invalid password on standard input"); | 
 | 		if(pass_nvram) | 
 | 			exits("invalid password in nvram"); | 
 | 		/* and let user try retyping the password */ | 
 | 		if(ntry==3) | 
 | 			fprint(2, "Enter an empty password to quit.\n"); | 
 | 	} | 
 | 	c->passlen = strlen(c->pass); | 
 | 	fprint(2, "server: %s\n", S); | 
 | 	free(S); | 
 | 	if(readstr(c->conn, s) < 0){ | 
 | 		c->conn->free(c->conn); | 
 | 		free(c); | 
 | 		return nil; | 
 | 	} | 
 | 	if(strcmp(s, "STA") == 0){ | 
 | 		long sn; | 
 | 		if(pass_stdin){ | 
 | 			if(PINSTA) | 
 | 				strncpy(s+3, PINSTA, (sizeof s)-3); | 
 | 			else | 
 | 				exits("missing PIN+SecureID on standard input"); | 
 | 			free(PINSTA); | 
 | 		}else{ | 
 | 			pass = readcons("STA PIN+SecureID", nil, 1); | 
 | 			if(pass == nil) | 
 | 				pass = estrdup(""); | 
 | 			strncpy(s+3, pass, (sizeof s)-4); | 
 | 			memset(pass, 0, strlen(pass)); | 
 | 			free(pass); | 
 | 		} | 
 | 		sn = strlen(s+3); | 
 | 		if(verbose) | 
 | 			fprint(2, "%ld\n", sn); | 
 | 		c->conn->write(c->conn, (uchar*)s, sn+3); | 
 | 		readstr(c->conn, s); | 
 | 	} | 
 | 	if(strcmp(s, "OK") != 0){ | 
 | 		fprint(2, "%s\n", s); | 
 | 		c->conn->free(c->conn); | 
 | 		free(c); | 
 | 		return nil; | 
 | 	} | 
 | 	return c; | 
 | } | 
 |  | 
 | int | 
 | main(int argc, char **argv) | 
 | { | 
 | 	int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc; | 
 | 	int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1]; | 
 | 	char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES]; | 
 | 	char *serve, *tcpserve, *user; | 
 | 	AuthConn *c; | 
 |  | 
 | 	serve = getenv("secstore"); | 
 | 	if(serve == nil) | 
 | 		serve = "secstore"; | 
 | 	user = getuser(); | 
 | 	memset(Gflag, 0, sizeof Gflag); | 
 | 	fmtinstall('B', mpfmt); | 
 | 	fmtinstall('H', encodefmt); | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	case 'c': | 
 | 		chpass = 1; | 
 | 		break; | 
 | 	case 'G': | 
 | 		Gflag[ngfile]++; | 
 | 		/* fall through */ | 
 | 	case 'g': | 
 | 		if(ngfile >= MAXFILES) | 
 | 			exits("too many gfiles"); | 
 | 		gfile[ngfile++] = ARGF(); | 
 | 		if(gfile[ngfile-1] == nil) | 
 | 			usage(); | 
 | 		break; | 
 | 	case 'i': | 
 | 		pass_stdin = 1; | 
 | 		break; | 
 | 	case 'n': | 
 | 		pass_nvram = 1; | 
 | 		break; | 
 | 	case 'p': | 
 | 		if(npfile >= MAXFILES) | 
 | 			exits("too many pfiles"); | 
 | 		pfile[npfile++] = ARGF(); | 
 | 		if(pfile[npfile-1] == nil) | 
 | 			usage(); | 
 | 		break; | 
 | 	case 'r': | 
 | 		if(nrfile >= MAXFILES) | 
 | 			exits("too many rfiles"); | 
 | 		rfile[nrfile++] = ARGF(); | 
 | 		if(rfile[nrfile-1] == nil) | 
 | 			usage(); | 
 | 		break; | 
 | 	case 's': | 
 | 		serve = EARGF(usage()); | 
 | 		break; | 
 | 	case 'u': | 
 | 		user = EARGF(usage()); | 
 | 		break; | 
 | 	case 'v': | 
 | 		verbose++; | 
 | 		break; | 
 | 	default: | 
 | 		usage(); | 
 | 		break; | 
 | 	}ARGEND; | 
 | 	gfile[ngfile] = nil; | 
 | 	pfile[npfile] = nil; | 
 | 	rfile[nrfile] = nil; | 
 |  | 
 | 	if(argc!=0 || user==nil) | 
 | 		usage(); | 
 |  | 
 | 	if(chpass && (ngfile || npfile || nrfile)){ | 
 | 		fprint(2, "Get, put, and remove invalid with password change.\n"); | 
 | 		exits("usage"); | 
 | 	} | 
 |  | 
 | 	tcpserve = netmkaddr(serve, "tcp", "secstore"); | 
 | 	c = login(user, tcpserve, pass_stdin, pass_nvram); | 
 | 	if(c == nil){ | 
 | 		fprint(2, "secstore authentication failed\n"); | 
 | 		exits("secstore authentication failed"); | 
 | 	} | 
 | 	if(chpass) | 
 | 		rc = chpasswd(c, user); | 
 | 	else | 
 | 		rc = cmd(c, gfile, Gflag, pfile, rfile); | 
 | 	if(rc < 0){ | 
 | 		fprint(2, "secstore cmd failed\n"); | 
 | 		exits("secstore cmd failed"); | 
 | 	} | 
 | 	exits(""); | 
 | 	return 0; | 
 | } | 
 |  |