[BACK]Return to radiusctl.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / radiusctl

File: [local] / src / usr.sbin / radiusctl / radiusctl.c (download)

Revision 1.8, Mon Feb 24 07:07:11 2020 UTC (4 years, 3 months ago) by dlg
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, HEAD
Changes since 1.7: +121 -20 lines

add retries and timeouts for test packets.

the most important bit of this is that the command will exit after
a timeout period. this is currently handy if you're running radiusctl
as a check from relayd, because at the moment it to get confused
about who it's children are if you run a lot of checks too rapidly.

before timeouts were added, radiusctl would wait forever for a
reply, but a reply may never arrive because networks are unreliable,
and worse, computers are unreliable and may be down for extended
periods of time.

the number of retries, the interval between retries, and the overall
wait time can be tweaked via command line arguments. the defaults
are set to something that seems reasonable if you're running a test.

ok yasuoka@

/*	$OpenBSD: radiusctl.c,v 1.8 2020/02/24 07:07:11 dlg Exp $	*/
/*
 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <errno.h>
#include <err.h>
#include <md5.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <radius.h>

#include <event.h>

#include "parser.h"
#include "chap_ms.h"


static int		 radius_test (struct parse_result *);
static void		 radius_dump (FILE *, RADIUS_PACKET *, bool,
			    const char *);
static const char	*radius_code_str (int code);
static const char	*hexstr(const u_char *, int, char *, int);

static void
usage(void)
{
	extern char *__progname;

	fprintf(stderr, "usage: %s command [argument ...]\n", __progname);
}

int
main(int argc, char *argv[])
{
	int			 ch;
	struct parse_result	*result;
	int			 ecode = EXIT_SUCCESS;

	while ((ch = getopt(argc, argv, "")) != -1)
		switch (ch) {
		default:
			usage();
			return (EXIT_FAILURE);
		}
	argc -= optind;
	argv += optind;

	if ((result = parse(argc, argv)) == NULL)
		return (EXIT_FAILURE);

	switch (result->action) {
	case NONE:
		break;
	case TEST:
		if (pledge("stdio dns inet", NULL) == -1)
			err(EXIT_FAILURE, "pledge");
		ecode = radius_test(result);
		break;
	}

	return (ecode);
}

struct radius_test {
	const struct parse_result	*res;
	int				 ecode;

	RADIUS_PACKET			*reqpkt;
	int				 sock;
	unsigned int			 tries;
	struct event			 ev_send;
	struct event			 ev_recv;
	struct event			 ev_timedout;
};

static void	radius_test_send(int, short, void *);
static void	radius_test_recv(int, short, void *);
static void	radius_test_timedout(int, short, void *);

static int
radius_test(struct parse_result *res)
{
	struct radius_test	 test = { .res = res };
	RADIUS_PACKET		*reqpkt;
	struct addrinfo		 hints, *ai;
	int			 sock, retval;
	struct sockaddr_storage	 sockaddr;
	socklen_t		 sockaddrlen;
	struct sockaddr_in	*sin4;
	struct sockaddr_in6	*sin6;
	uint32_t		 u32val;
	uint8_t			 id;

	reqpkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST);
	if (reqpkt == NULL)
		err(1, "radius_new_request_packet");
	id = arc4random();
	radius_set_id(reqpkt, id);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	retval = getaddrinfo(res->hostname, "radius", &hints, &ai);
	if (retval)
		errx(1, "%s %s", res->hostname, gai_strerror(retval));

	if (res->port != 0)
		((struct sockaddr_in *)ai->ai_addr)->sin_port =
		    htons(res->port);

	sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK,
	    ai->ai_protocol);
	if (sock == -1)
		err(1, "socket");

	/* Prepare NAS-IP{,V6}-ADDRESS attribute */
	if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1)
		err(1, "connect");
	sockaddrlen = sizeof(sockaddr);
	if (getsockname(sock, (struct sockaddr *)&sockaddr, &sockaddrlen) == -1)
		err(1, "getsockname");
	sin4 = (struct sockaddr_in *)&sockaddr;
	sin6 = (struct sockaddr_in6 *)&sockaddr;
	switch (sockaddr.ss_family) {
	case AF_INET:
		radius_put_ipv4_attr(reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
		    sin4->sin_addr);
		break;
	case AF_INET6:
		radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
		    sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr));
		break;
	}

	/* User-Name and User-Password */
	radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME,
	    res->username);

	switch (res->auth_method) {
	case PAP:
		if (res->password != NULL)
			radius_put_user_password_attr(reqpkt, res->password,
			    res->secret);
		break;
	case CHAP:
	    {
		u_char	 chal[16];
		u_char	 resp[1 + MD5_DIGEST_LENGTH]; /* "1 + " for CHAP Id */
		MD5_CTX	 md5ctx;

		arc4random_buf(resp, 1);	/* CHAP Id is random */
		MD5Init(&md5ctx);
		MD5Update(&md5ctx, resp, 1);
		if (res->password != NULL)
			MD5Update(&md5ctx, res->password,
			    strlen(res->password));
		MD5Update(&md5ctx, chal, sizeof(chal));
		MD5Final(resp + 1, &md5ctx);
		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_CHALLENGE,
		    chal, sizeof(chal));
		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_PASSWORD,
		    resp, sizeof(resp));
	    }
		break;
	case MSCHAPV2:
	    {
		u_char	pass[256], chal[16];
		u_int	i, lpass;
		struct _resp {
			u_int8_t ident;
			u_int8_t flags;
			char peer_challenge[16];
			char reserved[8];
			char response[24];
		} __packed resp;

		if (res->password == NULL) {
			lpass = 0;
		} else {
			lpass = strlen(res->password);
			if (lpass * 2 >= sizeof(pass))
				err(1, "password too long");
			for (i = 0; i < lpass; i++) {
				pass[i * 2] = res->password[i];
				pass[i * 2 + 1] = 0;
			}
		}

		memset(&resp, 0, sizeof(resp));
		resp.ident = arc4random();
		arc4random_buf(chal, sizeof(chal));
		arc4random_buf(resp.peer_challenge,
		    sizeof(resp.peer_challenge));

		mschap_nt_response(chal, resp.peer_challenge,
		    (char *)res->username, strlen(res->username), pass,
		    lpass * 2, resp.response);

		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
		    RADIUS_VTYPE_MS_CHAP_CHALLENGE, chal, sizeof(chal));
		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
		    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, sizeof(resp));
		explicit_bzero(pass, sizeof(pass));
	    }
		break;

	}
	u32val = htonl(res->nas_port);
	radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_PORT, &u32val, 4);

	radius_put_message_authenticator(reqpkt, res->secret);

	event_init();

	test.ecode = EXIT_FAILURE;
	test.res = res;
	test.sock = sock;
	test.reqpkt = reqpkt;
	
	event_set(&test.ev_recv, sock, EV_READ|EV_PERSIST,
	    radius_test_recv, &test);

	evtimer_set(&test.ev_send, radius_test_send, &test);
	evtimer_set(&test.ev_timedout, radius_test_timedout, &test);

	event_add(&test.ev_recv, NULL);
	evtimer_add(&test.ev_timedout, &res->maxwait);

	/* Send! */
	fprintf(stderr, "Sending:\n");
	radius_dump(stdout, reqpkt, false, res->secret);
	radius_test_send(0, EV_TIMEOUT, &test);

	event_dispatch();

	/* Release the resources */
	radius_delete_packet(reqpkt);
	close(sock);
	freeaddrinfo(ai);

	explicit_bzero((char *)res->secret, strlen(res->secret));
	if (res->password)
		explicit_bzero((char *)res->password, strlen(res->password));

	return (test.ecode);
}

