Blob


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 };
11 typedef struct AuthConn{
12 SConn *conn;
13 char pass[64];
14 int passlen;
15 } AuthConn;
17 int verbose;
18 Nvrsafe nvr;
19 char *SECSTORE_DIR;
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 }
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;
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);
44 snprint(s, Maxmsg, "GET %s\n", gf);
45 conn->write(conn, (uchar*)s, strlen(s));
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 }
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 }
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 }
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;
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;
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;
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;
124 }else{
125 assert(bufw+n <= bufe);
126 memmove(bufw, ibr, n);
127 bufw += n;
129 ibr += n;
131 memmove(ib, ibr, ibw-ibr);
132 ibw = ib + (ibw-ibr);
133 ibr = ib;
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;
142 return 0;
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)
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;
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);
165 snprint(s, Maxmsg, "PUT %s\n", pf);
166 conn->write(conn, (uchar*)s, strlen(s));
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;
174 len = seek(fd, 0, 2);
175 seek(fd, 0, 0);
176 } else {
177 fd = -1;
179 if(len > MAXFILESIZE){
180 fprint(2, "implausible filesize %ld for %s\n", len, pf);
181 return -1;
184 /* send file size */
185 snprint(s, Maxmsg, "%ld", len+AESbsize+CHK);
186 conn->write(conn, (uchar*)s, strlen(s));
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;
199 }else{
200 if((n = len - bufi) > Maxmsg-ivo)
201 n = Maxmsg-ivo;
202 memcpy(b+ivo, buf+bufi, n);
203 bufi += n;
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;
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);
219 conn->write(conn, b, n);
222 if(buf == nil)
223 close(fd);
224 fprint(2, "saved %ld bytes\n", len);
226 return 0;
229 static int
230 removefile(SConn *conn, char *rf)
232 char buf[Maxmsg];
234 if(strchr(rf, '/')){
235 fprint(2, "simple filenames, not paths like %s\n", rf);
236 return -1;
239 snprint(buf, Maxmsg, "RM %s\n", rf);
240 conn->write(conn, (uchar*)buf, strlen(buf));
242 return 0;
245 static int
246 cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
248 ulong len;
249 int rv = -1;
250 uchar *memfile, *memcur, *memnext;
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;
271 free(memfile);
273 gf++;
274 Gflag++;
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++;
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++;
291 c->conn->write(c->conn, (uchar*)"BYE", 3);
292 rv = 0;
294 Out:
295 c->conn->free(c->conn);
296 return rv;
299 static int
300 chpasswd(AuthConn *c, char *id)
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];
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;
324 print("!password must be at least 7 characters\n");
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;
333 if(strcmp(passck, newpass) != 0){
334 fprint(2, "passwords didn't match\n");
335 goto Out;
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);
345 if(getfile(c->conn, ".", (uchar **)(void*)&list, &len, nil, 0) < 0){
346 fprint(2, "directory listing failed.\n");
347 goto Out;
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;
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);
364 free(list);
365 c->conn->write(c->conn, (uchar*)"BYE", 3);
366 rv = 0;
368 Out:
369 if(newpass != nil){
370 memset(newpass, 0, newpasslen);
371 free(newpass);
373 c->conn->free(c->conn);
374 return rv;
377 static AuthConn*
378 login(char *id, char *dest, int pass_stdin, int pass_nvram)
380 AuthConn *c;
381 int fd, n, ntry = 0;
382 char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
384 if(dest == nil){
385 fprint(2, "tried to login with nil dest\n");
386 exits("nil dest");
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);
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;
407 strecpy(c->pass, c->pass+sizeof c->pass, s);
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;
417 if((c->conn = newSConn(fd)) == nil){
418 free(c);
419 return nil;
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");
430 strcpy(c->pass, pass);
431 memset(pass, 0, strlen(pass));
432 free(pass);
434 if(c->pass[0]==0){
435 fprint(2, "null password, skipping secstore login\n");
436 exits("no password");
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");
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;
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);
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);
479 if(strcmp(s, "OK") != 0){
480 fprint(2, "%s\n", s);
481 c->conn->free(c->conn);
482 free(c);
483 return nil;
485 return c;
488 int
489 main(int argc, char **argv)
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;
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);
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;
556 if(argc!=0 || user==nil)
557 usage();
559 if(chpass && (ngfile || npfile || nrfile)){
560 fprint(2, "Get, put, and remove invalid with password change.\n");
561 exits("usage");
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");
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");
578 exits("");
579 return 0;