OpenBSD-4.6/libexec/login_radius/raddauth.c

Compare this file to the similar file:
Show the results in this format:

/*	$OpenBSD: raddauth.c,v 1.23 2007/12/14 14:23:25 millert 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/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <inttypes.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 <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	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

#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;
	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 = getpass("Password:");
	} 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)) < 0) {
		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 = getpass("")) == 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;
	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;

	/* 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) {
		MD5Init(&context);
		MD5Update(&context, md5buf, secretlen + AUTH_VECTOR_LEN);
		MD5Final(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;
	}

	/* 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);

	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;

	salen = sizeof(sin);

	alarm(timeout);
	if ((recvfrom(sockfd, &auth, sizeof(auth), 0,
	    (struct sockaddr *)&sin, &salen)) < AUTH_HDR_LEN) {
		if (timedout)
			return(-1);
		errx(1, "bogus auth packet from server");
	}
	alarm(0);

	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);
	MD5Init(&context);
	MD5Update(&context, (u_char *)&auth, ntohs(auth.length));
	MD5Update(&context, auth_secret, strlen(auth_secret));
	MD5Final(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[MAXHOSTNAMELEN];

	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[MAXPATHLEN];
	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(host[--len]))
			;
		host[++len] = '\0';
		while (isspace(*host)) {
			++host;
			--len;
		}
		if (*host == '\0')
			continue;
		secret = host;
		while (*secret && !isspace(*secret))
			++secret;
		if (*secret)
			*secret++ = '\0';
		if (get_ipaddr(host) != auth_server) {
			memset(host, 0, len);
			continue;
		}
		while (isspace(*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;
	}
}