xref: /openbsd-src/usr.sbin/radiusctl/radiusctl.c (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
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 
40 static int		 radius_test (struct parse_result *);
41 static void		 radius_dump (FILE *, RADIUS_PACKET *, bool,
42 			    const char *);
43 static const char	*radius_code_str (int code);
44 static const char	*hexstr(const u_char *, int, char *, int);
45 
46 static void
47 usage(void)
48 {
49 	extern char *__progname;
50 
51 	fprintf(stderr, "usage: %s command [argument ...]\n", __progname);
52 }
53 
54 int
55 main(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 
86 struct 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 
98 static void	radius_test_send(int, short, void *);
99 static void	radius_test_recv(int, short, void *);
100 static void	radius_test_timedout(int, short, void *);
101 
102 static int
103 radius_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 
271 static void
272 radius_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 
278 retry:
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 
299 static void
300 radius_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 
306 retry:
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 
335 static void
336 radius_test_timedout(int thing, short revents, void *arg)
337 {
338 	struct radius_test	*test = arg;
339 
340 	event_del(&test->ev_recv);
341 }
342 
343 static void
344 radius_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 
479 static const char *
480 radius_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 
506 static const char *
507 hexstr(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