Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/* secstore - network login client */
#include <u.h>
#include <libc.h>
#include <mp.h>
#include <libsec.h>
#include <authsrv.h>
#include "SConn.h"
#include "secstore.h"

enum{ CHK = 16, MAXFILES = 100 };

typedef struct AuthConn{
        SConn   *conn;
        char    pass[64];
        int     passlen;
} AuthConn;

int verbose;
Nvrsafe nvr;

void
usage(void)
{
        fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] "
                "[-r rmfile] [-s tcp!server!5356] [-u user]\n");
        exits("usage");
}

static int
getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey)
{
        int fd = -1, i, n, nr, nw, len;
        char s[Maxmsg+1];
        uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
        AESstate aes;
        DigestState *sha;

        memset(&aes, 0, sizeof aes);

        snprint(s, Maxmsg, "GET %s", gf);
        conn->write(conn, (uchar*)s, strlen(s));

        /* get file size */
        s[0] = '\0';
        bufw = bufe = nil;
        if(readstr(conn, s) < 0){
                fprint(2, "secstore: remote: %s\n", s);
                return -1;
        }
        len = atoi(s);
        if(len == -1){
                fprint(2, "secstore: remote file %s does not exist\n", gf);
                return -1;
        }else if(len == -3){
                fprint(2, "secstore: implausible filesize for %s\n", gf);
                return -1;
        }else if(len < 0){
                fprint(2, "secstore: GET refused for %s\n", gf);
                return -1;
        }
        if(buf != nil){
                *buflen = len - AESbsize - CHK;
                *buf = bufw = emalloc(len);
                bufe = bufw + len;
        }

        /* directory listing */
        if(strcmp(gf,".")==0){
                if(buf != nil)
                        *buflen = len;
                for(i=0; i < len; i += n){
                        if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){
                                fprint(2, "secstore: empty file chunk\n");
                                return -1;
                        }
                        if(buf == nil)
                                write(1, s, n);
                        else
                                memmove(*buf + i, s, n);
                }
                return 0;
        }

        /*
         * conn is already encrypted against wiretappers, but gf is also
         * encrypted against server breakin.
         */
        if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){
                fprint(2, "secstore: can't open %s: %r\n", gf);
                return -1;
        }

        ibr = ibw = ib;
        for(nr=0; nr < len;){
                if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
                        fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n",
                                n, nr, len);
                        return -1;
                }
                nr += n;
                ibw += n;
                if(!aes.setup){         /* first time, read 16 byte IV */
                        if(n < AESbsize){
                                fprint(2, "secstore: no IV in file\n");
                                return -1;
                        }
                        sha = sha1((uchar*)"aescbc file", 11, nil, nil);
                        sha1(key, nkey, skey, sha);
                        setupAESstate(&aes, skey, AESbsize, ibr);
                        memset(skey, 0, sizeof skey);
                        ibr += AESbsize;
                        n   -= AESbsize;
                }
                aesCBCdecrypt(ibw-n, n, &aes);
                n = ibw - ibr - CHK;
                if(n > 0){
                        if(buf == nil){
                                nw = write(fd, ibr, n);
                                if(nw != n){
                                        fprint(2, "secstore: write error on %s", gf);
                                        return -1;
                                }
                        }else{
                                assert(bufw + n <= bufe);
                                memmove(bufw, ibr, n);
                                bufw += n;
                        }
                        ibr += n;
                }
                memmove(ib, ibr, ibw-ibr);
                ibw = ib + (ibw-ibr);
                ibr = ib;
        }
        if(buf == nil)
                close(fd);
        n = ibw-ibr;
        if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){
                fprint(2, "secstore: decrypted file failed to authenticate!\n");
                return -1;
        }
        return 0;
}

/*
 * This sends a file to the secstore disk that can, in an emergency, be
 * decrypted by the program aescbc.c.
 */
