summaryrefslogtreecommitdiff
path: root/sys/src/cmd/auth/secureidcheck.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/auth/secureidcheck.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/auth/secureidcheck.c')
-rwxr-xr-xsys/src/cmd/auth/secureidcheck.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/sys/src/cmd/auth/secureidcheck.c b/sys/src/cmd/auth/secureidcheck.c
new file mode 100755
index 000000000..d52689145
--- /dev/null
+++ b/sys/src/cmd/auth/secureidcheck.c
@@ -0,0 +1,466 @@
+/*
+ * This code uses RADIUS as a portable way to validate tokens such as SecurID.
+ * It is relatively simple to send a UDP packet and get a response, but various
+ * things can go wrong. Speaking the proprietary ACE protocol would allow
+ * handling "next token code" and other error messages. More importantly, the
+ * timeout threshold is inherently hard to pick. We observe responses taking
+ * longer than 10 seconds in normal times. That is a long time to wait before
+ * retrying on a second server. Moreover, if the UDP response is lost, retrying
+ * on a second server will also fail because the valid token code may be
+ * presented only once. This whole approach is flawed, but best we can do.
+ */
+/* 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*, char *msg)
+{
+ 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++){
+ /*
+ * increased timeout from 4sec to 15sec because
+ * corporate server really takes that long.
+ */
+ alarm(15000);
+ 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;
+ for(;;){
+ 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)
+{
+ int i;
+ char *np, *e;
+ char buf[255], pbuf[4*1024];
+ uchar *au = p->authenticator;
+ Attribute *a;
+
+ 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)
+{
+ char *radiussecret = nil;
+ char *rv = "authentication failed";
+ char dest[3*IPaddrlen+20], ruser[64];
+ uchar *ip;
+ uchar x[16];
+ ulong u[4];
+ Ndbs s;
+ Ndbtuple *t = nil, *nt, *tt;
+ Packet *req = nil, *resp = nil;
+ Secret shared, pass;
+ 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);
+ t = nil;
+
+ 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!radius", 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 */
+ }
+out:
+ if (t)
+ ndbfree(t);
+ free(radiussecret);
+ freePacket(req);
+ freePacket(resp);
+ return rv;
+}