/* $Id: radius.c 6155 2003-01-19 19:58:25Z rra $
**
** Authenticate a user against a remote radius server.
*/
#include "config.h"
#include "clibrary.h"
#include "portable/time.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
/* Needed on AIX 4.1 to get fd_set and friends. */
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include "inn/innconf.h"
#include "inn/md5.h"
#include "inn/messages.h"
#include "libinn.h"
#include "nntp.h"
#include "paths.h"
#include "conffile.h"
#include "libauth.h"
#define RADIUS_LOCAL_PORT NNTP_PORT
#define AUTH_VECTOR_LEN 16
typedef struct _auth_req {
unsigned char code;
unsigned char id;
unsigned short length;
unsigned char vector[AUTH_VECTOR_LEN];
unsigned char data[NNTP_STRLEN*2];
int datalen;
} auth_req;
typedef struct _rad_config_t {
char *secret; /* pseudo encryption thingy secret that radius uses */
char *radhost; /* parameters for talking to the remote radius sever */
int radport;
char *lochost;
int locport;
char *prefix, *suffix; /* futz with the username, if necessary */
int ignore_source;
struct _rad_config_t *next; /* point to any additional servers */
} rad_config_t;
typedef struct _sending_t {
auth_req req;
int reqlen;
struct sockaddr_in sinr;
struct _sending_t *next;
} sending_t;
#define RADlbrace 1
#define RADrbrace 2
#define RADserver 10
#define RADhost 11
#define RADsecret 12
#define RADport 13
#define RADlochost 14
#define RADlocport 15
#define RADprefix 16
#define RADsuffix 17
#define RADsource 18
static CONFTOKEN radtoks[] = {
{ RADlbrace, "{" },
{ RADrbrace, "}" },
{ RADserver, "server" },
{ RADhost, "radhost:" },
{ RADsecret, "secret:" },
{ RADport, "radport:" },
{ RADlochost, "lochost:" },
{ RADlocport, "locport:" },
{ RADprefix, "prefix:" },
{ RADsuffix, "suffix:" },
{ RADsource, "ignore-source:" },
{ 0, 0 }
};
static rad_config_t *get_radconf(void)
{
rad_config_t *new;
new = xmalloc(sizeof(rad_config_t));
new->next = NULL;
return new;
}
static int read_config(char *authfile, rad_config_t *radconf)
{
int inbrace;
rad_config_t *radconfig=NULL;
CONFFILE *file;
CONFTOKEN *token;
char *server;
int type;
char *iter;
if ((file = CONFfopen(authfile)) == NULL)
sysdie("cannot open config file %s", authfile);
inbrace = 0;
while ((token = CONFgettoken(radtoks, file)) != NULL) {
if (!inbrace) {
if (token->type != RADserver)
die("expected server keyword on line %d", file->lineno);
if ((token = CONFgettoken(0, file)) == NULL)
die("expected server name on line %d", file->lineno);
server = xstrdup(token->name);
if ((token = CONFgettoken(radtoks, file)) == NULL
|| token->type != RADlbrace)
die("expected { on line %d", file->lineno);
inbrace = 1;
if (radconfig == NULL)
radconfig = radconf;
else {
radconfig->next = get_radconf();
radconfig = radconfig->next;
}
}
else {
type = token->type;
if (type == RADrbrace)
inbrace = 0;
else {
if ((token = CONFgettoken(0, file)) == NULL)
die("keyword with no value on line %d", file->lineno);
iter = token->name;
/* what are we setting? */
switch(type) {
case RADsecret:
if (radconfig->secret) continue;
radconfig->secret = xstrdup(iter);
break;
case RADhost:
if (radconfig->radhost) continue;
radconfig->radhost = xstrdup(iter);
break;
case RADport:
if (radconfig->radport) continue;
radconfig->radport = atoi(iter);
break;
case RADlochost:
if (radconfig->lochost) continue;
radconfig->lochost = xstrdup(iter);
break;
case RADlocport:
if (radconfig->locport) continue;
radconfig->locport = atoi(iter);
break;
case RADprefix:
if (radconfig->prefix) continue;
radconfig->prefix = xstrdup(iter);
break;
case RADsuffix:
if (radconfig->suffix) continue;
radconfig->suffix = xstrdup(iter);
break;
case RADsource:
if (!strcasecmp(iter, "true"))
radconfig->ignore_source = 1;
else if (!strcasecmp(iter, "false"))
radconfig->ignore_source = 0;
else
die("expected true or false after ignore-source on line %d",
file->lineno);
break;
default:
die("unknown keyword on line %d", file->lineno);
}
}
}
}
CONFfclose(file);
if (!radconf->radhost)
die("no radius host specified");
else if (!radconf->secret)
die("no shared secret with radius host specified");
return(0);
}
#define PW_AUTH_UDP_PORT 1645
#define PW_AUTHENTICATION_REQUEST 1
#define PW_AUTHENTICATION_ACK 2
#define PW_AUTHENTICATION_REJECT 3
#define PW_USER_NAME 1
#define PW_PASSWORD 2
#define PW_SERVICE_TYPE 6
#define PW_SERVICE_AUTH_ONLY 8
#define RAD_NAS_IP_ADDRESS 4 /* IP address */
#define RAD_NAS_PORT 5 /* Integer */
static void req_copyto (auth_req to, sending_t *from)
{
to = from->req;
}
static void req_copyfrom (sending_t *to, auth_req from)
{
to->req = from;
}
static int rad_auth(rad_config_t *radconfig, char *uname, char *pass)
{
auth_req req;
int i, j, jlen, passstart;
unsigned char secbuf[128];
char hostname[SMBUF];
unsigned char digest[MD5_DIGESTSIZE];
struct timeval seed;
struct sockaddr_in sinl;
int sock;
struct hostent *hent;
int passlen;
time_t now, end;
struct timeval tmout;
int got;
fd_set rdfds;
uint32_t nvalue;
socklen_t slen;
int authtries= 3; /* number of times to try reaching the radius server */
rad_config_t *config;
sending_t *reqtop, *sreq, *new;
int done;
/* set up the linked list */
config = radconfig;
if (config == NULL) {
warn("no configuration file");
return(-2);
} else {
/* setting sreq to NULL guarantees reqtop will be properly set later */
sreq = NULL;
reqtop = NULL;
}
while (config != NULL){
new = xmalloc(sizeof(sending_t));
new->next = NULL;
if (sreq == NULL){
reqtop = new;
sreq = new;
} else {
sreq->next = new;
sreq = sreq->next;
}
req_copyto(req, sreq);
/* first, build the sockaddrs */
memset(&sinl, '\0', sizeof(sinl));
memset(&sreq->sinr, '\0', sizeof(sreq->sinr));
sinl.sin_family = AF_INET;
sreq->sinr.sin_family = AF_INET;
if (config->lochost == NULL) {
if (gethostname(hostname, sizeof(hostname)) != 0) {
syswarn("cannot get local hostname");
return(-2);
}
config->lochost = xstrdup(hostname);
}
if (config->lochost) {
if (inet_aton(config->lochost, &sinl.sin_addr) != 1) {
if ((hent = gethostbyname(config->lochost)) == NULL) {
warn("cannot gethostbyname lochost %s", config->lochost);
return(-2);
}
memcpy(&sinl.sin_addr.s_addr, hent->h_addr,
sizeof(struct in_addr));
}
}
if (inet_aton(config->radhost, &sreq->sinr.sin_addr) != 1) {
if ((hent = gethostbyname(config->radhost)) == NULL) {
warn("cannot gethostbyname radhost %s", config->radhost);
return(-2);
}
memcpy(&sreq->sinr.sin_addr.s_addr, hent->h_addr_list[0],
sizeof(struct in_addr));
}
if (config->radport)
sreq->sinr.sin_port = htons(config->radport);
else
sreq->sinr.sin_port = htons(PW_AUTH_UDP_PORT);
/* seed the random number generator for the auth vector */
gettimeofday(&seed, 0);
srandom((unsigned) seed.tv_sec+seed.tv_usec);
/* build the visible part of the auth vector randomly */
for (i = 0; i < AUTH_VECTOR_LEN; i++)
req.vector[i] = random() % 256;
strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
memcpy(secbuf+strlen(config->secret), req.vector, AUTH_VECTOR_LEN);
md5_hash(secbuf, strlen(config->secret)+AUTH_VECTOR_LEN, digest);
/* fill in the auth_req data */
req.code = PW_AUTHENTICATION_REQUEST;
req.id = 0;
/* bracket the username in the configured prefix/suffix */
req.data[0] = PW_USER_NAME;
req.data[1] = 2;
req.data[2] = '\0';
if (config->prefix) {
req.data[1] += strlen(config->prefix);
strlcat((char *) &req.data[2], config->prefix, sizeof(req.data) - 2);
}
req.data[1] += strlen(uname);
strlcat((char *)&req.data[2], uname, sizeof(req.data) - 2);
if (!strchr(uname, '@') && config->suffix) {
req.data[1] += strlen(config->suffix);
strlcat((char *)&req.data[2], config->suffix, sizeof(req.data) - 2);
}
req.datalen = req.data[1];
/* set the password */
passstart = req.datalen;
req.data[req.datalen] = PW_PASSWORD;
/* Null pad the password */
passlen = (strlen(pass) + 15) / 16;
passlen *= 16;
req.data[req.datalen+1] = passlen+2;
strlcpy((char *)&req.data[req.datalen+2], pass,
sizeof(req.data) - req.datalen - 2);
passlen -= strlen(pass);
while (passlen--)
req.data[req.datalen+passlen+2+strlen(pass)] = '\0';
req.datalen += req.data[req.datalen+1];
/* Add NAS_PORT and NAS_IP_ADDRESS into request */
if ((nvalue = config->locport) == 0)
nvalue = RADIUS_LOCAL_PORT;
req.data[req.datalen++] = RAD_NAS_PORT;
req.data[req.datalen++] = sizeof(nvalue) + 2;
nvalue = htonl(nvalue);
memcpy(req.data + req.datalen, &nvalue, sizeof(nvalue));
req.datalen += sizeof(nvalue);
req.data[req.datalen++] = RAD_NAS_IP_ADDRESS;
req.data[req.datalen++] = sizeof(struct in_addr) + 2;
memcpy(req.data + req.datalen, &sinl.sin_addr.s_addr,
sizeof(struct in_addr));
req.datalen += sizeof(struct in_addr);
/* we're only doing authentication */
req.data[req.datalen] = PW_SERVICE_TYPE;
req.data[req.datalen+1] = 6;
req.data[req.datalen+2] = (PW_SERVICE_AUTH_ONLY >> 24) & 0x000000ff;
req.data[req.datalen+3] = (PW_SERVICE_AUTH_ONLY >> 16) & 0x000000ff;
req.data[req.datalen+4] = (PW_SERVICE_AUTH_ONLY >> 8) & 0x000000ff;
req.data[req.datalen+5] = PW_SERVICE_AUTH_ONLY & 0x000000ff;
req.datalen += req.data[req.datalen+1];
/* filled in the data, now we know what the actual length is. */
req.length = 4+AUTH_VECTOR_LEN+req.datalen;
/* "encrypt" the password */
for (i = 0; i < req.data[passstart+1]-2; i += sizeof(HASH)) {
jlen = sizeof(HASH);
if (req.data[passstart+1]-(unsigned)i-2 < sizeof(HASH))
jlen = req.data[passstart+1]-i-2;
for (j = 0; j < jlen; j++)
req.data[passstart+2+i+j] ^= digest[j];
if (jlen == sizeof(HASH)) {
/* Recalculate the digest from the HASHed previous */
strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
memcpy(secbuf+strlen(config->secret), &req.data[passstart+2+i],
sizeof(HASH));
md5_hash(secbuf, strlen(config->secret)+sizeof(HASH), digest);
}
}
sreq->reqlen = req.length;
req.length = htons(req.length);
req_copyfrom(sreq, req);
/* Go to the next record in the list */
config = config->next;
}
/* YAYY! The auth_req is ready to go! Build the reply socket and send out
* the message. */
/* now, build the sockets */
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
syswarn("cannot build reply socket");
return(-1);
}
if (bind(sock, (struct sockaddr*) &sinl, sizeof(sinl)) < 0) {
syswarn("cannot bind reply socket");
close(sock);
return(-1);
}
for(done = 0; authtries > 0 && !done; authtries--) {
for (config = radconfig, sreq = reqtop; sreq != NULL && !done;
config = config->next, sreq = sreq->next){
req_copyto(req, sreq);
/* send out the packet and wait for reply. */
if (sendto(sock, (char *)&req, sreq->reqlen, 0,
(struct sockaddr*) &sreq->sinr,
sizeof (struct sockaddr_in)) < 0) {
syswarn("cannot send auth_reg");
close(sock);
return(-1);
}
/* wait 5 seconds maximum for a radius reply. */
now = time(0);
end = now+5;
tmout.tv_sec = 6;
tmout.tv_usec = 0;
FD_ZERO(&rdfds);
/* store the old vector to verify next checksum */
memcpy(secbuf+sizeof(req.vector), req.vector, sizeof(req.vector));
FD_SET(sock, &rdfds);
got = select(sock+1, &rdfds, 0, 0, &tmout);
if (got < 0) {
syswarn("cannot not select");
break;
} else if (got == 0) {
/* timer ran out */
now = time(0);
tmout.tv_sec = end - now + 1;
tmout.tv_usec = 0;
continue;
}
slen = sizeof(sinl);
if ((jlen = recvfrom(sock, (char *)&req, sizeof(req)-sizeof(int), 0,
(struct sockaddr*) &sinl, &slen)) < 0) {
syswarn("cannot recvfrom");
break;
}
if (!config->ignore_source) {
if (sinl.sin_addr.s_addr != sreq->sinr.sin_addr.s_addr ||
(sinl.sin_port != sreq->sinr.sin_port)) {
warn("received unexpected UDP packet from %s:%d",
inet_ntoa(sinl.sin_addr), ntohs(sinl.sin_port));
continue;
}
}
sreq->reqlen = ntohs(req.length);
if (jlen < 4+AUTH_VECTOR_LEN || jlen != sreq->reqlen) {
warn("received badly-sized packet");
continue;
}
/* verify the checksum */
memcpy(((char*)&req)+sreq->reqlen, config->secret, strlen(config->secret));
memcpy(secbuf, req.vector, sizeof(req.vector));
memcpy(req.vector, secbuf+sizeof(req.vector), sizeof(req.vector));
md5_hash((unsigned char *)&req, strlen(config->secret)+sreq->reqlen,
digest);
if (memcmp(digest, secbuf, sizeof(HASH)) != 0) {
warn("checksum didn't match");
continue;
}
/* FINALLY! Got back a known-good packet. See if we're in. */
close(sock);
return (req.code == PW_AUTHENTICATION_ACK) ? 0 : -1;
done = 1;
req_copyfrom(sreq, req);
break;
}
}
if (authtries == 0)
warn("cannot talk to remote radius server %s:%d",
inet_ntoa(sreq->sinr.sin_addr), ntohs(sreq->sinr.sin_port));
return(-2);
}
#define RAD_HAVE_HOST 1
#define RAD_HAVE_PORT 2
#define RAD_HAVE_PREFIX 4
#define RAD_HAVE_SUFFIX 8
#define RAD_HAVE_LOCHOST 16
#define RAD_HAVE_LOCPORT 32
int main(int argc, char *argv[])
{
int opt;
int havefile, haveother;
struct auth_info *authinfo;
rad_config_t radconfig;
int retval;
char *radius_config;
message_program_name = "radius";
if (!innconf_read(NULL))
exit(1);
memset(&radconfig, '\0', sizeof(rad_config_t));
haveother = havefile = 0;
while ((opt = getopt(argc, argv, "f:h")) != -1) {
switch (opt) {
case 'f':
if (haveother)
die("-f flag after another flag");
if (!havefile) {
/* override the standard config completely if the user
* specifies an alternate config file */
memset(&radconfig, '\0', sizeof(rad_config_t));
havefile = 1;
}
read_config(optarg, &radconfig);
break;
case 'h':
printf("Usage: radius [-f config]\n");
exit(0);
}
}
if (argc != optind)
exit(2);
if (!havefile) {
radius_config = concatpath(innconf->pathetc, _PATH_RADIUS_CONFIG);
read_config(radius_config, &radconfig);
free(radius_config);
}
authinfo = get_auth_info(stdin);
if (authinfo == NULL)
die("failed getting auth info");
if (authinfo->username[0] == '\0')
die("empty username");
/* got username and password, check that they're valid */
retval = rad_auth(&radconfig, authinfo->username, authinfo->password);
if (retval == -1)
die("user %s password doesn't match", authinfo->username);
else if (retval == -2)
/* couldn't talk to the radius server.. output logged above. */
exit(1);
else if (retval != 0)
die("unexpected return code from authentication function: %d",
retval);
/* radius password matches! */
printf("User:%s\n", authinfo->username);
exit(0);
}
syntax highlighted by Code2HTML, v. 0.9.1