/* $OpenBSD: raddauth.c,v 1.33 2024/07/18 02:45:31 yasuoka Exp $ */ /*- * Copyright (c) 1996, 1997 Berkeley Software Design, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Berkeley Software Design, * Inc. * 4. The name of Berkeley Software Design, Inc. may not be used to endorse * or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * BSDI $From: raddauth.c,v 1.6 1998/04/14 00:39:04 prb Exp $ */ /* * Copyright(c) 1996 by tfm associates. * All rights reserved. * * tfm associates * P.O. Box 2086 * Eugene OR 97402-0031 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of tfm associates may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY TFM ASSOC``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL TFM ASSOCIATES BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "login_radius.h" #define MAXPWNETNAM 64 /* longest username */ #define MAXSECRETLEN 128 /* maximum length of secret */ #define AUTH_VECTOR_LEN 16 #define AUTH_HDR_LEN 20 #define AUTH_PASS_LEN (256 - 16) #define AUTH_MSGAUTH_LEN 16 #define PW_AUTHENTICATION_REQUEST 1 #define PW_AUTHENTICATION_ACK 2 #define PW_AUTHENTICATION_REJECT 3 #define PW_ACCESS_CHALLENGE 11 #define PW_USER_NAME 1 #define PW_PASSWORD 2 #define PW_CLIENT_ID 4 #define PW_CLIENT_PORT_ID 5 #define PW_PORT_MESSAGE 18 #define PW_STATE 24 #define PW_MSG_AUTH 80 #ifndef RADIUS_DIR #define RADIUS_DIR "/etc/raddb" #endif #define RADIUS_SERVERS "servers" char *radius_dir = RADIUS_DIR; char auth_secret[MAXSECRETLEN+1]; volatile sig_atomic_t timedout; int alt_retries; int retries; int sockfd; int timeout; in_addr_t alt_server; in_addr_t auth_server; in_port_t radius_port; typedef struct { u_char code; u_char id; u_short length; u_char vector[AUTH_VECTOR_LEN]; u_char data[4096 - AUTH_HDR_LEN]; } auth_hdr_t; void servtimeout(int); in_addr_t get_ipaddr(char *); in_addr_t gethost(void); int rad_recv(char *, char *, u_char *); void parse_challenge(auth_hdr_t *, char *, char *); void rad_request(u_char, char *, char *, int, char *, char *); void getsecret(void); /* * challenge -- NULL for interactive service * password -- NULL for interactive service and when requesting a challenge */ int raddauth(char *username, char *class, char *style, char *challenge, char *password, char **emsg) { static char _pwstate[1024]; u_char req_id; char *userstyle, *passwd, *pwstate, *rad_service; char pbuf[AUTH_PASS_LEN+1]; int auth_port; char vector[AUTH_VECTOR_LEN+1], *p, *v; int i; login_cap_t *lc; u_int32_t r; struct servent *svp; struct sockaddr_in sin; struct sigaction sa; const char *errstr; memset(_pwstate, 0, sizeof(_pwstate)); pwstate = password ? challenge : _pwstate; if ((lc = login_getclass(class)) == NULL) { snprintf(_pwstate, sizeof(_pwstate), "%s: no such class", class); *emsg = _pwstate; return (1); } rad_service = login_getcapstr(lc, "radius-port", "radius", "radius"); timeout = login_getcapnum(lc, "radius-timeout", 2, 2); retries = login_getcapnum(lc, "radius-retries", 6, 6); if (timeout < 1) timeout = 1; if (retries < 2) retries = 2; if (challenge == NULL) { passwd = NULL; v = login_getcapstr(lc, "radius-challenge-styles", NULL, NULL); i = strlen(style); while (v && (p = strstr(v, style)) != NULL) { if ((p == v || p[-1] == ',') && (p[i] == ',' || p[i] == '\0')) { passwd = ""; break; } v = p+1; } if (passwd == NULL) passwd = readpassphrase("Password:", pbuf, sizeof(pbuf), RPP_ECHO_OFF); } else passwd = password; if (passwd == NULL) passwd = ""; if ((v = login_getcapstr(lc, "radius-server", NULL, NULL)) == NULL){ *emsg = "radius-server not configured"; return (1); } auth_server = get_ipaddr(v); if ((v = login_getcapstr(lc, "radius-server-alt", NULL, NULL)) == NULL) alt_server = 0; else { alt_server = get_ipaddr(v); alt_retries = retries/2; retries >>= 1; } /* get port number */ radius_port = strtonum(rad_service, 1, UINT16_MAX, &errstr); if (errstr) { svp = getservbyname(rad_service, "udp"); if (svp == NULL) { snprintf(_pwstate, sizeof(_pwstate), "No such service: %s/udp", rad_service); *emsg = _pwstate; return (1); } radius_port = svp->s_port; } else radius_port = htons(radius_port); /* get the secret from the servers file */ getsecret(); /* set up socket */ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { snprintf(_pwstate, sizeof(_pwstate), "%s", strerror(errno)); *emsg = _pwstate; return (1); } /* set up client structure */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = radius_port; req_id = (u_char) arc4random(); auth_port = ttyslot(); if (auth_port == 0) auth_port = (int)getppid(); if (strcmp(style, "radius") != 0) { if (asprintf(&userstyle, "%s:%s", username, style) == -1) err(1, NULL); } else userstyle = username; /* generate random vector */ for (i = 0; i < AUTH_VECTOR_LEN;) { r = arc4random(); memcpy(&vector[i], &r, sizeof(r)); i += sizeof(r); } vector[AUTH_VECTOR_LEN] = '\0'; sigemptyset(&sa.sa_mask); sa.sa_handler = servtimeout; sa.sa_flags = 0; /* don't restart system calls */ (void)sigaction(SIGALRM, &sa, NULL); retry: if (timedout) { timedout = 0; if (--retries <= 0) { /* * If we ran out of tries but there is an alternate * server, switch to it and try again. */ if (alt_retries) { auth_server = alt_server; retries = alt_retries; alt_retries = 0; getsecret(); } else warnx("no response from authentication server"); } } if (retries > 0) { rad_request(req_id, userstyle, passwd, auth_port, vector, pwstate); switch (i = rad_recv(_pwstate, challenge, vector)) { case PW_AUTHENTICATION_ACK: /* * Make sure we don't think a challenge was issued. */ if (challenge) *challenge = '\0'; return (0); case PW_AUTHENTICATION_REJECT: return (1); case PW_ACCESS_CHALLENGE: /* * If this is a response then reject them if * we got a challenge. */ if (password) return (1); /* * If we wanted a challenge, just return */ if (challenge) { if (strcmp(challenge, _pwstate) != 0) syslog(LOG_WARNING, "challenge for %s does not match state", userstyle); return (0); } req_id++; if ((passwd = readpassphrase("", pbuf, sizeof(pbuf), RPP_ECHO_OFF)) == NULL) passwd = ""; break; default: if (timedout) goto retry; snprintf(_pwstate, sizeof(_pwstate), "invalid response type %d\n", i); *emsg = _pwstate; return (1); } } return (1); } /* * Build a radius authentication digest and submit it to the radius server */ void rad_request(u_char id, char *name, char *password, int port, char *vector, char *state) { auth_hdr_t auth; int i, len, secretlen, total_length, p; struct sockaddr_in sin; u_char md5buf[MAXSECRETLEN+AUTH_VECTOR_LEN], digest[AUTH_VECTOR_LEN], pass_buf[AUTH_PASS_LEN], *pw, *ptr, *ma; u_int length; in_addr_t ipaddr; MD5_CTX context; memset(&auth, 0, sizeof(auth)); auth.code = PW_AUTHENTICATION_REQUEST; auth.id = id; memcpy(auth.vector, vector, AUTH_VECTOR_LEN); total_length = AUTH_HDR_LEN; ptr = auth.data; /* Preserve space for msgauth */ *ptr++ = PW_MSG_AUTH; length = 16; *ptr++ = length + 2; ma = ptr; memset(ma, 0, 16); ptr += length; total_length += length + 2; /* User name */ *ptr++ = PW_USER_NAME; length = strlen(name); if (length > MAXPWNETNAM) length = MAXPWNETNAM; *ptr++ = length + 2; memcpy(ptr, name, length); ptr += length; total_length += length + 2; /* Password */ length = strlen(password); if (length > AUTH_PASS_LEN) length = AUTH_PASS_LEN; p = (length + AUTH_VECTOR_LEN - 1) / AUTH_VECTOR_LEN; *ptr++ = PW_PASSWORD; *ptr++ = p * AUTH_VECTOR_LEN + 2; memset(pass_buf, 0, sizeof(pass_buf)); /* must zero fill */ strlcpy((char *)pass_buf, password, sizeof(pass_buf)); /* Calculate the md5 digest */ secretlen = strlen(auth_secret); memcpy(md5buf, auth_secret, secretlen); memcpy(md5buf + secretlen, auth.vector, AUTH_VECTOR_LEN); total_length += 2; /* XOR the password into the md5 digest */ pw = pass_buf; while (p-- > 0) { MD5_Init(&context); MD5_Update(&context, md5buf, secretlen + AUTH_VECTOR_LEN); MD5_Final(digest, &context); for (i = 0; i < AUTH_VECTOR_LEN; ++i) { *ptr = digest[i] ^ *pw; md5buf[secretlen+i] = *ptr++; *pw++ = '\0'; } total_length += AUTH_VECTOR_LEN; } explicit_bzero(pass_buf, strlen(pass_buf)); /* Client id */ *ptr++ = PW_CLIENT_ID; *ptr++ = sizeof(in_addr_t) + 2; ipaddr = gethost(); memcpy(ptr, &ipaddr, sizeof(in_addr_t)); ptr += sizeof(in_addr_t); total_length += sizeof(in_addr_t) + 2; /* client port */ *ptr++ = PW_CLIENT_PORT_ID; *ptr++ = sizeof(in_addr_t) + 2; port = htonl(port); memcpy(ptr, &port, sizeof(int)); ptr += sizeof(int); total_length += sizeof(int) + 2; /* Append the state info */ if ((state != NULL) && (strlen(state) > 0)) { len = strlen(state); *ptr++ = PW_STATE; *ptr++ = len + 2; memcpy(ptr, state, len); ptr += len; total_length += len + 2; } auth.length = htons(total_length); /* Calc msgauth */ if (HMAC(EVP_md5(), auth_secret, secretlen, (unsigned char *)&auth, total_length, ma, NULL) == NULL) errx(1, "HMAC() failed"); memset(&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = auth_server; sin.sin_port = radius_port; if (sendto(sockfd, &auth, total_length, 0, (struct sockaddr *)&sin, sizeof(sin)) == -1) err(1, NULL); } /* * Receive UDP responses from the radius server */ int rad_recv(char *state, char *challenge, u_char *req_vector) { auth_hdr_t auth; socklen_t salen; struct sockaddr_in sin; u_char recv_vector[AUTH_VECTOR_LEN], test_vector[AUTH_VECTOR_LEN]; MD5_CTX context; ssize_t total_length; salen = sizeof(sin); alarm(timeout); total_length = recvfrom(sockfd, &auth, sizeof(auth), 0, (struct sockaddr *)&sin, &salen); alarm(0); if (total_length < AUTH_HDR_LEN) { if (timedout) return(-1); errx(1, "bogus auth packet from server"); } if (ntohs(auth.length) > total_length) errx(1, "bogus auth packet from server"); if (sin.sin_addr.s_addr != auth_server) errx(1, "bogus authentication server"); /* verify server's shared secret */ memcpy(recv_vector, auth.vector, AUTH_VECTOR_LEN); memcpy(auth.vector, req_vector, AUTH_VECTOR_LEN); MD5_Init(&context); MD5_Update(&context, (u_char *)&auth, ntohs(auth.length)); MD5_Update(&context, auth_secret, strlen(auth_secret)); MD5_Final(test_vector, &context); if (memcmp(recv_vector, test_vector, AUTH_VECTOR_LEN) != 0) errx(1, "shared secret incorrect"); if (auth.code == PW_ACCESS_CHALLENGE) parse_challenge(&auth, state, challenge); return (auth.code); } /* * Get IP address of local hostname */ in_addr_t gethost(void) { char hostname[HOST_NAME_MAX+1]; if (gethostname(hostname, sizeof(hostname))) err(1, "gethost"); return (get_ipaddr(hostname)); } /* * Get an IP address in host in_addr_t notation from a hostname or dotted quad. */ in_addr_t get_ipaddr(char *host) { struct hostent *hp; if ((hp = gethostbyname(host)) == NULL) return (0); return (((struct in_addr *)hp->h_addr)->s_addr); } /* * Get the secret from the servers file */ void getsecret(void) { FILE *servfd; char *host, *secret, buffer[PATH_MAX]; size_t len; snprintf(buffer, sizeof(buffer), "%s/%s", radius_dir, RADIUS_SERVERS); if ((servfd = fopen(buffer, "r")) == NULL) { syslog(LOG_ERR, "%s: %m", buffer); return; } secret = NULL; /* Keeps gcc happy */ while ((host = fgetln(servfd, &len)) != NULL) { if (*host == '#') { memset(host, 0, len); continue; } if (host[len-1] == '\n') --len; else { /* No trailing newline, must allocate len+1 for NUL */ if ((secret = malloc(len + 1)) == NULL) { memset(host, 0, len); continue; } memcpy(secret, host, len); memset(host, 0, len); host = secret; } while (len > 0 && isspace((unsigned char)host[--len])) ; host[++len] = '\0'; while (isspace((unsigned char)*host)) { ++host; --len; } if (*host == '\0') continue; secret = host; while (*secret && !isspace((unsigned char)*secret)) ++secret; if (*secret) *secret++ = '\0'; if (get_ipaddr(host) != auth_server) { memset(host, 0, len); continue; } while (isspace((unsigned char)*secret)) ++secret; if (*secret) break; } if (host) { strlcpy(auth_secret, secret, sizeof(auth_secret)); memset(host, 0, len); } fclose(servfd); } void servtimeout(int signo) { timedout = 1; } /* * Parse a challenge received from the server */ void parse_challenge(auth_hdr_t *authhdr, char *state, char *challenge) { int length; int attribute, attribute_len; u_char *ptr; ptr = authhdr->data; length = ntohs(authhdr->length) - AUTH_HDR_LEN; *state = 0; while (length > 0) { attribute = *ptr++; attribute_len = *ptr++; length -= attribute_len; attribute_len -= 2; switch (attribute) { case PW_PORT_MESSAGE: if (challenge) { memcpy(challenge, ptr, attribute_len); challenge[attribute_len] = '\0'; } else printf("%.*s", attribute_len, ptr); break; case PW_STATE: memcpy(state, ptr, attribute_len); state[attribute_len] = '\0'; break; } ptr += attribute_len; } }