static int
putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey)
{
        int i, n, fd, ivo, bufi, done;
        char s[Maxmsg];
        uchar skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
        AESstate aes;
        DigestState *sha;

        /* create initialization vector */
        srand(time(0));                 /* doesn't need to be unpredictable */
        for(i=0; i<AESbsize; i++)
                IV[i] = 0xff & rand();
        sha = sha1((uchar*)"aescbc file", 11, nil, nil);
        sha1(key, nkey, skey, sha);
        setupAESstate(&aes, skey, AESbsize, IV);
        memset(skey, 0, sizeof skey);

        snprint(s, Maxmsg, "PUT %s", pf);
        conn->write(conn, (uchar*)s, strlen(s));

        if(buf == nil){
                /* get file size */
                if((fd = open(pf, OREAD)) < 0){
                        fprint(2, "secstore: can't open %s: %r\n", pf);
                        return -1;
                }
                len = seek(fd, 0, 2);
                seek(fd, 0, 0);
        } else
                fd = -1;
        if(len > MAXFILESIZE){
                fprint(2, "secstore: implausible filesize %ld for %s\n",
                        len, pf);
                return -1;
        }

        /* send file size */
        snprint(s, Maxmsg, "%ld", len + AESbsize + CHK);
        conn->write(conn, (uchar*)s, strlen(s));

        /* send IV and file+XXXXX in Maxmsg chunks */
        ivo = AESbsize;
        bufi = 0;
        memcpy(b, IV, ivo);
        for(done = 0; !done; ){
                if(buf == nil){
                        n = read(fd, b+ivo, Maxmsg-ivo);
                        if(n < 0){
                                fprint(2, "secstore: read error on %s: %r\n",
                                        pf);
                                return -1;
                        }
                }else{
                        if((n = len - bufi) > Maxmsg-ivo)
                                n = Maxmsg-ivo;
                        memcpy(b+ivo, buf+bufi, n);
                        bufi += n;
                }
                n += ivo;
                ivo = 0;
                if(n < Maxmsg){         /* EOF on input; append XX... */
                        memset(b+n, 'X', CHK);
                        n += CHK;       /* might push n>Maxmsg */
                        done = 1;
                }
                aesCBCencrypt(b, n, &aes);
                if(n > Maxmsg){
                        assert(done==1);
                        conn->write(conn, b, Maxmsg);
                        n -= Maxmsg;
                        memmove(b, b+Maxmsg, n);
                }
                conn->write(conn, b, n);
        }

        if(buf == nil)
                close(fd);
        fprint(2, "secstore: saved %ld bytes\n", len);

        return 0;
}

static int
removefile(SConn *conn, char *rf)
{
        char buf[Maxmsg];

        if(strchr(rf, '/') != nil){
                fprint(2, "secstore: simple filenames, not paths like %s\n", rf);
                return -1;
        }

        snprint(buf, Maxmsg, "RM %s", rf);
        conn->write(conn, (uchar*)buf, strlen(buf));

        return 0;
}

static int
cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
{
        ulong len;
        int rv = -1;
        uchar *memfile, *memcur, *memnext;

        while(*gf != nil){
                if(verbose)
                        fprint(2, "get %s\n", *gf);
                if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
                    (uchar*)c->pass, c->passlen) < 0)
                        goto Out;
                if(*Gflag){
                        /* write 1 line at a time, as required by /mnt/factotum/ctl */
                        memcur = memfile;
                        while(len>0){
                                memnext = (uchar*)strchr((char*)memcur, '\n');
                                if(memnext){
                                        write(1, memcur, memnext-memcur+1);
                                        len -= memnext-memcur+1;
                                        memcur = memnext+1;
                                }else{
                                        write(1, memcur, len);
                                        break;
                                }
                        }
                        free(memfile);
                }
                gf++;
                Gflag++;
        }
        while(*pf != nil){
                if(verbose)
                        fprint(2, "put %s\n", *pf);
                if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0)
                        goto Out;
                pf++;
        }
        while(*rf != nil){
                if(verbose)
                        fprint(2, "rm  %s\n", *rf);
                if(removefile(c->conn, *rf) < 0)
                        goto Out;
                rf++;
        }

        c->conn->write(c->conn, (uchar*)"BYE", 3);
        rv = 0;

