447 lines
8.8 KiB
C
447 lines
8.8 KiB
C
|
|
/* 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;
|
||
|
|
}
|