Edit

IABSD.fr/src/libexec/login_radius/raddauth.c

Branch :

  • Show log

    Commit

  • Author : yasuoka
    Date : 2024-07-18 02:45:31
    Hash : 671eecb8
    Message : Since libcrypto is used to calc message authenticator, use libcrypto md5 also in other places instead libc md5. ok millert

  • libexec/login_radius/raddauth.c
  • /*	$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 <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <ctype.h>
    #include <err.h>
    #include <errno.h>
    #include <stdint.h>
    #include <limits.h>
    #include <login_cap.h>
    #include <netdb.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <syslog.h>
    #include <time.h>
    #include <unistd.h>
    #include <readpassphrase.h>
    #include <openssl/hmac.h>
    #include <openssl/md5.h>
    #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;
    	}
    }