commit 096ff3e14a188992d2dfe59c7fd3f5d6da791331 from: rsc date: Fri Feb 11 19:39:51 2005 UTC add secstored; use readcons commit - d93fca6a7ab52f518d3e8aca1fc94139313b97ad commit + 096ff3e14a188992d2dfe59c7fd3f5d6da791331 blob - 09e7955db3e73f6a657dfe5c35d93c5a5eb2883c blob + 56aeb00b531f69c7715634f77242d43cb3ba6740 --- src/cmd/secstore/aescbc.c +++ src/cmd/secstore/aescbc.c @@ -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"); blob - 0af9c410ea73683e99c12ed05a52bd8afb7e2640 blob + b4479413a8a00ba426432bf72f0ae9b5000eb8f1 --- src/cmd/secstore/dirls.c +++ src/cmd/secstore/dirls.c @@ -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 nmwid) nmwid = m; blob - 8d9e0f8e093e1118b57084a2ea4b121479f3f987 blob + 72986edd3ad56d1c3be889b3c9fcde8be552285f --- src/cmd/secstore/mkfile +++ src/cmd/secstore/mkfile @@ -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 blob - 94e9ff7e4f51fa729186fe05de57a9ad272853ea blob + 864aa88d373b8467e7e1ba060d36a6a86b99ba4f --- src/cmd/secstore/secstore.c +++ src/cmd/secstore/secstore.c @@ -16,6 +16,7 @@ typedef struct AuthConn{ int verbose; Nvrsafe nvr; +char *SECSTORE_DIR; void usage(void) @@ -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) @@ -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){ @@ -419,7 +420,9 @@ login(char *id, char *dest, int pass_stdin, int pass_n } 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"); @@ -444,7 +447,7 @@ login(char *id, char *dest, int pass_stdin, int pass_n 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); @@ -460,7 +463,9 @@ login(char *id, char *dest, int pass_stdin, int pass_n 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); blob - /dev/null blob + 4390129a1bd5beab28c664d673985630f9dcbf44 (mode 644) --- /dev/null +++ src/cmd/secstore/secacct.c @@ -0,0 +1,35 @@ +#include +#include +#include + +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); +} blob - 1d6d6d34b35e50c78134956bc55f59b671459cc8 blob + dbd2ec9cfeec4dd3aad3a57c5fd9a602ce10168c --- src/cmd/secstore/secstore.h +++ src/cmd/secstore/secstore.h @@ -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 @@ -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; + blob - /dev/null blob + 59e26d512ae6dfda4aec0a121c6ae5a7ad5ce2db (mode 644) --- /dev/null +++ src/cmd/secstore/secchk.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +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); +} blob - d791bc72325fa53d9ca8aff019d4f93771efc8a5 blob + ebbb12df8745f412311704ab7ad1fcecc0be741e --- src/cmd/secstore/util.c +++ src/cmd/secstore/util.c @@ -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; -} blob - /dev/null blob + 58f7459aec7b6f1783fda5734c1693cffecde193 (mode 644) --- /dev/null +++ src/cmd/secstore/secstored.c @@ -0,0 +1,420 @@ +#include +#include +#include +#include +#include +#include +#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; + } + } +} + blob - /dev/null blob + 95adb38541c9a7a656957ae28cabd7e2ecefb149 (mode 644) --- /dev/null +++ src/cmd/secstore/secureidcheck.c @@ -0,0 +1,446 @@ +/* RFC2138 */ +#include +#include +#include +#include +#include +#include +#include +#include +#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; ilen; 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; +} blob - /dev/null blob + 31ba184bb5d680aa3f18d666e44481814ce7cbd6 (mode 644) --- /dev/null +++ src/cmd/secstore/secuser.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#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] \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"); + } +} +