/* $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 #include #include #include #include /* Needed on AIX 4.1 to get fd_set and friends. */ #if HAVE_SYS_SELECT_H # include #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); }