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