]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/auth/secstore/secstore.c
bfcacaa1e50b656e9a6362e4b44ebcd07d3f8226
[plan9front.git] / sys / src / cmd / auth / secstore / secstore.c
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                         fprint(2, "secstore: readnvram: %r\n");
394                         exits("readnvram failed");
395                 }
396                 strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
397         }
398         if(pass_stdin){
399                 n = readn(0, s, Maxmsg-2);      /* so len(PINSTA)<Maxmsg-3 */
400                 if(n < 1)
401                         exits("no password on standard input");
402                 s[n] = 0;
403                 nl = strchr(s, '\n');
404                 if(nl){
405                         *nl++ = 0;
406                         PINSTA = estrdup(nl);
407                         nl = strchr(PINSTA, '\n');
408                         if(nl)
409                                 *nl = 0;
410                 }
411                 strecpy(c->pass, c->pass+sizeof c->pass, s);
412         }
413         for(;;){
414                 if(verbose)
415                         fprint(2, "dialing %s\n", dest);
416                 if((fd = dial(dest, nil, nil, nil)) < 0){
417                         fprint(2, "secstore: can't dial %s\n", dest);
418                         free(c);
419                         return nil;
420                 }
421                 if((c->conn = newSConn(fd)) == nil){
422                         free(c);
423                         return nil;
424                 }
425                 ntry++;
426                 if(!pass_stdin && !pass_nvram){
427                         pass = getpassm("secstore password: ");
428                         if(strlen(pass) >= sizeof c->pass){
429                                 fprint(2, "secstore: password too long, skipping secstore login\n");
430                                 exits("password too long");
431                         }
432                         strcpy(c->pass, pass);
433                         memset(pass, 0, strlen(pass));
434                         free(pass);
435                 }
436                 if(c->pass[0]==0){
437                         fprint(2, "secstore: null password, skipping secstore login\n");
438                         exits("no password");
439                 }
440                 if(PAKclient(c->conn, id, c->pass, &S) >= 0)
441                         break;
442                 c->conn->free(c->conn);
443                 if(pass_stdin)
444                         exits("invalid password on standard input");
445                 if(pass_nvram)
446                         exits("invalid password in nvram");
447                 /* and let user try retyping the password */
448                 if(ntry==3)
449                         fprint(2, "Enter an empty password to quit.\n");
450         }
451         c->passlen = strlen(c->pass);
452         fprint(2, "%s\n", S);
453         free(S);
454         if(readstr(c->conn, s) < 0){
455                 c->conn->free(c->conn);
456                 free(c);
457                 return nil;
458         }
459         if(strcmp(s, "STA") == 0){
460                 long sn;
461
462                 if(pass_stdin){
463                         if(PINSTA)
464                                 strncpy(s+3, PINSTA, sizeof s - 3);
465                         else
466                                 exits("missing PIN+SecureID on standard input");
467                         free(PINSTA);
468                 }else{
469                         pass = getpassm("STA PIN+SecureID: ");
470                         strncpy(s+3, pass, sizeof s - 4);
471                         memset(pass, 0, strlen(pass));
472                         free(pass);
473                 }
474                 sn = strlen(s+3);
475                 if(verbose)
476                         fprint(2, "%ld\n", sn);
477                 c->conn->write(c->conn, (uchar*)s, sn+3);
478                 readstr(c->conn, s);    /* TODO: check for error? */
479         }
480         if(strcmp(s, "OK") != 0){
481                 fprint(2, "%s: %s\n", argv0, s);
482                 c->conn->free(c->conn);
483                 free(c);
484                 return nil;
485         }
486         return c;
487 }
488
489 void
490 main(int argc, char **argv)
491 {
492         int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
493         int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
494         char *serve, *tcpserve, *user;
495         char *gfile[MAXFILES+1], *pfile[MAXFILES+1], *rfile[MAXFILES+1];
496         AuthConn *c;
497
498         serve = "$auth";
499         user = getuser();
500         memset(Gflag, 0, sizeof Gflag);
501
502         ARGBEGIN{
503         case 'c':
504                 chpass = 1;
505                 break;
506         case 'G':
507                 Gflag[ngfile]++;
508                 /* fall through */
509         case 'g':
510                 if(ngfile >= MAXFILES)
511                         exits("too many gfiles");
512                 gfile[ngfile++] = EARGF(usage());
513                 break;
514         case 'i':
515                 pass_stdin = 1;
516                 break;
517         case 'n':
518                 pass_nvram = 1;
519                 break;
520         case 'p':
521                 if(npfile >= MAXFILES)
522                         exits("too many pfiles");
523                 pfile[npfile++] = EARGF(usage());
524                 break;
525         case 'r':
526                 if(nrfile >= MAXFILES)
527                         exits("too many rfiles");
528                 rfile[nrfile++] = EARGF(usage());
529                 break;
530         case 's':
531                 serve = EARGF(usage());
532                 break;
533         case 'u':
534                 user = EARGF(usage());
535                 break;
536         case 'v':
537                 verbose++;
538                 break;
539         default:
540                 usage();
541                 break;
542         }ARGEND;
543         gfile[ngfile] = nil;
544         pfile[npfile] = nil;
545         rfile[nrfile] = nil;
546
547         if(argc!=0 || user==nil)
548                 usage();
549
550         if(chpass && (ngfile || npfile || nrfile)){
551                 fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
552                 exits("usage");
553         }
554
555         rc = strlen(serve) + sizeof "tcp!!99990";
556         tcpserve = emalloc(rc);
557         if(strchr(serve,'!'))
558                 strcpy(tcpserve, serve);
559         else
560                 snprint(tcpserve, rc, "tcp!%s!5356", serve);
561         c = login(user, tcpserve, pass_stdin, pass_nvram);
562         free(tcpserve);
563         if(c == nil)
564                 sysfatal("secstore authentication failed");
565         if(chpass)
566                 rc = chpasswd(c, user);
567         else
568                 rc = cmd(c, gfile, Gflag, pfile, rfile);
569         if(rc < 0)
570                 sysfatal("secstore cmd failed");
571         exits("");
572 }