Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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