1 /* $Id: radius.c 7745 2008-04-06 10:18:54Z iulius $
3 ** Authenticate a user against a remote radius server.
8 #include "portable/time.h"
15 /* Needed on AIX 4.1 to get fd_set and friends. */
17 # include <sys/select.h>
20 #include "inn/innconf.h"
22 #include "inn/messages.h"
30 #define RADIUS_LOCAL_PORT NNTP_PORT
32 #define AUTH_VECTOR_LEN 16
34 typedef struct _auth_req {
37 unsigned short length;
38 unsigned char vector[AUTH_VECTOR_LEN];
39 unsigned char data[NNTP_STRLEN*2];
43 typedef struct _rad_config_t {
44 char *secret; /* pseudo encryption thingy secret that radius uses */
46 char *radhost; /* parameters for talking to the remote radius sever */
51 char *prefix, *suffix; /* futz with the username, if necessary */
54 struct _rad_config_t *next; /* point to any additional servers */
57 typedef struct _sending_t {
60 struct sockaddr_in sinr;
61 struct _sending_t *next;
76 static CONFTOKEN radtoks[] = {
79 { RADserver, "server" },
80 { RADhost, "radhost:" },
81 { RADsecret, "secret:" },
82 { RADport, "radport:" },
83 { RADlochost, "lochost:" },
84 { RADlocport, "locport:" },
85 { RADprefix, "prefix:" },
86 { RADsuffix, "suffix:" },
87 { RADsource, "ignore-source:" },
91 static rad_config_t *get_radconf(void)
95 new = xcalloc(1, sizeof(rad_config_t));
101 static int read_config(char *authfile, rad_config_t *radconf)
104 rad_config_t *radconfig=NULL;
111 if ((file = CONFfopen(authfile)) == NULL)
112 sysdie("cannot open config file %s", authfile);
115 while ((token = CONFgettoken(radtoks, file)) != NULL) {
117 if (token->type != RADserver)
118 die("expected server keyword on line %d", file->lineno);
119 if ((token = CONFgettoken(0, file)) == NULL)
120 die("expected server name on line %d", file->lineno);
121 server = xstrdup(token->name);
122 if ((token = CONFgettoken(radtoks, file)) == NULL
123 || token->type != RADlbrace)
124 die("expected { on line %d", file->lineno);
127 if (radconfig == NULL)
130 radconfig->next = get_radconf();
131 radconfig = radconfig->next;
136 if (type == RADrbrace)
139 if ((token = CONFgettoken(0, file)) == NULL)
140 die("keyword with no value on line %d", file->lineno);
143 /* what are we setting? */
146 if (radconfig->secret) continue;
147 radconfig->secret = xstrdup(iter);
150 if (radconfig->radhost) continue;
151 radconfig->radhost = xstrdup(iter);
154 if (radconfig->radport) continue;
155 radconfig->radport = atoi(iter);
158 if (radconfig->lochost) continue;
159 radconfig->lochost = xstrdup(iter);
162 if (radconfig->locport) continue;
163 radconfig->locport = atoi(iter);
166 if (radconfig->prefix) continue;
167 radconfig->prefix = xstrdup(iter);
170 if (radconfig->suffix) continue;
171 radconfig->suffix = xstrdup(iter);
174 if (!strcasecmp(iter, "true"))
175 radconfig->ignore_source = 1;
176 else if (!strcasecmp(iter, "false"))
177 radconfig->ignore_source = 0;
179 die("expected true or false after ignore-source on line %d",
183 die("unknown keyword on line %d", file->lineno);
191 if (!radconf->radhost)
192 die("no radius host specified");
193 else if (!radconf->secret)
194 die("no shared secret with radius host specified");
199 #define PW_AUTH_UDP_PORT 1645
201 #define PW_AUTHENTICATION_REQUEST 1
202 #define PW_AUTHENTICATION_ACK 2
203 #define PW_AUTHENTICATION_REJECT 3
205 #define PW_USER_NAME 1
206 #define PW_PASSWORD 2
208 #define PW_SERVICE_TYPE 6
209 #define PW_SERVICE_AUTH_ONLY 8
211 #define RAD_NAS_IP_ADDRESS 4 /* IP address */
212 #define RAD_NAS_PORT 5 /* Integer */
214 static void req_copyto (auth_req to, sending_t *from)
219 static void req_copyfrom (sending_t *to, auth_req from)
224 static int rad_auth(rad_config_t *radconfig, char *uname, char *pass)
227 int i, j, jlen, passstart;
228 unsigned char secbuf[128];
229 char hostname[SMBUF];
230 unsigned char digest[MD5_DIGESTSIZE];
232 struct sockaddr_in sinl;
234 struct hostent *hent;
237 struct timeval tmout;
242 int authtries= 3; /* number of times to try reaching the radius server */
243 rad_config_t *config;
244 sending_t *reqtop, *sreq, *new;
247 /* set up the linked list */
250 if (config == NULL) {
251 warn("no configuration file");
254 /* setting sreq to NULL guarantees reqtop will be properly set later */
259 while (config != NULL){
260 new = xmalloc(sizeof(sending_t));
270 req_copyto(req, sreq);
272 /* first, build the sockaddrs */
273 memset(&sinl, '\0', sizeof(sinl));
274 memset(&sreq->sinr, '\0', sizeof(sreq->sinr));
275 sinl.sin_family = AF_INET;
276 sreq->sinr.sin_family = AF_INET;
277 if (config->lochost == NULL) {
278 if (gethostname(hostname, sizeof(hostname)) != 0) {
279 syswarn("cannot get local hostname");
282 config->lochost = xstrdup(hostname);
284 if (config->lochost) {
285 if (inet_aton(config->lochost, &sinl.sin_addr) != 1) {
286 if ((hent = gethostbyname(config->lochost)) == NULL) {
287 warn("cannot gethostbyname lochost %s", config->lochost);
290 memcpy(&sinl.sin_addr.s_addr, hent->h_addr,
291 sizeof(struct in_addr));
294 if (inet_aton(config->radhost, &sreq->sinr.sin_addr) != 1) {
295 if ((hent = gethostbyname(config->radhost)) == NULL) {
296 warn("cannot gethostbyname radhost %s", config->radhost);
299 memcpy(&sreq->sinr.sin_addr.s_addr, hent->h_addr_list[0],
300 sizeof(struct in_addr));
304 sreq->sinr.sin_port = htons(config->radport);
306 sreq->sinr.sin_port = htons(PW_AUTH_UDP_PORT);
308 /* seed the random number generator for the auth vector */
309 gettimeofday(&seed, 0);
310 srandom((unsigned) seed.tv_sec+seed.tv_usec);
311 /* build the visible part of the auth vector randomly */
312 for (i = 0; i < AUTH_VECTOR_LEN; i++)
313 req.vector[i] = random() % 256;
314 strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
315 memcpy(secbuf+strlen(config->secret), req.vector, AUTH_VECTOR_LEN);
316 md5_hash(secbuf, strlen(config->secret)+AUTH_VECTOR_LEN, digest);
317 /* fill in the auth_req data */
318 req.code = PW_AUTHENTICATION_REQUEST;
321 /* bracket the username in the configured prefix/suffix */
322 req.data[0] = PW_USER_NAME;
325 if (config->prefix) {
326 req.data[1] += strlen(config->prefix);
327 strlcat((char *) &req.data[2], config->prefix, sizeof(req.data) - 2);
329 req.data[1] += strlen(uname);
330 strlcat((char *)&req.data[2], uname, sizeof(req.data) - 2);
331 if (!strchr(uname, '@') && config->suffix) {
332 req.data[1] += strlen(config->suffix);
333 strlcat((char *)&req.data[2], config->suffix, sizeof(req.data) - 2);
335 req.datalen = req.data[1];
337 /* set the password */
338 passstart = req.datalen;
339 req.data[req.datalen] = PW_PASSWORD;
340 /* Null pad the password */
341 passlen = (strlen(pass) + 15) / 16;
343 req.data[req.datalen+1] = passlen+2;
344 strlcpy((char *)&req.data[req.datalen+2], pass,
345 sizeof(req.data) - req.datalen - 2);
346 passlen -= strlen(pass);
348 req.data[req.datalen+passlen+2+strlen(pass)] = '\0';
349 req.datalen += req.data[req.datalen+1];
351 /* Add NAS_PORT and NAS_IP_ADDRESS into request */
352 if ((nvalue = config->locport) == 0)
353 nvalue = RADIUS_LOCAL_PORT;
354 req.data[req.datalen++] = RAD_NAS_PORT;
355 req.data[req.datalen++] = sizeof(nvalue) + 2;
356 nvalue = htonl(nvalue);
357 memcpy(req.data + req.datalen, &nvalue, sizeof(nvalue));
358 req.datalen += sizeof(nvalue);
359 req.data[req.datalen++] = RAD_NAS_IP_ADDRESS;
360 req.data[req.datalen++] = sizeof(struct in_addr) + 2;
361 memcpy(req.data + req.datalen, &sinl.sin_addr.s_addr,
362 sizeof(struct in_addr));
363 req.datalen += sizeof(struct in_addr);
365 /* we're only doing authentication */
366 req.data[req.datalen] = PW_SERVICE_TYPE;
367 req.data[req.datalen+1] = 6;
368 req.data[req.datalen+2] = (PW_SERVICE_AUTH_ONLY >> 24) & 0x000000ff;
369 req.data[req.datalen+3] = (PW_SERVICE_AUTH_ONLY >> 16) & 0x000000ff;
370 req.data[req.datalen+4] = (PW_SERVICE_AUTH_ONLY >> 8) & 0x000000ff;
371 req.data[req.datalen+5] = PW_SERVICE_AUTH_ONLY & 0x000000ff;
372 req.datalen += req.data[req.datalen+1];
374 /* filled in the data, now we know what the actual length is. */
375 req.length = 4+AUTH_VECTOR_LEN+req.datalen;
377 /* "encrypt" the password */
378 for (i = 0; i < req.data[passstart+1]-2; i += sizeof(HASH)) {
380 if (req.data[passstart+1]-(unsigned)i-2 < sizeof(HASH))
381 jlen = req.data[passstart+1]-i-2;
382 for (j = 0; j < jlen; j++)
383 req.data[passstart+2+i+j] ^= digest[j];
384 if (jlen == sizeof(HASH)) {
385 /* Recalculate the digest from the HASHed previous */
386 strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
387 memcpy(secbuf+strlen(config->secret), &req.data[passstart+2+i],
389 md5_hash(secbuf, strlen(config->secret)+sizeof(HASH), digest);
392 sreq->reqlen = req.length;
393 req.length = htons(req.length);
395 req_copyfrom(sreq, req);
397 /* Go to the next record in the list */
398 config = config->next;
401 /* YAYY! The auth_req is ready to go! Build the reply socket and send out
404 /* now, build the sockets */
405 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
406 syswarn("cannot build reply socket");
409 if (bind(sock, (struct sockaddr*) &sinl, sizeof(sinl)) < 0) {
410 syswarn("cannot bind reply socket");
415 for(done = 0; authtries > 0 && !done; authtries--) {
416 for (config = radconfig, sreq = reqtop; sreq != NULL && !done;
417 config = config->next, sreq = sreq->next){
418 req_copyto(req, sreq);
420 /* send out the packet and wait for reply. */
421 if (sendto(sock, (char *)&req, sreq->reqlen, 0,
422 (struct sockaddr*) &sreq->sinr,
423 sizeof (struct sockaddr_in)) < 0) {
424 syswarn("cannot send auth_reg");
429 /* wait 5 seconds maximum for a radius reply. */
435 /* store the old vector to verify next checksum */
436 memcpy(secbuf+sizeof(req.vector), req.vector, sizeof(req.vector));
437 FD_SET(sock, &rdfds);
438 got = select(sock+1, &rdfds, 0, 0, &tmout);
440 syswarn("cannot not select");
442 } else if (got == 0) {
445 tmout.tv_sec = end - now + 1;
450 if ((jlen = recvfrom(sock, (char *)&req, sizeof(req)-sizeof(int), 0,
451 (struct sockaddr*) &sinl, &slen)) < 0) {
452 syswarn("cannot recvfrom");
455 if (!config->ignore_source) {
456 if (sinl.sin_addr.s_addr != sreq->sinr.sin_addr.s_addr ||
457 (sinl.sin_port != sreq->sinr.sin_port)) {
458 warn("received unexpected UDP packet from %s:%d",
459 inet_ntoa(sinl.sin_addr), ntohs(sinl.sin_port));
463 sreq->reqlen = ntohs(req.length);
464 if (jlen < 4+AUTH_VECTOR_LEN || jlen != sreq->reqlen) {
465 warn("received badly-sized packet");
468 /* verify the checksum */
469 memcpy(((char*)&req)+sreq->reqlen, config->secret, strlen(config->secret));
470 memcpy(secbuf, req.vector, sizeof(req.vector));
471 memcpy(req.vector, secbuf+sizeof(req.vector), sizeof(req.vector));
472 md5_hash((unsigned char *)&req, strlen(config->secret)+sreq->reqlen,
474 if (memcmp(digest, secbuf, sizeof(HASH)) != 0) {
475 warn("checksum didn't match");
478 /* FINALLY! Got back a known-good packet. See if we're in. */
480 return (req.code == PW_AUTHENTICATION_ACK) ? 0 : -1;
482 req_copyfrom(sreq, req);
487 warn("cannot talk to remote radius server %s:%d",
488 inet_ntoa(sreq->sinr.sin_addr), ntohs(sreq->sinr.sin_port));
492 #define RAD_HAVE_HOST 1
493 #define RAD_HAVE_PORT 2
494 #define RAD_HAVE_PREFIX 4
495 #define RAD_HAVE_SUFFIX 8
496 #define RAD_HAVE_LOCHOST 16
497 #define RAD_HAVE_LOCPORT 32
499 int main(int argc, char *argv[])
502 int havefile, haveother;
503 struct auth_info *authinfo;
504 rad_config_t radconfig;
508 message_program_name = "radius";
510 if (!innconf_read(NULL))
513 memset(&radconfig, '\0', sizeof(rad_config_t));
514 haveother = havefile = 0;
516 while ((opt = getopt(argc, argv, "f:h")) != -1) {
520 die("-f flag after another flag");
522 /* override the standard config completely if the user
523 * specifies an alternate config file */
524 memset(&radconfig, '\0', sizeof(rad_config_t));
527 read_config(optarg, &radconfig);
530 printf("Usage: radius [-f config]\n");
537 radius_config = concatpath(innconf->pathetc, _PATH_RADIUS_CONFIG);
538 read_config(radius_config, &radconfig);
543 authinfo = get_auth_info(stdin);
544 if (authinfo == NULL)
545 die("failed getting auth info");
546 if (authinfo->username[0] == '\0')
547 die("empty username");
549 /* got username and password, check that they're valid */
551 retval = rad_auth(&radconfig, authinfo->username, authinfo->password);
553 die("user %s password doesn't match", authinfo->username);
554 else if (retval == -2)
555 /* couldn't talk to the radius server.. output logged above. */
557 else if (retval != 0)
558 die("unexpected return code from authentication function: %d",
561 /* radius password matches! */
562 printf("User:%s\n", authinfo->username);