static void
radius_test_send(int thing, short revents, void *arg)
{
	struct radius_test	*test = arg;
	RADIUS_PACKET		*reqpkt = test->reqpkt;
	ssize_t			 rv;

retry:
	rv = send(test->sock,
	    radius_get_data(reqpkt), radius_get_length(reqpkt), 0);
	if (rv == -1) {
		switch (errno) {
		case EINTR:
		case EAGAIN:
			goto retry;
		default:
			break;
		}

		warn("send");
	}

	if (++test->tries >= test->res->tries)
		return;

	evtimer_add(&test->ev_send, &test->res->interval);
}

static void
radius_test_recv(int sock, short revents, void *arg)
{
	struct radius_test	*test = arg;
	RADIUS_PACKET		*respkt;
	RADIUS_PACKET		*reqpkt = test->reqpkt;

retry:
	respkt = radius_recv(sock, 0);
	if (respkt == NULL) {
		switch (errno) {
		case EINTR:
		case EAGAIN:
			goto retry;
		default:
			break;
		}

		warn("recv");
		return;
	}

	radius_set_request_packet(respkt, reqpkt);
	if (radius_get_id(respkt) == radius_get_id(reqpkt)) {
		fprintf(stderr, "\nReceived:\n");
		radius_dump(stdout, respkt, true, test->res->secret);

		event_del(&test->ev_recv);
		evtimer_del(&test->ev_send);
		evtimer_del(&test->ev_timedout);
		test->ecode = EXIT_SUCCESS;
	}

	radius_delete_packet(respkt);
}

static void
radius_test_timedout(int thing, short revents, void *arg)
{
	struct radius_test	*test = arg;

	event_del(&test->ev_recv);
}