Out:
        c->conn->free(c->conn);
        return rv;
}

static int
chpasswd(AuthConn *c, char *id)
{
        int rv = -1, newpasslen = 0;
        ulong len;
        uchar *memfile;
        char *newpass, *passck, *list, *cur, *next, *hexHi;
        char *f[8], prompt[128];
        mpint *H, *Hi;

        H = mpnew(0);
        Hi = mpnew(0);
        /* changing our password is vulnerable to connection failure */
        for(;;){
                snprint(prompt, sizeof(prompt), "new password for %s: ", id);
                newpass = getpassm(prompt);
                if(newpass == nil)
                        goto Out;
                if(strlen(newpass) >= 7)
                        break;
                else if(strlen(newpass) == 0){
                        fprint(2, "!password change aborted\n");
                        goto Out;
                }
                print("!password must be at least 7 characters\n");
        }
        newpasslen = strlen(newpass);
        snprint(prompt, sizeof(prompt), "retype password: ");
        passck = getpassm(prompt);
        if(passck == nil){
                fprint(2, "secstore: getpassm failed\n");
                goto Out;
        }
        if(strcmp(passck, newpass) != 0){
                fprint(2, "secstore: passwords didn't match\n");
                goto Out;
        }

        c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS"));
        hexHi = PAK_Hi(id, newpass, H, Hi);
        c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi));
        free(hexHi);
        mpfree(H);
        mpfree(Hi);

        if(getfile(c->conn, ".", (uchar **) &list, &len, nil, 0) < 0){
                fprint(2, "secstore: directory listing failed.\n");
                goto Out;
        }

        /* Loop over files and reencrypt them; try to keep going after error */
        for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){
                *next = '\0';
                if(tokenize(cur, f, nelem(f))< 1)
                        break;
                fprint(2, "secstore: reencrypting '%s'\n", f[0]);
                if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass,
                    c->passlen) < 0){
                        fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
                        continue;
                }
                if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass,
                    newpasslen) < 0)
                        fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
                free(memfile);
        }
        free(list);
        c->conn->write(c->conn, (uchar*)"BYE", 3);
        rv = 0;

Out:
        if(newpass != nil){
                memset(newpass, 0, newpasslen);
                free(newpass);
        }
        c->conn->free(c->conn);
        return rv;
}

