URI: 
       tadd secstored; use readcons - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 096ff3e14a188992d2dfe59c7fd3f5d6da791331
   DIR parent d93fca6a7ab52f518d3e8aca1fc94139313b97ad
  HTML Author: rsc <devnull@localhost>
       Date:   Fri, 11 Feb 2005 19:39:51 +0000
       
       add secstored; use readcons
       
       Diffstat:
         M src/cmd/secstore/aescbc.c           |       2 +-
         M src/cmd/secstore/dirls.c            |       2 +-
         M src/cmd/secstore/mkfile             |       9 +++++++--
         A src/cmd/secstore/secacct.c          |      35 +++++++++++++++++++++++++++++++
         A src/cmd/secstore/secchk.c           |      28 ++++++++++++++++++++++++++++
         M src/cmd/secstore/secstore.c         |      17 +++++++++++------
         M src/cmd/secstore/secstore.h         |       5 +++--
         A src/cmd/secstore/secstored.c        |     420 +++++++++++++++++++++++++++++++
         A src/cmd/secstore/secureidcheck.c    |     446 ++++++++++++++++++++++++++++++
         A src/cmd/secstore/secuser.c          |     244 +++++++++++++++++++++++++++++++
         M src/cmd/secstore/util.c             |      10 ----------
       
       11 files changed, 1196 insertions(+), 22 deletions(-)
       ---
   DIR diff --git a/src/cmd/secstore/aescbc.c b/src/cmd/secstore/aescbc.c
       t@@ -75,7 +75,7 @@ main(int argc, char **argv)
                        while(buf[n-1] == '\n')
                                buf[--n] = 0;
                }else{
       -                pass = getpassm("aescbc key:");
       +                pass = readcons("aescbc key", nil, 1);
                        n = strlen(pass);
                        if(n >= BUF)
                                exits("key too long");
   DIR diff --git a/src/cmd/secstore/dirls.c b/src/cmd/secstore/dirls.c
       t@@ -64,7 +64,7 @@ dirls(char *path)
                if(path==nil || (ndir = ls(path, &dirbuf)) < 0)
                        return nil;
        
       -        qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(void *, void *))compare);
       +        qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(const void *, const void *))compare);
                for(nmwid=lenwid=i=0; i<ndir; i++){
                        if((m = strlen(dirbuf[i].name)) > nmwid)
                                nmwid = m;
   DIR diff --git a/src/cmd/secstore/mkfile b/src/cmd/secstore/mkfile
       t@@ -13,10 +13,15 @@ OFILES =\
                util.$O\
        
        
       -TARG=aescbc secstore
       +TARG=aescbc secstore secstored secuser
        
        <$PLAN9/src/mkmany
        
       -$O.aescbc:        aescbc.$O util.$O  $LIB ${SHORTLIB:%=$LIBDIR/lib%.a}
       +$O.aescbc:        aescbc.$O util.$O
                $LD -o $target $prereq $LDFLAGS
        
       +$O.secstored: secstored.$O dirls.$O secureidcheck.$O $OFILES
       +        $LD -o $target $prereq
       +
       +$O.secuser: secuser.$O $OFILES
       +        $LD -o $target $prereq
   DIR diff --git a/src/cmd/secstore/secacct.c b/src/cmd/secstore/secacct.c
       t@@ -0,0 +1,35 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <ip.h>
       +
       +int verbose = 1;
       +static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n";
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int n, m, fd;
       +        uchar buf[500];
       +
       +        if(argc != 2)
       +                exits("usage: secacct userid");
       +
       +        n = snprint((char*)buf, sizeof buf, testmess, argv[1]);
       +        hnputs(buf, 0x8000+n-2);
       +
       +        fd = dial("tcp!ruble.cs.bell-labs.com!5356", 0, 0, 0);
       +        if(fd < 0)
       +                exits("cannot dial ruble");
       +        if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2)
       +                exits("cannot exchange first round");
       +        n = ((buf[0]&0x7f)<<8) + buf[1];
       +        if(n+1 > sizeof buf)
       +                exits("implausibly large count");
       +        m = readn(fd, buf, n);
       +        close(fd);
       +        if(m != n)
       +                fprint(2,"short read from secstore\n");
       +        buf[m] = 0;
       +        print("%s\n", (char*)buf);
       +        exits(0);
       +}
   DIR diff --git a/src/cmd/secstore/secchk.c b/src/cmd/secstore/secchk.c
       t@@ -0,0 +1,28 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <ndb.h>
       +
       +extern char* secureidcheck(char *user, char *response);
       +Ndb *db;
       +
       +void
       +main(int argc, char **argv)
       +{
       +        Ndb *db2;
       +
       +        if(argc!=2){
       +                fprint(2,"usage %s pinsecurid\n", argv[0]);
       +                exits("usage");
       +        }
       +        db = ndbopen("/lib/ndb/auth");
       +        if(db == 0)
       +                syslog(0, "secstore", "no /lib/ndb/auth");
       +        db2 = ndbopen(0);
       +        if(db2 == 0)
       +                syslog(0, "secstore", "no /lib/ndb/local");
       +        db = ndbcat(db, db2);
       +        print("user=%s\n", getenv("user"));
       +        print("%s\n", secureidcheck(getenv("user"), argv[1]));
       +        exits(0);
       +}
   DIR diff --git a/src/cmd/secstore/secstore.c b/src/cmd/secstore/secstore.c
       t@@ -16,6 +16,7 @@ typedef struct AuthConn{
        
        int verbose;
        Nvrsafe nvr;
       +char *SECSTORE_DIR;
        
        void
        usage(void)
       t@@ -311,7 +312,7 @@ chpasswd(AuthConn *c, char *id)
                // changing our password is vulnerable to connection failure
                for(;;){
                        snprint(prompt, sizeof(prompt), "new password for %s: ", id);
       -                newpass = getpassm(prompt);
       +                newpass = readcons(prompt, nil, 1);
                        if(newpass == nil)
                                goto Out;
                        if(strlen(newpass) >= 7)
       t@@ -324,9 +325,9 @@ chpasswd(AuthConn *c, char *id)
                }
                newpasslen = strlen(newpass);
                snprint(prompt, sizeof(prompt), "retype password: ");
       -        passck = getpassm(prompt);
       +        passck = readcons(prompt, nil, 1);
                if(passck == nil){
       -                fprint(2, "getpassmwd failed\n");
       +                fprint(2, "readcons failed\n");
                        goto Out;
                }
                if(strcmp(passck, newpass) != 0){
       t@@ -419,7 +420,9 @@ login(char *id, char *dest, int pass_stdin, int pass_nvram)
                        }
                        ntry++;
                        if(!pass_stdin && !pass_nvram){
       -                        pass = getpassm("secstore password: ");
       +                        pass = readcons("secstore password", nil, 1);
       +                        if(pass == nil)
       +                                pass = estrdup("");
                                if(strlen(pass) >= sizeof c->pass){
                                        fprint(2, "password too long, skipping secstore login\n");
                                        exits("password too long");
       t@@ -444,7 +447,7 @@ login(char *id, char *dest, int pass_stdin, int pass_nvram)
                                fprint(2, "Enter an empty password to quit.\n");
                }
                c->passlen = strlen(c->pass);
       -        fprint(2, "%s\n", S);
       +        fprint(2, "server: %s\n", S);
                free(S);
                if(readstr(c->conn, s) < 0){
                        c->conn->free(c->conn);
       t@@ -460,7 +463,9 @@ login(char *id, char *dest, int pass_stdin, int pass_nvram)
                                        exits("missing PIN+SecureID on standard input");
                                free(PINSTA);
                        }else{
       -                        pass = getpassm("STA PIN+SecureID: ");
       +                        pass = readcons("STA PIN+SecureID", nil, 1);
       +                        if(pass == nil)
       +                                pass = estrdup("");
                                strncpy(s+3, pass, (sizeof s)-4);
                                memset(pass, 0, strlen(pass));
                                free(pass);
   DIR diff --git a/src/cmd/secstore/secstore.h b/src/cmd/secstore/secstore.h
       t@@ -17,7 +17,6 @@ typedef struct PW {
        PW *getPW(char *, int);
        int putPW(PW *);
        void freePW(PW *);
       -char* getpassm(const char*);
        
        // *client: SConn, client name, passphrase
        // *server: SConn, (partial) 1st msg, PW entry
       t@@ -27,4 +26,6 @@ int PAKserver(SConn *, char *, char *, PW **);
        char *PAK_Hi(char *, char *, mpint *, mpint *);
        
        #define LOG "secstore"
       -#define SECSTORE_DIR        "/adm/secstore"
       +
       +extern        char        *SECSTORE_DIR;
       +
   DIR diff --git a/src/cmd/secstore/secstored.c b/src/cmd/secstore/secstored.c
       t@@ -0,0 +1,420 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <ndb.h>
       +#include <mp.h>
       +#include <libsec.h>
       +#include "SConn.h"
       +#include "secstore.h"
       +
       +char *SECSTORE_DIR;
       +char* secureidcheck(char *, char *);   // from /sys/src/cmd/auth/
       +extern char* dirls(char *path);
       +
       +int verbose;
       +Ndb *db;
       +
       +static void
       +usage(void)
       +{
       +        fprint(2, "usage: secstored [-R] [-S servername] [-s tcp!*!5356] [-v] [-x netmtpt]\n");
       +        exits("usage");
       +}
       +
       +static int
       +getdir(SConn *conn, char *id)
       +{
       +        char *ls, *s; 
       +        uchar *msg;
       +        int n, len;
       +
       +        s = emalloc(Maxmsg);
       +        snprint(s, Maxmsg, "%s/store/%s", SECSTORE_DIR, id);
       +
       +        if((ls = dirls(s)) == nil)
       +                len = 0;
       +        else
       +                len = strlen(ls);
       +
       +        /* send file size */
       +        snprint(s, Maxmsg, "%d", len);
       +        conn->write(conn, (uchar*)s, strlen(s));
       +
       +        /* send directory listing in Maxmsg chunks */
       +        n = Maxmsg;
       +        msg = (uchar*)ls;
       +        while(len > 0){
       +                if(len < Maxmsg)
       +                        n = len;
       +                conn->write(conn, msg, n);
       +                msg += n;
       +                len -= n;
       +        }
       +        free(s);
       +        free(ls);
       +        return 0;
       +}
       +
       +char *
       +validatefile(char *f)
       +{
       +        char *nl;
       +
       +        if(f==nil || *f==0)
       +                return nil;
       +        if(nl = strchr(f, '\n'))
       +                *nl = 0;
       +        if(strchr(f,'/') != nil || strcmp(f,"..")==0 || strlen(f) >= 300){
       +                syslog(0, LOG, "no slashes allowed: %s\n", f);
       +                return nil;
       +        }
       +        return f;
       +}
       +
       +static int
       +getfile(SConn *conn, char *id, char *gf)
       +{
       +        int n, gd, len;
       +        ulong mode;
       +        char *s;
       +        Dir *st;
       +
       +        if(strcmp(gf,".")==0)
       +                return getdir(conn, id);
       +
       +        /* send file size */
       +        s = emalloc(Maxmsg);
       +        snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, gf);
       +        gd = open(s, OREAD);
       +        if(gd < 0){
       +                syslog(0, LOG, "can't open %s: %r\n", s);
       +                free(s);
       +                conn->write(conn, (uchar*)"-1", 2);
       +                return -1;
       +        }
       +        st = dirfstat(gd);
       +        if(st == nil){
       +                syslog(0, LOG, "can't stat %s: %r\n", s);
       +                free(s);
       +                conn->write(conn, (uchar*)"-1", 2);
       +                return -1;
       +        }
       +        mode = st->mode;
       +        len = st->length;
       +        free(st);
       +        if(mode & DMDIR) {
       +                syslog(0, LOG, "%s should be a plain file, not a directory\n", s);
       +                free(s);
       +                conn->write(conn, (uchar*)"-1", 2);
       +                return -1;
       +        }
       +        if(len < 0 || len > MAXFILESIZE){
       +                syslog(0, LOG, "implausible filesize %d for %s\n", len, gf);
       +                free(s);
       +                conn->write(conn, (uchar*)"-3", 2);
       +                return -1;
       +        }
       +        snprint(s, Maxmsg, "%d", len);
       +        conn->write(conn, (uchar*)s, strlen(s));
       +
       +        /* send file in Maxmsg chunks */
       +        while(len > 0){
       +                n = read(gd, s, Maxmsg);
       +                if(n <= 0){
       +                        syslog(0, LOG, "read error on %s: %r\n", gf);
       +                        free(s);
       +                        return -1;
       +                }
       +                conn->write(conn, (uchar*)s, n);
       +                len -= n;
       +        }
       +        close(gd);
       +        free(s);
       +        return 0;
       +}
       +
       +static int
       +putfile(SConn *conn, char *id, char *pf)
       +{
       +        int n, nw, pd;
       +        long len;
       +        char s[Maxmsg+1];
       +
       +        /* get file size */
       +        n = readstr(conn, s);
       +        if(n < 0){
       +                syslog(0, LOG, "remote: %s: %r\n", s);
       +                return -1;
       +        }
       +        len = atoi(s);
       +        if(len == -1){
       +                syslog(0, LOG, "remote file %s does not exist\n", pf);
       +                return -1;
       +        }else if(len < 0 || len > MAXFILESIZE){
       +                syslog(0, LOG, "implausible filesize %ld for %s\n", len, pf);
       +                return -1;
       +        }
       +
       +        /* get file in Maxmsg chunks */
       +        if(strchr(pf,'/') != nil || strcmp(pf,"..")==0){
       +                syslog(0, LOG, "no slashes allowed: %s\n", pf);
       +                return -1;
       +        }
       +        snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, pf);
       +        pd = create(s, OWRITE, 0660);
       +        if(pd < 0){
       +                syslog(0, LOG, "can't open %s: %r\n", s);
       +                return -1;
       +        }
       +        while(len > 0){
       +                n = conn->read(conn, (uchar*)s, Maxmsg);
       +                if(n <= 0){
       +                        syslog(0, LOG, "empty file chunk\n");
       +                        return -1;
       +                }
       +                nw = write(pd, s, n);
       +                if(nw != n){
       +                        syslog(0, LOG, "write error on %s: %r", pf);
       +                        return -1;
       +                }
       +                len -= n;
       +        }
       +        close(pd);
       +        return 0;
       +
       +}
       +
       +static int
       +removefile(SConn *conn, char *id, char *f)
       +{
       +        Dir *d;
       +        char buf[Maxmsg];
       +
       +        snprint(buf, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, f);
       +
       +        if((d = dirstat(buf)) == nil){
       +                snprint(buf, sizeof buf, "remove failed: %r");
       +                writerr(conn, buf);
       +                return -1;
       +        }else if(d->mode & DMDIR){
       +                snprint(buf, sizeof buf, "can't remove a directory");
       +                writerr(conn, buf);
       +                free(d);
       +                return -1;
       +        }
       +
       +        free(d);
       +        if(remove(buf) < 0){
       +                snprint(buf, sizeof buf, "remove failed: %r");
       +                writerr(conn, buf);
       +                return -1;
       +        }
       +        return 0;
       +}
       +
       +/* given line directory from accept, returns ipaddr!port */
       +static char*
       +remoteIP(char *ldir)
       +{
       +        int fd, n;
       +        char rp[100], ap[500];
       +
       +        snprint(rp, sizeof rp, "%s/remote", ldir);
       +        fd = open(rp, OREAD);
       +        if(fd < 0)
       +                return strdup("?!?");
       +        n = read(fd, ap, sizeof ap);
       +        if(n <= 0 || n == sizeof ap){
       +                fprint(2, "error %d reading %s: %r\n", n, rp);
       +                return strdup("?!?");
       +        }
       +        close(fd);
       +        ap[n--] = 0;
       +        if(ap[n] == '\n')
       +                ap[n] = 0;
       +        return strdup(ap);
       +}
       +
       +static int
       +dologin(int fd, char *S, int forceSTA)
       +{
       +        int i, n, rv;
       +        char *file, *mess;
       +        char msg[Maxmsg+1];
       +        PW *pw;
       +        SConn *conn;
       +
       +        pw = nil;
       +        rv = -1;
       +
       +        // collect the first message
       +        if((conn = newSConn(fd)) == nil)
       +                return -1;
       +        if(readstr(conn, msg) < 0){
       +                fprint(2, "remote: %s: %r\n", msg);
       +                writerr(conn, "can't read your first message");
       +                goto Out;
       +        }
       +
       +        // authenticate
       +        if(PAKserver(conn, S, msg, &pw) < 0){
       +                if(pw != nil)
       +                        syslog(0, LOG, "secstore denied for %s", pw->id);
       +                goto Out;
       +        }
       +        if((forceSTA || pw->status&STA) != 0){
       +                conn->write(conn, (uchar*)"STA", 3);
       +                if(readstr(conn, msg) < 10 || strncmp(msg, "STA", 3) != 0){
       +                        syslog(0, LOG, "no STA from %s", pw->id);
       +                        goto Out;
       +                }
       +                mess = secureidcheck(pw->id, msg+3);
       +                if(mess != nil){
       +                        syslog(0, LOG, "secureidcheck denied %s because %s", pw->id, mess);
       +                        goto Out;
       +                }
       +        }
       +        conn->write(conn, (uchar*)"OK", 2);
       +        syslog(0, LOG, "AUTH %s", pw->id);
       +
       +        // perform operations as asked
       +        while((n = readstr(conn, msg)) > 0){
       +                syslog(0, LOG, "[%s] %s", pw->id, msg);
       +
       +                if(strncmp(msg, "GET ", 4) == 0){
       +                        file = validatefile(msg+4);
       +                        if(file==nil || getfile(conn, pw->id, file) < 0)
       +                                goto Err;
       +
       +                }else if(strncmp(msg, "PUT ", 4) == 0){
       +                        file = validatefile(msg+4);
       +                        if(file==nil || putfile(conn, pw->id, file) < 0){
       +                                syslog(0, LOG, "failed PUT %s/%s", pw->id, file);
       +                                goto Err;
       +                        }
       +
       +                }else if(strncmp(msg, "RM ", 3) == 0){
       +                        file = validatefile(msg+3);
       +                        if(file==nil || removefile(conn, pw->id, file) < 0){
       +                                syslog(0, LOG, "failed RM %s/%s", pw->id, file);
       +                                goto Err;
       +                        }
       +
       +                }else if(strncmp(msg, "CHPASS", 6) == 0){
       +                        if(readstr(conn, msg) < 0){
       +                                syslog(0, LOG, "protocol botch CHPASS for %s", pw->id);
       +                                writerr(conn, "protocol botch while setting PAK");
       +                                goto Out;
       +                        }
       +                        pw->Hi = strtomp(msg, nil, 64, pw->Hi);
       +                        for(i=0; i < 4 && putPW(pw) < 0; i++)
       +                                syslog(0, LOG, "password change failed for %s (%d): %r", pw->id, i);
       +                        if(i==4)
       +                                goto Out;
       +
       +                }else if(strncmp(msg, "BYE", 3) == 0){
       +                        rv = 0;
       +                        break;
       +
       +                }else{
       +                        writerr(conn, "unrecognized operation");
       +                        break;
       +                }
       +
       +        }
       +        if(n <= 0)
       +                syslog(0, LOG, "%s closed connection without saying goodbye\n", pw->id);
       +
       +Out:
       +        freePW(pw);
       +        conn->free(conn);
       +        return rv;
       +Err:
       +        writerr(conn, "operation failed");
       +        goto Out;
       +}
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int afd, dfd, lcfd, forceSTA = 0;
       +        char adir[40], ldir[40], *remote;
       +        char *serve = "tcp!*!5356", *p, aserve[128];
       +        char *S = "secstore";
       +        char *dbpath;
       +        Ndb *db2;
       +
       +        S = sysname();
       +        SECSTORE_DIR = unsharp("#9/secstore");
       +//        setnetmtpt(net, sizeof(net), nil);
       +        ARGBEGIN{
       +        case 'R':
       +                forceSTA = 1;
       +                break;
       +        case 's':
       +                serve = EARGF(usage());
       +                break;
       +        case 'S':
       +                S = EARGF(usage());
       +                break;
       +        case 'x':
       +                p = ARGF();
       +                if(p == nil)
       +                        usage();
       +                USED(p);
       +        //        setnetmtpt(net, sizeof(net), p);
       +                break;
       +        case 'v':
       +                verbose++;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND;
       +
       +        if(!verbose)
       +                switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
       +                case -1:
       +                        sysfatal("fork: %r");
       +                case 0:
       +                        break;
       +                default:
       +                        exits(0);
       +                }
       +
       +        snprint(aserve, sizeof aserve, "%s", serve);
       +        afd = announce(aserve, adir);
       +        if(afd < 0)
       +                sysfatal("%s: %r\n", aserve);
       +        syslog(0, LOG, "ANNOUNCE %s", aserve);
       +        for(;;){
       +                if((lcfd = listen(adir, ldir)) < 0)
       +                        exits("can't listen");
       +                switch(fork()){
       +                case -1:
       +                        fprint(2, "secstore forking: %r\n");
       +                        close(lcfd);
       +                        break;
       +                case 0:
       +                        // "/lib/ndb/common.radius does not exist" if db set before fork
       +                        db = ndbopen(dbpath=unsharp("#9/ndb/auth"));
       +                        if(db == 0)
       +                                syslog(0, LOG, "no ndb/auth");
       +                        db2 = ndbopen(0);
       +                        if(db2 == 0)
       +                                syslog(0, LOG, "no ndb/local");
       +                        db = ndbcat(db, db2);
       +                        if((dfd = accept(lcfd, ldir)) < 0)
       +                                exits("can't accept");
       +                        alarm(30*60*1000);         // 30 min
       +                        remote = remoteIP(ldir);
       +                        syslog(0, LOG, "secstore from %s", remote);
       +                        free(remote);
       +                        dologin(dfd, S, forceSTA);
       +                        exits(nil);
       +                default:
       +                        close(lcfd);
       +                        break;
       +                }
       +        }
       +}
       +
   DIR diff --git a/src/cmd/secstore/secureidcheck.c b/src/cmd/secstore/secureidcheck.c
       t@@ -0,0 +1,446 @@
       +/* RFC2138 */
       +#include <u.h>
       +#include <libc.h>
       +#include <ip.h>
       +#include <ctype.h>
       +#include <mp.h>
       +#include <libsec.h>
       +#include <bio.h>
       +#include <ndb.h>
       +#define AUTHLOG "auth"
       +
       +enum{        R_AccessRequest=1,        /* Packet code */
       +        R_AccessAccept=2,
       +        R_AccessReject=3,
       +        R_AccessChallenge=11,
       +        R_UserName=1,
       +        R_UserPassword=2,
       +        R_NASIPAddress=4,
       +        R_ReplyMessage=18,
       +        R_State=24,
       +        R_NASIdentifier=32
       +};
       +
       +typedef struct Secret{
       +        uchar *s;
       +        int len;
       +} Secret;
       +
       +typedef struct Attribute{
       +        struct Attribute *next;
       +        uchar type;
       +        uchar len;        // number of bytes in value
       +        uchar val[256];
       +} Attribute;
       +
       +typedef struct Packet{
       +        uchar code, ID;
       +        uchar authenticator[16];
       +        Attribute first;
       +} Packet;
       +
       +// assumes pass is at most 16 chars
       +void
       +hide(Secret *shared, uchar *auth, Secret *pass, uchar *x)
       +{
       +        DigestState *M;
       +        int i, n = pass->len;
       +
       +        M = md5(shared->s, shared->len, nil, nil);
       +        md5(auth, 16, x, M);
       +        if(n > 16)
       +                n = 16;
       +        for(i = 0; i < n; i++)
       +                x[i] ^= (pass->s)[i];
       +}
       +
       +int
       +authcmp(Secret *shared, uchar *buf, int m, uchar *auth)
       +{
       +        DigestState *M;
       +        uchar x[16];
       +
       +        M = md5(buf, 4, nil, nil); // Code+ID+Length
       +        M = md5(auth, 16, nil, M); // RequestAuth
       +        M = md5(buf+20, m-20, nil, M); // Attributes
       +        md5(shared->s, shared->len, x, M);
       +        return memcmp(x, buf+4, 16);
       +}
       +
       +Packet*
       +newRequest(uchar *auth)
       +{
       +        static uchar ID = 0;
       +        Packet *p;
       +
       +        p = (Packet*)malloc(sizeof(*p));
       +        if(p == nil)
       +                return nil;
       +        p->code = R_AccessRequest;
       +        p->ID = ++ID;
       +        memmove(p->authenticator, auth, 16);
       +        p->first.next = nil;
       +        p->first.type = 0;
       +        return p;
       +}
       +
       +void
       +freePacket(Packet *p)
       +{
       +        Attribute *a, *x;
       +
       +        if(!p)
       +                return;
       +        a = p->first.next;
       +        while(a){
       +                x = a;
       +                a = a->next;
       +                free(x);
       +        }
       +        free(p);
       +}
       +
       +int
       +ding(void *v, char *msg)
       +{
       +        USED(v);
       +/*        syslog(0, AUTHLOG, "ding %s", msg); */
       +        if(strstr(msg, "alarm"))
       +                return 1;
       +        return 0;
       +}
       +
       +Packet *
       +rpc(char *dest, Secret *shared, Packet *req)
       +{
       +        uchar buf[4096], buf2[4096], *b, *e;
       +        Packet *resp;
       +        Attribute *a;
       +        int m, n, fd, try;
       +
       +        // marshal request
       +        e = buf + sizeof buf;
       +        buf[0] = req->code;
       +        buf[1] = req->ID;
       +        memmove(buf+4, req->authenticator, 16);
       +        b = buf+20;
       +        for(a = &req->first; a; a = a->next){
       +                if(b + 2 + a->len > e)
       +                        return nil;
       +                *b++ = a->type;
       +                *b++ = 2 + a->len;
       +                memmove(b, a->val, a->len);
       +                b += a->len;
       +        }
       +        n = b-buf;
       +        buf[2] = n>>8;
       +        buf[3] = n;
       +
       +        // send request, wait for reply
       +        fd = dial(dest, 0, 0, 0);
       +        if(fd < 0){
       +                syslog(0, AUTHLOG, "%s: rpc can't get udp channel", dest);
       +                return nil;
       +        }
       +        atnotify(ding, 1);
       +        m = -1;
       +        for(try = 0; try < 2; try++){
       +                alarm(4000);
       +                m = write(fd, buf, n);
       +                if(m != n){
       +                        syslog(0, AUTHLOG, "%s: rpc write err %d %d: %r", dest, m, n);
       +                        m = -1;
       +                        break;
       +                }
       +                m = read(fd, buf2, sizeof buf2);
       +                alarm(0);
       +                if(m < 0){
       +                        syslog(0, AUTHLOG, "%s rpc read err %d: %r", dest, m);
       +                        break; // failure
       +                }
       +                if(m == 0 || buf2[1] != buf[1]){  // need matching ID
       +                        syslog(0, AUTHLOG, "%s unmatched reply %d", dest, m);
       +                        continue;
       +                }
       +                if(authcmp(shared, buf2, m, buf+4) == 0)
       +                        break;
       +                syslog(0, AUTHLOG, "%s bad rpc chksum", dest);
       +        }
       +        close(fd);
       +        if(m <= 0)
       +                return nil;
       +
       +        // unmarshal reply
       +        b = buf2;
       +        e = buf2+m;
       +        resp = (Packet*)malloc(sizeof(*resp));
       +        if(resp == nil)
       +                return nil;
       +        resp->code = *b++;
       +        resp->ID = *b++;
       +        n = *b++;
       +        n = (n<<8) | *b++;
       +        if(m != n){
       +                syslog(0, AUTHLOG, "rpc got %d bytes, length said %d", m, n);
       +                if(m > n)
       +                        e = buf2+n;
       +        }
       +        memmove(resp->authenticator, b, 16);
       +        b += 16;
       +        a = &resp->first;
       +        a->type = 0;
       +        while(1){
       +                if(b >= e){
       +                        a->next = nil;
       +                        break;                        // exit loop
       +                }
       +                a->type = *b++;
       +                a->len = (*b++) - 2;
       +                if(b + a->len > e){ // corrupt packet
       +                        a->next = nil;
       +                        freePacket(resp);
       +                        return nil;
       +                }
       +                memmove(a->val, b, a->len);
       +                b += a->len;
       +                if(b < e){  // any more attributes?
       +                        a->next = (Attribute*)malloc(sizeof(*a));
       +                        if(a->next == nil){
       +                                free(req);
       +                                return nil;
       +                        }
       +                        a = a->next;
       +                }
       +        }
       +        return resp;
       +}
       +
       +int
       +setAttribute(Packet *p, uchar type, uchar *s, int n)
       +{
       +        Attribute *a;
       +
       +        a = &p->first;
       +        if(a->type != 0){
       +                a = (Attribute*)malloc(sizeof(*a));
       +                if(a == nil)
       +                        return -1;
       +                a->next = p->first.next;
       +                p->first.next = a;
       +        }
       +        a->type = type;
       +        a->len = n;
       +        if(a->len > 253 )  // RFC2138, section 5
       +                a->len = 253;
       +        memmove(a->val, s, a->len);
       +        return 0;
       +}
       +
       +/* return a reply message attribute string */
       +char*
       +replymsg(Packet *p)
       +{
       +        Attribute *a;
       +        static char buf[255];
       +
       +        for(a = &p->first; a; a = a->next){
       +                if(a->type == R_ReplyMessage){
       +                        if(a->len >= sizeof buf)
       +                                a->len = sizeof(buf)-1;
       +                        memmove(buf, a->val, a->len);
       +                        buf[a->len] = 0;
       +                }
       +        }
       +        return buf;
       +}
       +
       +/* for convenience while debugging */
       +char *replymess;
       +Attribute *stateattr;
       +
       +void
       +logPacket(Packet *p)
       +{
       +        Attribute *a;
       +        char buf[255];
       +        char pbuf[4*1024];
       +        uchar *au = p->authenticator;
       +        int i;
       +        char *np, *e;
       +
       +        e = pbuf + sizeof(pbuf);
       +
       +        np = seprint(pbuf, e, "Packet ID=%d auth=%x %x %x... ", p->ID, au[0], au[1], au[2]);
       +        switch(p->code){
       +        case R_AccessRequest:
       +                np = seprint(np, e, "request\n");
       +                break;
       +        case R_AccessAccept:
       +                np = seprint(np, e, "accept\n");
       +                break;
       +        case R_AccessReject:
       +                np = seprint(np, e, "reject\n");
       +                break;
       +        case R_AccessChallenge:
       +                np = seprint(np, e, "challenge\n");
       +                break;
       +        default:
       +                np = seprint(np, e, "code=%d\n", p->code);
       +                break;
       +        }
       +        replymess = "0000000";
       +        for(a = &p->first; a; a = a->next){
       +                if(a->len > 253 )
       +                        a->len = 253;
       +                memmove(buf, a->val, a->len);
       +                np = seprint(np, e, " [%d]", a->type);
       +                for(i = 0; i<a->len; i++)
       +                        if(isprint(a->val[i]))
       +                                np = seprint(np, e, "%c", a->val[i]);
       +                        else
       +                                np = seprint(np, e, "\\%o", a->val[i]);
       +                np = seprint(np, e, "\n");
       +                buf[a->len] = 0;
       +                if(a->type == R_ReplyMessage)
       +                        replymess = strdup(buf);
       +                else if(a->type == R_State)
       +                        stateattr = a;
       +        }
       +
       +        syslog(0, AUTHLOG, "%s", pbuf);
       +}
       +
       +static uchar*
       +getipv4addr(void)
       +{
       +        Ipifc *nifc;
       +        Iplifc *lifc;
       +        static Ipifc *ifc;
       +
       +        ifc = readipifc("/net", ifc, -1);
       +        for(nifc = ifc; nifc; nifc = nifc->next)
       +                for(lifc = nifc->lifc; lifc; lifc = lifc->next)
       +                        if(ipcmp(lifc->ip, IPnoaddr) != 0 && ipcmp(lifc->ip, v4prefix) != 0)
       +                                return lifc->ip;
       +        return nil;
       +}
       +
       +extern Ndb *db;
       +
       +/* returns 0 on success, error message on failure */
       +char*
       +secureidcheck(char *user, char *response)
       +{
       +        Packet *req = nil, *resp = nil;
       +        ulong u[4];
       +        uchar x[16];
       +        char *radiussecret;
       +        char ruser[ 64];
       +        char dest[3*IPaddrlen+20];
       +        Secret shared, pass;
       +        char *rv = "authentication failed";
       +        Ndbs s;
       +        Ndbtuple *t, *nt, *tt;
       +        uchar *ip;
       +        static Ndb *netdb;
       +
       +        if(netdb == nil)
       +                netdb = ndbopen(0);
       +
       +        /* bad responses make them disable the fob, avoid silly checks */
       +        if(strlen(response) < 4 || strpbrk(response,"abcdefABCDEF") != nil)
       +                goto out;
       +
       +        /* get radius secret */
       +        radiussecret = ndbgetvalue(db, &s, "radius", "lra-radius", "secret", &t);
       +        if(radiussecret == nil){
       +                syslog(0, AUTHLOG, "secureidcheck: nil radius secret: %r");
       +                goto out;
       +        }
       +
       +        /* translate user name if we have to */
       +        strcpy(ruser, user);
       +        for(nt = t; nt; nt = nt->entry){
       +                if(strcmp(nt->attr, "uid") == 0 && strcmp(nt->val, user) == 0)
       +                        for(tt = nt->line; tt != nt; tt = tt->line)
       +                                if(strcmp(tt->attr, "rid") == 0){
       +                                        strcpy(ruser, tt->val);
       +                                        break;
       +                                }
       +        }
       +        ndbfree(t);
       +
       +        u[0] = fastrand();
       +        u[1] = fastrand();
       +        u[2] = fastrand();
       +        u[3] = fastrand();
       +        req = newRequest((uchar*)u);
       +        if(req == nil)
       +                goto out;
       +        shared.s = (uchar*)radiussecret;
       +        shared.len = strlen(radiussecret);
       +        ip = getipv4addr();
       +        if(ip == nil){
       +                syslog(0, AUTHLOG, "no interfaces: %r\n");
       +                goto out;
       +        }
       +        if(setAttribute(req, R_NASIPAddress, ip + IPv4off, 4) < 0)
       +                goto out;
       +
       +        if(setAttribute(req, R_UserName, (uchar*)ruser, strlen(ruser)) < 0)
       +                goto out;
       +        pass.s = (uchar*)response;
       +        pass.len = strlen(response);
       +        hide(&shared, req->authenticator, &pass, x);
       +        if(setAttribute(req, R_UserPassword, x, 16) < 0)
       +                goto out;
       +
       +        t = ndbsearch(netdb, &s, "sys", "lra-radius");
       +        if(t == nil){
       +                syslog(0, AUTHLOG, "secureidcheck: nil radius sys search: %r\n");
       +                goto out;
       +        }
       +        for(nt = t; nt; nt = nt->entry){
       +                if(strcmp(nt->attr, "ip") != 0)
       +                        continue;
       +
       +                snprint(dest,sizeof dest,"udp!%s!oradius", nt->val);
       +                resp = rpc(dest, &shared, req);
       +                if(resp == nil){
       +                        syslog(0, AUTHLOG, "%s nil response", dest);
       +                        continue;
       +                }
       +                if(resp->ID != req->ID){
       +                        syslog(0, AUTHLOG, "%s mismatched ID  req=%d resp=%d",
       +                                dest, req->ID, resp->ID);
       +                        freePacket(resp);
       +                        resp = nil;
       +                        continue;
       +                }
       +        
       +                switch(resp->code){
       +                case R_AccessAccept:
       +                        syslog(0, AUTHLOG, "%s accepted ruser=%s", dest, ruser);
       +                        rv = nil;
       +                        break;
       +                case R_AccessReject:
       +                        syslog(0, AUTHLOG, "%s rejected ruser=%s %s", dest, ruser, replymsg(resp));
       +                        rv = "secureid failed";
       +                        break;
       +                case R_AccessChallenge:
       +                        syslog(0, AUTHLOG, "%s challenge ruser=%s %s", dest, ruser, replymsg(resp));
       +                        rv = "secureid out of sync";
       +                        break;
       +                default:
       +                        syslog(0, AUTHLOG, "%s code=%d ruser=%s %s", dest, resp->code, ruser, replymsg(resp));
       +                        break;
       +                }
       +                break; // we have a proper reply, no need to ask again
       +        }
       +        ndbfree(t);
       +        free(radiussecret);
       +out:
       +        freePacket(req);
       +        freePacket(resp);
       +        return rv;
       +}
   DIR diff --git a/src/cmd/secstore/secuser.c b/src/cmd/secstore/secuser.c
       t@@ -0,0 +1,244 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <mp.h>
       +#include <libsec.h>
       +#include "SConn.h"
       +#include "secstore.h"
       +
       +int verbose;
       +
       +static void userinput(char *, int);
       +char *SECSTORE_DIR;
       +
       +static void
       +ensure_exists(char *f, ulong perm)
       +{
       +        int fd;
       +
       +        if(access(f, AEXIST) >= 0)
       +                return;
       +        if(verbose)
       +                fprint(2,"first time setup for secstore: create %s %lo\n", f, perm);
       +        fd = create(f, OREAD, perm);
       +        if(fd < 0){
       +                fprint(2, "unable to create %s\n", f);
       +                exits("secstored directories");
       +        }
       +        close(fd);
       +}
       +
       +
       +int
       +main(int argc, char **argv)
       +{
       +        int isnew;
       +        char *id, buf[Maxmsg], home[Maxmsg], prompt[100], *hexHi;
       +        char *pass, *passck;
       +        long expsecs;
       +        mpint *H = mpnew(0), *Hi = mpnew(0);
       +        PW *pw;
       +        Tm *tm;
       +
       +        SECSTORE_DIR = unsharp("#9/secstore");
       +
       +        ARGBEGIN{
       +        case 'v':
       +                verbose++;
       +                break;
       +        }ARGEND;
       +        if(argc!=1){
       +                print("usage: secuser [-v] <user>\n");
       +                exits("usage");
       +        }
       +
       +        ensure_exists(SECSTORE_DIR, DMDIR|0755L);
       +        snprint(home, sizeof(home), "%s/who", SECSTORE_DIR);
       +        ensure_exists(home, DMDIR|0755L);
       +        snprint(home, sizeof(home), "%s/store", SECSTORE_DIR);
       +        ensure_exists(home, DMDIR|0700L);
       +
       +        id = argv[0];
       +        if(verbose)
       +                fprint(2,"secuser %s\n", id);
       +        if((pw = getPW(id,1)) == nil){
       +                isnew = 1;
       +                print("new account (because %s/%s %r)\n", SECSTORE_DIR, id);
       +                pw = emalloc(sizeof(*pw));
       +                pw->id = estrdup(id);
       +                snprint(home, sizeof(home), "%s/store/%s", SECSTORE_DIR, id);
       +                if(access(home, AEXIST) == 0){
       +                        print("new user, but directory %s already exists\n", home);
       +                        exits(home);
       +                }
       +        }else{
       +                isnew = 0;
       +        }
       +
       +        /* get main password for id */
       +        for(;;){
       +                if(isnew)
       +                        snprint(prompt, sizeof(prompt), "%s password", id);
       +                else
       +                        snprint(prompt, sizeof(prompt), "%s password [default = don't change]", id);
       +                pass = readcons(prompt, nil, 1);
       +                if(pass == nil){
       +                        print("getpass failed\n");
       +                        exits("getpass failed");
       +                }
       +                if(verbose)
       +                        print("%ld characters\n", strlen(pass));
       +                if(pass[0] == '\0' && isnew == 0)
       +                        break;
       +                if(strlen(pass) >= 7)
       +                        break;
       +                print("password must be at least 7 characters\n");
       +        }
       +
       +        if(pass[0] != '\0'){
       +                snprint(prompt, sizeof(prompt), "retype password");
       +                if(verbose)
       +                        print("confirming...\n");
       +                passck = readcons(prompt, nil, 1);
       +                if(passck == nil){
       +                        print("getpass failed\n");
       +                        exits("getpass failed");
       +                }
       +                if(strcmp(pass, passck) != 0){
       +                        print("passwords didn't match\n");
       +                        exits("no match");
       +                }
       +                memset(passck, 0, strlen(passck));
       +                free(passck);
       +                hexHi = PAK_Hi(id, pass, H, Hi);
       +                memset(pass, 0, strlen(pass));
       +                free(pass);
       +                free(hexHi);
       +                mpfree(H);
       +                pw->Hi = Hi;
       +        }
       +
       +        /* get expiration time (midnight of date specified) */
       +        if(isnew)
       +                expsecs = time(0) + 365*24*60*60;
       +        else
       +                expsecs = pw->expire;
       +
       +        for(;;){
       +                tm = localtime(expsecs);
       +                print("expires [DDMMYYYY, default = %2.2d%2.2d%4.4d]: ",
       +                                tm->mday, tm->mon, tm->year+1900);
       +                userinput(buf, sizeof(buf));
       +                if(strlen(buf) == 0)
       +                        break;
       +                if(strlen(buf) != 8){
       +                        print("!bad date format: %s\n", buf);
       +                        continue;
       +                }
       +                tm->mday = (buf[0]-'0')*10 + (buf[1]-'0');
       +                if(tm->mday > 31 || tm->mday < 1){
       +                        print("!bad day of month: %d\n", tm->mday);
       +                        continue;
       +                }
       +                tm->mon = (buf[2]-'0')*10 + (buf[3]-'0') - 1;
       +                if(tm->mon > 11 || tm->mday < 0){
       +                        print("!bad month: %d\n", tm->mon + 1);
       +                        continue;
       +                }
       +                tm->year = atoi(buf+4) - 1900;
       +                if(tm->year < 70){
       +                        print("!bad year: %d\n", tm->year + 1900);
       +                        continue;
       +                }
       +                tm->sec = 59;
       +                tm->min = 59;
       +                tm->hour = 23;
       +                tm->yday = 0;
       +                expsecs = tm2sec(tm);
       +                break;
       +        }
       +        pw->expire = expsecs;
       +
       +        /* failed logins */
       +        if(pw->failed != 0 )
       +                print("clearing %d failed login attempts\n", pw->failed);
       +        pw->failed = 0;
       +
       +        /* status bits */
       +        if(isnew)
       +                pw->status = Enabled;
       +        for(;;){
       +                print("Enabled or Disabled [default %s]: ",
       +                        (pw->status & Enabled) ? "Enabled" : "Disabled" );
       +                userinput(buf, sizeof(buf));
       +                if(strlen(buf) == 0)
       +                        break;
       +                if(buf[0]=='E' || buf[0]=='e'){
       +                        pw->status |= Enabled;
       +                        break;
       +                }
       +                if(buf[0]=='D' || buf[0]=='d'){
       +                        pw->status = pw->status & ~Enabled;
       +                        break;
       +                }
       +        }
       +        for(;;){
       +                print("require STA? [default %s]: ",
       +                        (pw->status & STA) ? "yes" : "no" );
       +                userinput(buf, sizeof(buf));
       +                if(strlen(buf) == 0)
       +                        break;
       +                if(buf[0]=='Y' || buf[0]=='y'){
       +                        pw->status |= STA;
       +                        break;
       +                }
       +                if(buf[0]=='N' || buf[0]=='n'){
       +                        pw->status = pw->status & ~STA;
       +                        break;
       +                }
       +        }
       +
       +        /* free form field */
       +        if(isnew)
       +                pw->other = nil;
       +        print("comments [default = %s]: ", (pw->other == nil) ? "" : pw->other);
       +        userinput(buf, 72);  /* 72 comes from password.h */
       +        if(buf[0])
       +                if((pw->other = strdup(buf)) == nil)
       +                        sysfatal("strdup");
       +
       +        syslog(0, LOG, "CHANGELOGIN for '%s'", pw->id);
       +        if(putPW(pw) < 0){
       +                print("error writing entry: %r\n");
       +                exits("can't write password file");
       +        }else{
       +                print("change written\n");
       +                if(isnew && create(home, OREAD, DMDIR | 0775L) < 0){
       +                        print("unable to create %s: %r\n", home);
       +                        exits(home);
       +                }
       +        }
       +
       +        exits("");
       +        return 1;  /* keep  other compilers happy */
       +}
       +
       +
       +static void
       +userinput(char *buf, int blen)
       +{
       +        int n;
       +
       +        while(1){
       +                n = read(0, buf, blen);
       +                if(n<=0)
       +                        exits("read error");
       +                if(buf[n-1]=='\n'){
       +                        buf[n-1] = '\0';
       +                        return;
       +                }
       +                buf += n;  blen -= n;
       +                if(blen<=0)
       +                        exits("input too large");
       +        }
       +}
       +
   DIR diff --git a/src/cmd/secstore/util.c b/src/cmd/secstore/util.c
       t@@ -26,13 +26,3 @@ estrdup(char *s)
                        sysfatal("estrdup");
                return s;
        }
       -
       -char *
       -getpassm(char *prompt)
       -{
       -        char *p = getpass(prompt);
       -
       -        if(p == nil || (p = strdup(p)) == nil)
       -                sysfatal("getpassm");
       -        return p;
       -}