static void
radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret)
{
	size_t		 len;
	char		 buf[256], buf1[256];
	uint32_t	 u32val;
	struct in_addr	 ipv4;

	fprintf(out,
	    "    Id                        = %d\n"
	    "    Code                      = %s(%d)\n",
	    (int)radius_get_id(pkt), radius_code_str((int)radius_get_code(pkt)),
	    (int)radius_get_code(pkt));
	if (resp && secret) {
		fprintf(out, "    Authenticator             = %s\n",
		    (radius_check_response_authenticator(pkt, secret) == 0)
		    ? "Verified" : "NG");
		fprintf(out, "    Message-Authenticator     = %s\n",
		    (!radius_has_attr(pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR))
		    ? "(Not present)"
		    : (radius_check_message_authenticator(pkt, secret) == 0)
		    ? "Verified" : "NG");
	}

	if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME, buf,
	    sizeof(buf)) == 0)
		fprintf(out, "    User-Name                 = \"%s\"\n", buf);

	if (secret &&
	    radius_get_user_password_attr(pkt, buf, sizeof(buf), secret) == 0)
		fprintf(out, "    User-Password             = \"%s\"\n", buf);

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_PASSWORD, buf, &len)
	    == 0)
		fprintf(out, "    CHAP-Password             = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
			    ? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_CHALLENGE, buf, &len)
	    == 0)
		fprintf(out, "    CHAP-Challenge            = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
			? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MS_CHAP_CHALLENGE, buf, &len) == 0)
		fprintf(out, "    MS-CHAP-Challenge         = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
			? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MS_CHAP2_RESPONSE, buf, &len) == 0)
		fprintf(out, "    MS-CHAP2-Response         = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
		    ? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf) - 1;
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MS_CHAP2_SUCCESS, buf, &len) == 0) {
		fprintf(out, "    MS-CHAP-Success           = Id=%u \"%s\"\n",
		    (u_int)(u_char)buf[0], buf + 1);
	}

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf) - 1;
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MS_CHAP_ERROR, buf, &len) == 0) {
		fprintf(out, "    MS-CHAP-Error             = Id=%u \"%s\"\n",
		    (u_int)(u_char)buf[0], buf + 1);
	}

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MPPE_SEND_KEY, buf, &len) == 0)
		fprintf(out, "    MS-MPPE-Send-Key          = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
		    ? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MPPE_RECV_KEY, buf, &len) == 0)
		fprintf(out, "    MS-MPPE-Recv-Key          = %s\n",
		    (hexstr(buf, len, buf1, sizeof(buf1)))
		    ? buf1 : "(too long)");

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MPPE_ENCRYPTION_POLICY, buf, &len) == 0)
		fprintf(out, "    MS-MPPE-Encryption-Policy = 0x%08x\n",
		    ntohl(*(u_long *)buf));


	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
	    RADIUS_VTYPE_MPPE_ENCRYPTION_TYPES, buf, &len) == 0)
		fprintf(out, "    MS-MPPE-Encryption-Types  = 0x%08x\n",
		    ntohl(*(u_long *)buf));

	if (radius_get_string_attr(pkt, RADIUS_TYPE_REPLY_MESSAGE, buf,
	    sizeof(buf)) == 0)
		fprintf(out, "    Reply-Message             = \"%s\"\n", buf);

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_uint32_attr(pkt, RADIUS_TYPE_NAS_PORT, &u32val) == 0)
		fprintf(out, "    NAS-Port                  = %lu\n",
		    (u_long)u32val);

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS, &ipv4) == 0)
		fprintf(out, "    NAS-IP-Address            = %s\n",
		    inet_ntoa(ipv4));

	memset(buf, 0, sizeof(buf));
	len = sizeof(buf);
	if (radius_get_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, buf, &len)
	    == 0)
		fprintf(out, "    NAS-IPv6-Address          = %s\n",
		    inet_ntop(AF_INET6, buf, buf1, len));

}

static const char *
radius_code_str(int code)
{
	int i;
	static struct _codestr {
		int		 code;
		const char	*str;
	} codestr[] = {
	    { RADIUS_CODE_ACCESS_REQUEST,	"Access-Request" },
	    { RADIUS_CODE_ACCESS_ACCEPT,	"Access-Accept" },
	    { RADIUS_CODE_ACCESS_REJECT,	"Access-Reject" },
	    { RADIUS_CODE_ACCOUNTING_REQUEST,	"Accounting-Request" },
	    { RADIUS_CODE_ACCOUNTING_RESPONSE,	"Accounting-Response" },
	    { RADIUS_CODE_ACCESS_CHALLENGE,	"Access-Challenge" },
	    { RADIUS_CODE_STATUS_SERVER,	"Status-Server" },
	    { RADIUS_CODE_STATUS_CLIENT,	"Status-Client" },
	    { -1, NULL }
	};

	for (i = 0; codestr[i].code != -1; i++) {
		if (codestr[i].code == code)
			return (codestr[i].str);
	}

	return ("Unknown");
}

static const char *
hexstr(const u_char *data, int len, char *str, int strsiz)
{
	int			 i, off = 0;
	static const char	 hex[] = "0123456789abcdef";

	for (i = 0; i < len; i++) {
		if (strsiz - off < 3)
			return (NULL);
		str[off++] = hex[(data[i] & 0xf0) >> 4];
		str[off++] = hex[(data[i] & 0x0f)];
		str[off++] = ' ';
	}
	if (strsiz - off < 1)
		return (NULL);

	str[off++] = '\0';

	return (str);
}