1/*	$OpenBSD: radiusctl.c,v 1.8 2020/02/24 07:07:11 dlg Exp $	*/
2/*
3 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17#include <sys/types.h>
18#include <sys/socket.h>
19#include <netinet/in.h>
20
21#include <arpa/inet.h>
22#include <errno.h>
23#include <err.h>
24#include <md5.h>
25#include <netdb.h>
26#include <stdbool.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include <radius.h>
33
34#include <event.h>
35
36#include "parser.h"
37#include "chap_ms.h"
38
39
40static int		 radius_test (struct parse_result *);
41static void		 radius_dump (FILE *, RADIUS_PACKET *, bool,
42			    const char *);
43static const char	*radius_code_str (int code);
44static const char	*hexstr(const u_char *, int, char *, int);
45
46static void
47usage(void)
48{
49	extern char *__progname;
50
51	fprintf(stderr, "usage: %s command [argument ...]\n", __progname);
52}
53
54int
55main(int argc, char *argv[])
56{
57	int			 ch;
58	struct parse_result	*result;
59	int			 ecode = EXIT_SUCCESS;
60
61	while ((ch = getopt(argc, argv, "")) != -1)
62		switch (ch) {
63		default:
64			usage();
65			return (EXIT_FAILURE);
66		}
67	argc -= optind;
68	argv += optind;
69
70	if ((result = parse(argc, argv)) == NULL)
71		return (EXIT_FAILURE);
72
73	switch (result->action) {
74	case NONE:
75		break;
76	case TEST:
77		if (pledge("stdio dns inet", NULL) == -1)
78			err(EXIT_FAILURE, "pledge");
79		ecode = radius_test(result);
80		break;
81	}
82
83	return (ecode);
84}
85
86struct radius_test {
87	const struct parse_result	*res;
88	int				 ecode;
89
90	RADIUS_PACKET			*reqpkt;
91	int				 sock;
92	unsigned int			 tries;
93	struct event			 ev_send;
94	struct event			 ev_recv;
95	struct event			 ev_timedout;
96};
97
98static void	radius_test_send(int, short, void *);
99static void	radius_test_recv(int, short, void *);
100static void	radius_test_timedout(int, short, void *);
101
102static int
103radius_test(struct parse_result *res)
104{
105	struct radius_test	 test = { .res = res };
106	RADIUS_PACKET		*reqpkt;
107	struct addrinfo		 hints, *ai;
108	int			 sock, retval;
109	struct sockaddr_storage	 sockaddr;
110	socklen_t		 sockaddrlen;
111	struct sockaddr_in	*sin4;
112	struct sockaddr_in6	*sin6;
113	uint32_t		 u32val;
114	uint8_t			 id;
115
116	reqpkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST);
117	if (reqpkt == NULL)
118		err(1, "radius_new_request_packet");
119	id = arc4random();
120	radius_set_id(reqpkt, id);
121
122	memset(&hints, 0, sizeof(hints));
123	hints.ai_family = PF_UNSPEC;
124	hints.ai_socktype = SOCK_DGRAM;
125
126	retval = getaddrinfo(res->hostname, "radius", &hints, &ai);
127	if (retval)
128		errx(1, "%s %s", res->hostname, gai_strerror(retval));
129
130	if (res->port != 0)
131		((struct sockaddr_in *)ai->ai_addr)->sin_port =
132		    htons(res->port);
133
134	sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK,
135	    ai->ai_protocol);
136	if (sock == -1)
137		err(1, "socket");
138
139	/* Prepare NAS-IP{,V6}-ADDRESS attribute */
140	if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1)
141		err(1, "connect");
142	sockaddrlen = sizeof(sockaddr);
143	if (getsockname(sock, (struct sockaddr *)&sockaddr, &sockaddrlen) == -1)
144		err(1, "getsockname");
145	sin4 = (struct sockaddr_in *)&sockaddr;
146	sin6 = (struct sockaddr_in6 *)&sockaddr;
147	switch (sockaddr.ss_family) {
148	case AF_INET:
149		radius_put_ipv4_attr(reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
150		    sin4->sin_addr);
151		break;
152	case AF_INET6:
153		radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
154		    sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr));
155		break;
156	}
157
158	/* User-Name and User-Password */
159	radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME,
160	    res->username);
161
162	switch (res->auth_method) {
163	case PAP:
164		if (res->password != NULL)
165			radius_put_user_password_attr(reqpkt, res->password,
166			    res->secret);
167		break;
168	case CHAP:
169	    {
170		u_char	 chal[16];
171		u_char	 resp[1 + MD5_DIGEST_LENGTH]; /* "1 + " for CHAP Id */
172		MD5_CTX	 md5ctx;
173
174		arc4random_buf(resp, 1);	/* CHAP Id is random */
175		MD5Init(&md5ctx);
176		MD5Update(&md5ctx, resp, 1);
177		if (res->password != NULL)
178			MD5Update(&md5ctx, res->password,
179			    strlen(res->password));
180		MD5Update(&md5ctx, chal, sizeof(chal));
181		MD5Final(resp + 1, &md5ctx);
182		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_CHALLENGE,
183		    chal, sizeof(chal));
184		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_PASSWORD,
185		    resp, sizeof(resp));
186	    }
187		break;
188	case MSCHAPV2:
189	    {
190		u_char	pass[256], chal[16];
191		u_int	i, lpass;
192		struct _resp {
193			u_int8_t ident;
194			u_int8_t flags;
195			char peer_challenge[16];
196			char reserved[8];
197			char response[24];
198		} __packed resp;
199
200		if (res->password == NULL) {
201			lpass = 0;
202		} else {
203			lpass = strlen(res->password);
204			if (lpass * 2 >= sizeof(pass))
205				err(1, "password too long");
206			for (i = 0; i < lpass; i++) {
207				pass[i * 2] = res->password[i];
208				pass[i * 2 + 1] = 0;
209			}
210		}
211
212		memset(&resp, 0, sizeof(resp));
213		resp.ident = arc4random();
214		arc4random_buf(chal, sizeof(chal));
215		arc4random_buf(resp.peer_challenge,
216		    sizeof(resp.peer_challenge));
217
218		mschap_nt_response(chal, resp.peer_challenge,
219		    (char *)res->username, strlen(res->username), pass,
220		    lpass * 2, resp.response);
221
222		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
223		    RADIUS_VTYPE_MS_CHAP_CHALLENGE, chal, sizeof(chal));
224		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
225		    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, sizeof(resp));
226		explicit_bzero(pass, sizeof(pass));
227	    }
228		break;
229
230	}
231	u32val = htonl(res->nas_port);
232	radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_PORT, &u32val, 4);
233
234	radius_put_message_authenticator(reqpkt, res->secret);
235
236	event_init();
237
238	test.ecode = EXIT_FAILURE;
239	test.res = res;
240	test.sock = sock;
241	test.reqpkt = reqpkt;
242
243	event_set(&test.ev_recv, sock, EV_READ|EV_PERSIST,
244	    radius_test_recv, &test);
245
246	evtimer_set(&test.ev_send, radius_test_send, &test);
247	evtimer_set(&test.ev_timedout, radius_test_timedout, &test);
248
249	event_add(&test.ev_recv, NULL);
250	evtimer_add(&test.ev_timedout, &res->maxwait);
251
252	/* Send! */
253	fprintf(stderr, "Sending:\n");
254	radius_dump(stdout, reqpkt, false, res->secret);
255	radius_test_send(0, EV_TIMEOUT, &test);
256
257	event_dispatch();
258
259	/* Release the resources */
260	radius_delete_packet(reqpkt);
261	close(sock);
262	freeaddrinfo(ai);
263
264	explicit_bzero((char *)res->secret, strlen(res->secret));
265	if (res->password)
266		explicit_bzero((char *)res->password, strlen(res->password));
267
268	return (test.ecode);
269}
270
271static void
272radius_test_send(int thing, short revents, void *arg)
273{
274	struct radius_test	*test = arg;
275	RADIUS_PACKET		*reqpkt = test->reqpkt;
276	ssize_t			 rv;
277
278retry:
279	rv = send(test->sock,
280	    radius_get_data(reqpkt), radius_get_length(reqpkt), 0);
281	if (rv == -1) {
282		switch (errno) {
283		case EINTR:
284		case EAGAIN:
285			goto retry;
286		default:
287			break;
288		}
289
290		warn("send");
291	}
292
293	if (++test->tries >= test->res->tries)
294		return;
295
296	evtimer_add(&test->ev_send, &test->res->interval);
297}
298
299static void
300radius_test_recv(int sock, short revents, void *arg)
301{
302	struct radius_test	*test = arg;
303	RADIUS_PACKET		*respkt;
304	RADIUS_PACKET		*reqpkt = test->reqpkt;
305
306retry:
307	respkt = radius_recv(sock, 0);
308	if (respkt == NULL) {
309		switch (errno) {
310		case EINTR:
311		case EAGAIN:
312			goto retry;
313		default:
314			break;
315		}
316
317		warn("recv");
318		return;
319	}
320
321	radius_set_request_packet(respkt, reqpkt);
322	if (radius_get_id(respkt) == radius_get_id(reqpkt)) {
323		fprintf(stderr, "\nReceived:\n");
324		radius_dump(stdout, respkt, true, test->res->secret);
325
326		event_del(&test->ev_recv);
327		evtimer_del(&test->ev_send);
328		evtimer_del(&test->ev_timedout);
329		test->ecode = EXIT_SUCCESS;
330	}
331
332	radius_delete_packet(respkt);
333}
334
335static void
336radius_test_timedout(int thing, short revents, void *arg)
337{
338	struct radius_test	*test = arg;
339
340	event_del(&test->ev_recv);
341}
342
343static void
344radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret)
345{
346	size_t		 len;
347	char		 buf[256], buf1[256];
348	uint32_t	 u32val;
349	struct in_addr	 ipv4;
350
351	fprintf(out,
352	    "    Id                        = %d\n"
353	    "    Code                      = %s(%d)\n",
354	    (int)radius_get_id(pkt), radius_code_str((int)radius_get_code(pkt)),
355	    (int)radius_get_code(pkt));
356	if (resp && secret) {
357		fprintf(out, "    Authenticator             = %s\n",
358		    (radius_check_response_authenticator(pkt, secret) == 0)
359		    ? "Verified" : "NG");
360		fprintf(out, "    Message-Authenticator     = %s\n",
361		    (!radius_has_attr(pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR))
362		    ? "(Not present)"
363		    : (radius_check_message_authenticator(pkt, secret) == 0)
364		    ? "Verified" : "NG");
365	}
366
367	if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME, buf,
368	    sizeof(buf)) == 0)
369		fprintf(out, "    User-Name                 = \"%s\"\n", buf);
370
371	if (secret &&
372	    radius_get_user_password_attr(pkt, buf, sizeof(buf), secret) == 0)
373		fprintf(out, "    User-Password             = \"%s\"\n", buf);
374
375	memset(buf, 0, sizeof(buf));
376	len = sizeof(buf);
377	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_PASSWORD, buf, &len)
378	    == 0)
379		fprintf(out, "    CHAP-Password             = %s\n",
380		    (hexstr(buf, len, buf1, sizeof(buf1)))
381			    ? buf1 : "(too long)");
382
383	memset(buf, 0, sizeof(buf));
384	len = sizeof(buf);
385	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_CHALLENGE, buf, &len)
386	    == 0)
387		fprintf(out, "    CHAP-Challenge            = %s\n",
388		    (hexstr(buf, len, buf1, sizeof(buf1)))
389			? buf1 : "(too long)");
390
391	memset(buf, 0, sizeof(buf));
392	len = sizeof(buf);
393	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
394	    RADIUS_VTYPE_MS_CHAP_CHALLENGE, buf, &len) == 0)
395		fprintf(out, "    MS-CHAP-Challenge         = %s\n",
396		    (hexstr(buf, len, buf1, sizeof(buf1)))
397			? buf1 : "(too long)");
398
399	memset(buf, 0, sizeof(buf));
400	len = sizeof(buf);
401	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
402	    RADIUS_VTYPE_MS_CHAP2_RESPONSE, buf, &len) == 0)
403		fprintf(out, "    MS-CHAP2-Response         = %s\n",
404		    (hexstr(buf, len, buf1, sizeof(buf1)))
405		    ? buf1 : "(too long)");
406
407	memset(buf, 0, sizeof(buf));
408	len = sizeof(buf) - 1;
409	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
410	    RADIUS_VTYPE_MS_CHAP2_SUCCESS, buf, &len) == 0) {
411		fprintf(out, "    MS-CHAP-Success           = Id=%u \"%s\"\n",
412		    (u_int)(u_char)buf[0], buf + 1);
413	}
414
415	memset(buf, 0, sizeof(buf));
416	len = sizeof(buf) - 1;
417	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
418	    RADIUS_VTYPE_MS_CHAP_ERROR, buf, &len) == 0) {
419		fprintf(out, "    MS-CHAP-Error             = Id=%u \"%s\"\n",
420		    (u_int)(u_char)buf[0], buf + 1);
421	}
422
423	memset(buf, 0, sizeof(buf));
424	len = sizeof(buf);
425	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
426	    RADIUS_VTYPE_MPPE_SEND_KEY, buf, &len) == 0)
427		fprintf(out, "    MS-MPPE-Send-Key          = %s\n",
428		    (hexstr(buf, len, buf1, sizeof(buf1)))
429		    ? buf1 : "(too long)");
430
431	memset(buf, 0, sizeof(buf));
432	len = sizeof(buf);
433	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
434	    RADIUS_VTYPE_MPPE_RECV_KEY, buf, &len) == 0)
435		fprintf(out, "    MS-MPPE-Recv-Key          = %s\n",
436		    (hexstr(buf, len, buf1, sizeof(buf1)))
437		    ? buf1 : "(too long)");
438
439	memset(buf, 0, sizeof(buf));
440	len = sizeof(buf);
441	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
442	    RADIUS_VTYPE_MPPE_ENCRYPTION_POLICY, buf, &len) == 0)
443		fprintf(out, "    MS-MPPE-Encryption-Policy = 0x%08x\n",
444		    ntohl(*(u_long *)buf));
445
446
447	memset(buf, 0, sizeof(buf));
448	len = sizeof(buf);
449	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
450	    RADIUS_VTYPE_MPPE_ENCRYPTION_TYPES, buf, &len) == 0)
451		fprintf(out, "    MS-MPPE-Encryption-Types  = 0x%08x\n",
452		    ntohl(*(u_long *)buf));
453
454	if (radius_get_string_attr(pkt, RADIUS_TYPE_REPLY_MESSAGE, buf,
455	    sizeof(buf)) == 0)
456		fprintf(out, "    Reply-Message             = \"%s\"\n", buf);
457
458	memset(buf, 0, sizeof(buf));
459	len = sizeof(buf);
460	if (radius_get_uint32_attr(pkt, RADIUS_TYPE_NAS_PORT, &u32val) == 0)
461		fprintf(out, "    NAS-Port                  = %lu\n",
462		    (u_long)u32val);
463
464	memset(buf, 0, sizeof(buf));
465	len = sizeof(buf);
466	if (radius_get_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS, &ipv4) == 0)
467		fprintf(out, "    NAS-IP-Address            = %s\n",
468		    inet_ntoa(ipv4));
469
470	memset(buf, 0, sizeof(buf));
471	len = sizeof(buf);
472	if (radius_get_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, buf, &len)
473	    == 0)
474		fprintf(out, "    NAS-IPv6-Address          = %s\n",
475		    inet_ntop(AF_INET6, buf, buf1, len));
476
477}
478
479static const char *
480radius_code_str(int code)
481{
482	int i;
483	static struct _codestr {
484		int		 code;
485		const char	*str;
486	} codestr[] = {
487	    { RADIUS_CODE_ACCESS_REQUEST,	"Access-Request" },
488	    { RADIUS_CODE_ACCESS_ACCEPT,	"Access-Accept" },
489	    { RADIUS_CODE_ACCESS_REJECT,	"Access-Reject" },
490	    { RADIUS_CODE_ACCOUNTING_REQUEST,	"Accounting-Request" },
491	    { RADIUS_CODE_ACCOUNTING_RESPONSE,	"Accounting-Response" },
492	    { RADIUS_CODE_ACCESS_CHALLENGE,	"Access-Challenge" },
493	    { RADIUS_CODE_STATUS_SERVER,	"Status-Server" },
494	    { RADIUS_CODE_STATUS_CLIENT,	"Status-Client" },
495	    { -1, NULL }
496	};
497
498	for (i = 0; codestr[i].code != -1; i++) {
499		if (codestr[i].code == code)
500			return (codestr[i].str);
501	}
502
503	return ("Unknown");
504}
505
506static const char *
507hexstr(const u_char *data, int len, char *str, int strsiz)
508{
509	int			 i, off = 0;
510	static const char	 hex[] = "0123456789abcdef";
511
512	for (i = 0; i < len; i++) {
513		if (strsiz - off < 3)
514			return (NULL);
515		str[off++] = hex[(data[i] & 0xf0) >> 4];
516		str[off++] = hex[(data[i] & 0x0f)];
517		str[off++] = ' ';
518	}
519	if (strsiz - off < 1)
520		return (NULL);
521
522	str[off++] = '\0';
523
524	return (str);
525}
526