plan9port

[fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port # fast
git clone https://src.adamsgaard.dk/plan9port.git # slow
Log | Files | Refs | README | LICENSE Back to index

secstore.c (12665B)


      1 /* network login client */
      2 #include <u.h>
      3 #include <libc.h>
      4 #include <mp.h>
      5 #include <libsec.h>
      6 #include <authsrv.h>
      7 #include "SConn.h"
      8 #include "secstore.h"
      9 enum{ CHK = 16, MAXFILES = 100 };
     10 
     11 typedef struct AuthConn{
     12 	SConn *conn;
     13 	char pass[64];
     14 	int passlen;
     15 } AuthConn;
     16 
     17 int verbose;
     18 Nvrsafe nvr;
     19 char *SECSTORE_DIR;
     20 
     21 void
     22 usage(void)
     23 {
     24 	fprint(2, "usage: secstore [-cin] [-g getfile] [-p putfile] [-r rmfile] [-s tcp!server!5356] [-u user] [-v]\n");
     25 	exits("usage");
     26 }
     27 
     28 static int
     29 getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey)
     30 {
     31 	int fd = -1;
     32 	int i, n, nr, nw, len;
     33 	char s[Maxmsg+1];
     34 	uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
     35 	AESstate aes;
     36 	DigestState *sha;
     37 
     38 	if(strchr(gf, '/')){
     39 		fprint(2, "simple filenames, not paths like %s\n", gf);
     40 		return -1;
     41 	}
     42 	memset(&aes, 0, sizeof aes);
     43 
     44 	snprint(s, Maxmsg, "GET %s\n", gf);
     45 	conn->write(conn, (uchar*)s, strlen(s));
     46 
     47 	/* get file size */
     48 	s[0] = '\0';
     49 	bufw = bufe = nil;
     50 	if(readstr(conn, s) < 0){
     51 		fprint(2, "remote: %s\n", s);
     52 		return -1;
     53 	}
     54 	len = atoi(s);
     55 	if(len == -1){
     56 		fprint(2, "remote file %s does not exist\n", gf);
     57 		return -1;
     58 	}else if(len == -3){
     59 		fprint(2, "implausible filesize for %s\n", gf);
     60 		return -1;
     61 	}else if(len < 0){
     62 		fprint(2, "GET refused for %s\n", gf);
     63 		return -1;
     64 	}
     65 	if(buf != nil){
     66 		*buflen = len - AESbsize - CHK;
     67 		*buf = bufw = emalloc(len);
     68 		bufe = bufw + len;
     69 	}
     70 
     71 	/* directory listing */
     72 	if(strcmp(gf,".")==0){
     73 		if(buf != nil)
     74 			*buflen = len;
     75 		for(i=0; i < len; i += n){
     76 			if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){
     77 				fprint(2, "empty file chunk\n");
     78 				return -1;
     79 			}
     80 			if(buf == nil)
     81 				write(1, s, n);
     82 			else
     83 				memmove((*buf)+i, s, n);
     84 		}
     85 		return 0;
     86 	}
     87 
     88 	/* conn is already encrypted against wiretappers,
     89 		but gf is also encrypted against server breakin. */
     90 	if(buf == nil && (fd =create(gf, OWRITE, 0600)) < 0){
     91 		fprint(2, "can't open %s: %r\n", gf);
     92 		return -1;
     93 	}
     94 
     95 	ibr = ibw = ib;
     96 	for(nr=0; nr < len;){
     97 		if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
     98 			fprint(2, "empty file chunk n=%d nr=%d len=%d: %r\n", n, nr, len);
     99 			return -1;
    100 		}
    101 		nr += n;
    102 		ibw += n;
    103 		if(!aes.setup){ /* first time, read 16 byte IV */
    104 			if(n < AESbsize){
    105 				fprint(2, "no IV in file\n");
    106 				return -1;
    107 			}
    108 			sha = sha1((uchar*)"aescbc file", 11, nil, nil);
    109 			sha1(key, nkey, skey, sha);
    110 			setupAESstate(&aes, skey, AESbsize, ibr);
    111 			memset(skey, 0, sizeof skey);
    112 			ibr += AESbsize;
    113 			n -= AESbsize;
    114 		}
    115 		aesCBCdecrypt(ibw-n, n, &aes);
    116 		n = ibw-ibr-CHK;
    117 		if(n > 0){
    118 			if(buf == nil){
    119 				nw = write(fd, ibr, n);
    120 				if(nw != n){
    121 					fprint(2, "write error on %s", gf);
    122 					return -1;
    123 				}
    124 			}else{
    125 				assert(bufw+n <= bufe);
    126 				memmove(bufw, ibr, n);
    127 				bufw += n;
    128 			}
    129 			ibr += n;
    130 		}
    131 		memmove(ib, ibr, ibw-ibr);
    132 		ibw = ib + (ibw-ibr);
    133 		ibr = ib;
    134 	}
    135 	if(buf == nil)
    136 		close(fd);
    137 	n = ibw-ibr;
    138 	if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){
    139 			fprint(2,"decrypted file failed to authenticate!\n");
    140 			return -1;
    141 	}
    142 	return 0;
    143 }
    144 
    145 /* This sends a file to the secstore disk that can, in an emergency, be */
    146 /* decrypted by the program aescbc.c. */
    147 static int
    148 putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey)
    149 {
    150 	int i, n, fd, ivo, bufi, done;
    151 	char s[Maxmsg];
    152 	uchar  skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
    153 	AESstate aes;
    154 	DigestState *sha;
    155 
    156 	/* create initialization vector */
    157 	srand(time(0));  /* doesn't need to be unpredictable */
    158 	for(i=0; i<AESbsize; i++)
    159 		IV[i] = 0xff & rand();
    160 	sha = sha1((uchar*)"aescbc file", 11, nil, nil);
    161 	sha1(key, nkey, skey, sha);
    162 	setupAESstate(&aes, skey, AESbsize, IV);
    163 	memset(skey, 0, sizeof skey);
    164 
    165 	snprint(s, Maxmsg, "PUT %s\n", pf);
    166 	conn->write(conn, (uchar*)s, strlen(s));
    167 
    168 	if(buf == nil){
    169 		/* get file size */
    170 		if((fd = open(pf, OREAD)) < 0){
    171 			fprint(2, "can't open %s: %r\n", pf);
    172 			return -1;
    173 		}
    174 		len = seek(fd, 0, 2);
    175 		seek(fd, 0, 0);
    176 	} else {
    177 		fd = -1;
    178 	}
    179 	if(len > MAXFILESIZE){
    180 		fprint(2, "implausible filesize %ld for %s\n", len, pf);
    181 		return -1;
    182 	}
    183 
    184 	/* send file size */
    185 	snprint(s, Maxmsg, "%ld", len+AESbsize+CHK);
    186 	conn->write(conn, (uchar*)s, strlen(s));
    187 
    188 	/* send IV and file+XXXXX in Maxmsg chunks */
    189 	ivo = AESbsize;
    190 	bufi = 0;
    191 	memcpy(b, IV, ivo);
    192 	for(done = 0; !done; ){
    193 		if(buf == nil){
    194 			n = read(fd, b+ivo, Maxmsg-ivo);
    195 			if(n < 0){
    196 				fprint(2, "read error on %s: %r\n", pf);
    197 				return -1;
    198 			}
    199 		}else{
    200 			if((n = len - bufi) > Maxmsg-ivo)
    201 				n = Maxmsg-ivo;
    202 			memcpy(b+ivo, buf+bufi, n);
    203 			bufi += n;
    204 		}
    205 		n += ivo;
    206 		ivo = 0;
    207 		if(n < Maxmsg){ /* EOF on input; append XX... */
    208 			memset(b+n, 'X', CHK);
    209 			n += CHK; /* might push n>Maxmsg */
    210 			done = 1;
    211 		}
    212 		aesCBCencrypt(b, n, &aes);
    213 		if(n > Maxmsg){
    214 			assert(done==1);
    215 			conn->write(conn, b, Maxmsg);
    216 			n -= Maxmsg;
    217 			memmove(b, b+Maxmsg, n);
    218 		}
    219 		conn->write(conn, b, n);
    220 	}
    221 
    222 	if(buf == nil)
    223 		close(fd);
    224 	fprint(2, "saved %ld bytes\n", len);
    225 
    226 	return 0;
    227 }
    228 
    229 static int
    230 removefile(SConn *conn, char *rf)
    231 {
    232 	char buf[Maxmsg];
    233 
    234 	if(strchr(rf, '/')){
    235 		fprint(2, "simple filenames, not paths like %s\n", rf);
    236 		return -1;
    237 	}
    238 
    239 	snprint(buf, Maxmsg, "RM %s\n", rf);
    240 	conn->write(conn, (uchar*)buf, strlen(buf));
    241 
    242 	return 0;
    243 }
    244 
    245 static int
    246 cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
    247 {
    248 	ulong len;
    249 	int rv = -1;
    250 	uchar *memfile, *memcur, *memnext;
    251 
    252 	while(*gf != nil){
    253 		if(verbose)
    254 			fprint(2, "get %s\n", *gf);
    255 		if(getfile(c->conn, *gf, *Gflag ? &memfile : nil, &len, (uchar*)c->pass, c->passlen) < 0)
    256 			goto Out;
    257 		if(*Gflag){
    258 			/* write one line at a time, as required by /mnt/factotum/ctl */
    259 			memcur = memfile;
    260 			while(len>0){
    261 				memnext = (uchar*)strchr((char*)memcur, '\n');
    262 				if(memnext){
    263 					write(1, memcur, memnext-memcur+1);
    264 					len -= memnext-memcur+1;
    265 					memcur = memnext+1;
    266 				}else{
    267 					write(1, memcur, len);
    268 					break;
    269 				}
    270 			}
    271 			free(memfile);
    272 		}
    273 		gf++;
    274 		Gflag++;
    275 	}
    276 	while(*pf != nil){
    277 		if(verbose)
    278 			fprint(2, "put %s\n", *pf);
    279 		if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0)
    280 			goto Out;
    281 		pf++;
    282 	}
    283 	while(*rf != nil){
    284 		if(verbose)
    285 			fprint(2, "rm  %s\n", *rf);
    286 		if(removefile(c->conn, *rf) < 0)
    287 			goto Out;
    288 		rf++;
    289 	}
    290 
    291 	c->conn->write(c->conn, (uchar*)"BYE", 3);
    292 	rv = 0;
    293 
    294 Out:
    295 	c->conn->free(c->conn);
    296 	return rv;
    297 }
    298 
    299 static int
    300 chpasswd(AuthConn *c, char *id)
    301 {
    302 	ulong len;
    303 	int rv = -1, newpasslen = 0;
    304 	mpint *H, *Hi;
    305 	uchar *memfile;
    306 	char *newpass, *passck;
    307 	char *list, *cur, *next, *hexHi;
    308 	char *f[8], prompt[128];
    309 
    310 	H = mpnew(0);
    311 	Hi = mpnew(0);
    312 	/* changing our password is vulnerable to connection failure */
    313 	for(;;){
    314 		snprint(prompt, sizeof(prompt), "new password for %s: ", id);
    315 		newpass = readcons(prompt, nil, 1);
    316 		if(newpass == nil)
    317 			goto Out;
    318 		if(strlen(newpass) >= 7)
    319 			break;
    320 		else if(strlen(newpass) == 0){
    321 			fprint(2, "!password change aborted\n");
    322 			goto Out;
    323 		}
    324 		print("!password must be at least 7 characters\n");
    325 	}
    326 	newpasslen = strlen(newpass);
    327 	snprint(prompt, sizeof(prompt), "retype password: ");
    328 	passck = readcons(prompt, nil, 1);
    329 	if(passck == nil){
    330 		fprint(2, "readcons failed\n");
    331 		goto Out;
    332 	}
    333 	if(strcmp(passck, newpass) != 0){
    334 		fprint(2, "passwords didn't match\n");
    335 		goto Out;
    336 	}
    337 
    338 	c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS"));
    339 	hexHi = PAK_Hi(id, newpass, H, Hi);
    340 	c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi));
    341 	free(hexHi);
    342 	mpfree(H);
    343 	mpfree(Hi);
    344 
    345 	if(getfile(c->conn, ".", (uchar **)(void*)&list, &len, nil, 0) < 0){
    346 		fprint(2, "directory listing failed.\n");
    347 		goto Out;
    348 	}
    349 
    350 	/* Loop over files and reencrypt them; try to keep going after error */
    351 	for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){
    352 		*next = '\0';
    353 		if(tokenize(cur, f, nelem(f))< 1)
    354 			break;
    355 		fprint(2, "reencrypting '%s'\n", f[0]);
    356 		if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass, c->passlen) < 0){
    357 			fprint(2, "getfile of '%s' failed\n", f[0]);
    358 			continue;
    359 		}
    360 		if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpasslen) < 0)
    361 			fprint(2, "putfile of '%s' failed\n", f[0]);
    362 		free(memfile);
    363 	}
    364 	free(list);
    365 	c->conn->write(c->conn, (uchar*)"BYE", 3);
    366 	rv = 0;
    367 
    368 Out:
    369 	if(newpass != nil){
    370 		memset(newpass, 0, newpasslen);
    371 		free(newpass);
    372 	}
    373 	c->conn->free(c->conn);
    374 	return rv;
    375 }
    376 
    377 static AuthConn*
    378 login(char *id, char *dest, int pass_stdin, int pass_nvram)
    379 {
    380 	AuthConn *c;
    381 	int fd, n, ntry = 0;
    382 	char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
    383 
    384 	if(dest == nil){
    385 		fprint(2, "tried to login with nil dest\n");
    386 		exits("nil dest");
    387 	}
    388 	c = emalloc(sizeof(*c));
    389 	if(pass_nvram){
    390 		/* if(readnvram(&nvr, 0) < 0) */
    391 			exits("readnvram: %r");
    392 		strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
    393 	}
    394 	if(pass_stdin){
    395 		n = readn(0, s, Maxmsg-2);  /* so len(PINSTA)<Maxmsg-3 */
    396 		if(n < 1)
    397 			exits("no password on standard input");
    398 		s[n] = 0;
    399 		nl = strchr(s, '\n');
    400 		if(nl){
    401 			*nl++ = 0;
    402 			PINSTA = estrdup(nl);
    403 			nl = strchr(PINSTA, '\n');
    404 			if(nl)
    405 				*nl = 0;
    406 		}
    407 		strecpy(c->pass, c->pass+sizeof c->pass, s);
    408 	}
    409 	while(1){
    410 		if(verbose)
    411 			fprint(2, "dialing %s\n", dest);
    412 		if((fd = dial(dest, nil, nil, nil)) < 0){
    413 			fprint(2, "can't dial %s\n", dest);
    414 			free(c);
    415 			return nil;
    416 		}
    417 		if((c->conn = newSConn(fd)) == nil){
    418 			free(c);
    419 			return nil;
    420 		}
    421 		ntry++;
    422 		if(!pass_stdin && !pass_nvram){
    423 			pass = readcons("secstore password", nil, 1);
    424 			if(pass == nil)
    425 				pass = estrdup("");
    426 			if(strlen(pass) >= sizeof c->pass){
    427 				fprint(2, "password too long, skipping secstore login\n");
    428 				exits("password too long");
    429 			}
    430 			strcpy(c->pass, pass);
    431 			memset(pass, 0, strlen(pass));
    432 			free(pass);
    433 		}
    434 		if(c->pass[0]==0){
    435 			fprint(2, "null password, skipping secstore login\n");
    436 			exits("no password");
    437 		}
    438 		if(PAKclient(c->conn, id, c->pass, &S) >= 0)
    439 			break;
    440 		c->conn->free(c->conn);
    441 		if(pass_stdin)
    442 			exits("invalid password on standard input");
    443 		if(pass_nvram)
    444 			exits("invalid password in nvram");
    445 		/* and let user try retyping the password */
    446 		if(ntry==3)
    447 			fprint(2, "Enter an empty password to quit.\n");
    448 	}
    449 	c->passlen = strlen(c->pass);
    450 	fprint(2, "server: %s\n", S);
    451 	free(S);
    452 	if(readstr(c->conn, s) < 0){
    453 		c->conn->free(c->conn);
    454 		free(c);
    455 		return nil;
    456 	}
    457 	if(strcmp(s, "STA") == 0){
    458 		long sn;
    459 		if(pass_stdin){
    460 			if(PINSTA)
    461 				strncpy(s+3, PINSTA, (sizeof s)-3);
    462 			else
    463 				exits("missing PIN+SecureID on standard input");
    464 			free(PINSTA);
    465 		}else{
    466 			pass = readcons("STA PIN+SecureID", nil, 1);
    467 			if(pass == nil)
    468 				pass = estrdup("");
    469 			strncpy(s+3, pass, (sizeof s)-4);
    470 			memset(pass, 0, strlen(pass));
    471 			free(pass);
    472 		}
    473 		sn = strlen(s+3);
    474 		if(verbose)
    475 			fprint(2, "%ld\n", sn);
    476 		c->conn->write(c->conn, (uchar*)s, sn+3);
    477 		readstr(c->conn, s);
    478 	}
    479 	if(strcmp(s, "OK") != 0){
    480 		fprint(2, "%s\n", s);
    481 		c->conn->free(c->conn);
    482 		free(c);
    483 		return nil;
    484 	}
    485 	return c;
    486 }
    487 
    488 int
    489 main(int argc, char **argv)
    490 {
    491 	int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
    492 	int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
    493 	char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES];
    494 	char *serve, *tcpserve, *user;
    495 	AuthConn *c;
    496 
    497 	serve = getenv("secstore");
    498 	if(serve == nil)
    499 		serve = "secstore";
    500 	user = getuser();
    501 	memset(Gflag, 0, sizeof Gflag);
    502 	fmtinstall('B', mpfmt);
    503 	fmtinstall('H', encodefmt);
    504 
    505 	ARGBEGIN{
    506 	case 'c':
    507 		chpass = 1;
    508 		break;
    509 	case 'G':
    510 		Gflag[ngfile]++;
    511 		/* fall through */
    512 	case 'g':
    513 		if(ngfile >= MAXFILES)
    514 			exits("too many gfiles");
    515 		gfile[ngfile++] = ARGF();
    516 		if(gfile[ngfile-1] == nil)
    517 			usage();
    518 		break;
    519 	case 'i':
    520 		pass_stdin = 1;
    521 		break;
    522 	case 'n':
    523 		pass_nvram = 1;
    524 		break;
    525 	case 'p':
    526 		if(npfile >= MAXFILES)
    527 			exits("too many pfiles");
    528 		pfile[npfile++] = ARGF();
    529 		if(pfile[npfile-1] == nil)
    530 			usage();
    531 		break;
    532 	case 'r':
    533 		if(nrfile >= MAXFILES)
    534 			exits("too many rfiles");
    535 		rfile[nrfile++] = ARGF();
    536 		if(rfile[nrfile-1] == nil)
    537 			usage();
    538 		break;
    539 	case 's':
    540 		serve = EARGF(usage());
    541 		break;
    542 	case 'u':
    543 		user = EARGF(usage());
    544 		break;
    545 	case 'v':
    546 		verbose++;
    547 		break;
    548 	default:
    549 		usage();
    550 		break;
    551 	}ARGEND;
    552 	gfile[ngfile] = nil;
    553 	pfile[npfile] = nil;
    554 	rfile[nrfile] = nil;
    555 
    556 	if(argc!=0 || user==nil)
    557 		usage();
    558 
    559 	if(chpass && (ngfile || npfile || nrfile)){
    560 		fprint(2, "Get, put, and remove invalid with password change.\n");
    561 		exits("usage");
    562 	}
    563 
    564 	tcpserve = netmkaddr(serve, "tcp", "secstore");
    565 	c = login(user, tcpserve, pass_stdin, pass_nvram);
    566 	if(c == nil){
    567 		fprint(2, "secstore authentication failed\n");
    568 		exits("secstore authentication failed");
    569 	}
    570 	if(chpass)
    571 		rc = chpasswd(c, user);
    572 	else
    573 		rc = cmd(c, gfile, Gflag, pfile, rfile);
    574 	if(rc < 0){
    575 		fprint(2, "secstore cmd failed\n");
    576 		exits("secstore cmd failed");
    577 	}
    578 	exits("");
    579 	return 0;
    580 }