aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-02-11 19:39:51 +0000
committerrsc <devnull@localhost>2005-02-11 19:39:51 +0000
commit096ff3e14a188992d2dfe59c7fd3f5d6da791331 (patch)
tree7d570f58a3e398c7f04c04a34bd330aaf077ee45
parentd93fca6a7ab52f518d3e8aca1fc94139313b97ad (diff)
downloadplan9port-096ff3e14a188992d2dfe59c7fd3f5d6da791331.tar.gz
plan9port-096ff3e14a188992d2dfe59c7fd3f5d6da791331.tar.bz2
plan9port-096ff3e14a188992d2dfe59c7fd3f5d6da791331.zip
add secstored; use readcons
-rw-r--r--src/cmd/secstore/aescbc.c2
-rw-r--r--src/cmd/secstore/dirls.c2
-rw-r--r--src/cmd/secstore/mkfile9
-rw-r--r--src/cmd/secstore/secacct.c35
-rw-r--r--src/cmd/secstore/secchk.c28
-rw-r--r--src/cmd/secstore/secstore.c17
-rw-r--r--src/cmd/secstore/secstore.h5
-rw-r--r--src/cmd/secstore/secstored.c420
-rw-r--r--src/cmd/secstore/secureidcheck.c446
-rw-r--r--src/cmd/secstore/secuser.c244
-rw-r--r--src/cmd/secstore/util.c10
11 files changed, 1196 insertions, 22 deletions
diff --git a/src/cmd/secstore/aescbc.c b/src/cmd/secstore/aescbc.c
index 09e7955d..56aeb00b 100644
--- a/src/cmd/secstore/aescbc.c
+++ b/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");
diff --git a/src/cmd/secstore/dirls.c b/src/cmd/secstore/dirls.c
index 0af9c410..b4479413 100644
--- a/src/cmd/secstore/dirls.c
+++ b/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<ndir; i++){
if((m = strlen(dirbuf[i].name)) > nmwid)
nmwid = m;
diff --git a/src/cmd/secstore/mkfile b/src/cmd/secstore/mkfile
index 8d9e0f8e..72986edd 100644
--- a/src/cmd/secstore/mkfile
+++ b/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
diff --git a/src/cmd/secstore/secacct.c b/src/cmd/secstore/secacct.c
new file mode 100644
index 00000000..4390129a
--- /dev/null
+++ b/src/cmd/secstore/secacct.c
@@ -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);
+}
diff --git a/src/cmd/secstore/secchk.c b/src/cmd/secstore/secchk.c
new file mode 100644
index 00000000..59e26d51
--- /dev/null
+++ b/src/cmd/secstore/secchk.c
@@ -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);
+}
diff --git a/src/cmd/secstore/secstore.c b/src/cmd/secstore/secstore.c
index 94e9ff7e..864aa88d 100644
--- a/src/cmd/secstore/secstore.c
+++ b/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_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");
@@ -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);
@@ -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);
diff --git a/src/cmd/secstore/secstore.h b/src/cmd/secstore/secstore.h
index 1d6d6d34..dbd2ec9c 100644
--- a/src/cmd/secstore/secstore.h
+++ b/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;
+
diff --git a/src/cmd/secstore/secstored.c b/src/cmd/secstore/secstored.c
new file mode 100644
index 00000000..58f7459a
--- /dev/null
+++ b/src/cmd/secstore/secstored.c
@@ -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;
+ }
+ }
+}
+
diff --git a/src/cmd/secstore/secureidcheck.c b/src/cmd/secstore/secureidcheck.c
new file mode 100644
index 00000000..95adb385
--- /dev/null
+++ b/src/cmd/secstore/secureidcheck.c
@@ -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;
+}
diff --git a/src/cmd/secstore/secuser.c b/src/cmd/secstore/secuser.c
new file mode 100644
index 00000000..31ba184b
--- /dev/null
+++ b/src/cmd/secstore/secuser.c
@@ -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");
+ }
+}
+
diff --git a/src/cmd/secstore/util.c b/src/cmd/secstore/util.c
index d791bc72..ebbb12df 100644
--- a/src/cmd/secstore/util.c
+++ b/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;
-}