static AuthConn*
login(char *id, char *dest, int pass_stdin, int pass_nvram)
{
        int fd, n, ntry = 0;
        char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
        AuthConn *c;

        if(dest == nil)
                sysfatal("tried to login with nil dest");
        c = emalloc(sizeof(*c));
        if(pass_nvram){
                if(readnvram(&nvr, 0) < 0)
                        exits("readnvram: %r");
                strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
        }
        if(pass_stdin){
                n = readn(0, s, Maxmsg-2);      /* so len(PINSTA)<Maxmsg-3 */
                if(n < 1)
                        exits("no password on standard input");
                s[n] = 0;
                nl = strchr(s, '\n');
                if(nl){
                        *nl++ = 0;
                        PINSTA = estrdup(nl);
                        nl = strchr(PINSTA, '\n');
                        if(nl)
                                *nl = 0;
                }
                strecpy(c->pass, c->pass+sizeof c->pass, s);
        }
        for(;;){
                if(verbose)
                        fprint(2, "dialing %s\n", dest);
                if((fd = dial(dest, nil, nil, nil)) < 0){
                        fprint(2, "secstore: can't dial %s\n", dest);
                        free(c);
                        return nil;
                }
                if((c->conn = newSConn(fd)) == nil){
                        free(c);
                        return nil;
                }
                ntry++;
                if(!pass_stdin && !pass_nvram){
                        pass = getpassm("secstore password: ");
                        if(strlen(pass) >= sizeof c->pass){
                                fprint(2, "secstore: password too long, skipping secstore login\n");
                                exits("password too long");
                        }
                        strcpy(c->pass, pass);
                        memset(pass, 0, strlen(pass));
                        free(pass);
                }
                if(c->pass[0]==0){
                        fprint(2, "secstore: null password, skipping secstore login\n");
                        exits("no password");
                }
                if(PAKclient(c->conn, id, c->pass, &S) >= 0)
                        break;
                c->conn->free(c->conn);
                if(pass_stdin)
                        exits("invalid password on standard input");
                if(pass_nvram)
                        exits("invalid password in nvram");
                /* and let user try retyping the password */
                if(ntry==3)
                        fprint(2, "Enter an empty password to quit.\n");
        }
        c->passlen = strlen(c->pass);
        fprint(2, "%s\n", S);
        free(S);
        if(readstr(c->conn, s) < 0){
                c->conn->free(c->conn);
                free(c);
                return nil;
        }
        if(strcmp(s, "STA") == 0){
                long sn;

                if(pass_stdin){
                        if(PINSTA)
                                strncpy(s+3, PINSTA, sizeof s - 3);
                        else
                                exits("missing PIN+SecureID on standard input");
                        free(PINSTA);
                }else{
                        pass = getpassm("STA PIN+SecureID: ");
                        strncpy(s+3, pass, sizeof s - 4);
                        memset(pass, 0, strlen(pass));
                        free(pass);
                }
                sn = strlen(s+3);
                if(verbose)
                        fprint(2, "%ld\n", sn);
                c->conn->write(c->conn, (uchar*)s, sn+3);
                readstr(c->conn, s);    /* TODO: check for error? */
        }
        if(strcmp(s, "OK") != 0){
                fprint(2, "%s: %s\n", argv0, s);
                c->conn->free(c->conn);
                free(c);
                return nil;
        }
        return c;
}

void
main(int argc, char **argv)
{
        int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
        int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
        char *serve, *tcpserve, *user;
        char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES];
        AuthConn *c;

        serve = "$auth";
        user = getuser();
        memset(Gflag, 0, sizeof Gflag);

        ARGBEGIN{
        case 'c':
                chpass = 1;
                break;
        case 'G':
                Gflag[ngfile]++;
                /* fall through */
        case 'g':
                if(ngfile >= MAXFILES)
                        exits("too many gfiles");
                gfile[ngfile++] = EARGF(usage());
                break;
        case 'i':
                pass_stdin = 1;
                break;
        case 'n':
                pass_nvram = 1;
                break;
        case 'p':
                if(npfile >= MAXFILES)
                        exits("too many pfiles");
                pfile[npfile++] = EARGF(usage());
                break;
        case 'r':
                if(nrfile >= MAXFILES)
                        exits("too many rfiles");
                rfile[nrfile++] = EARGF(usage());
                break;
        case 's':
                serve = EARGF(usage());
                break;
        case 'u':
                user = EARGF(usage());
                break;
        case 'v':
                verbose++;
                break;
        default:
                usage();
                break;
        }ARGEND;
        gfile[ngfile] = nil;
        pfile[npfile] = nil;
        rfile[nrfile] = nil;

        if(argc!=0 || user==nil)
                usage();

        if(chpass && (ngfile || npfile || nrfile)){
                fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
                exits("usage");
        }

        rc = strlen(serve) + sizeof "tcp!!99990";
        tcpserve = emalloc(rc);
        if(strchr(serve,'!'))
                strcpy(tcpserve, serve);
        else
                snprint(tcpserve, rc, "tcp!%s!5356", serve);
        c = login(user, tcpserve, pass_stdin, pass_nvram);
        free(tcpserve);
        if(c == nil)
                sysfatal("secstore authentication failed");
        if(chpass)
                rc = chpasswd(c, user);
        else
                rc = cmd(c, gfile, Gflag, pfile, rfile);
        if(rc < 0)
                sysfatal("secstore cmd failed");
        exits("");
}