xref: /openbsd-src/usr.sbin/radiusctl/radiusctl.c (revision 4b70baf6e17fc8b27fc1f7fa7929335753fa94c3)
1 /*	$OpenBSD: radiusctl.c,v 1.7 2019/04/01 09:51:56 yasuoka 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 <err.h>
23 #include <md5.h>
24 #include <netdb.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include <radius.h>
32 
33 #include "parser.h"
34 #include "chap_ms.h"
35 
36 
37 static void		 radius_test (struct parse_result *);
38 static void		 radius_dump (FILE *, RADIUS_PACKET *, bool,
39 			    const char *);
40 static const char	*radius_code_str (int code);
41 static const char	*hexstr(const u_char *, int, char *, int);
42 
43 static void
44 usage(void)
45 {
46 	extern char *__progname;
47 
48 	fprintf(stderr, "usage: %s command [argument ...]\n", __progname);
49 }
50 
51 int
52 main(int argc, char *argv[])
53 {
54 	int			 ch;
55 	struct parse_result	*result;
56 
57 	while ((ch = getopt(argc, argv, "")) != -1)
58 		switch (ch) {
59 		default:
60 			usage();
61 			return (EXIT_FAILURE);
62 		}
63 	argc -= optind;
64 	argv += optind;
65 
66 	if ((result = parse(argc, argv)) == NULL)
67 		return (EXIT_FAILURE);
68 
69 	switch (result->action) {
70 	case NONE:
71 		break;
72 	case TEST:
73 		if (pledge("stdio dns inet", NULL) == -1)
74 			err(EXIT_FAILURE, "pledge");
75 		radius_test(result);
76 		break;
77 	}
78 
79 	return (EXIT_SUCCESS);
80 }
81 
82 static void
83 radius_test(struct parse_result *res)
84 {
85 	struct addrinfo		 hints, *ai;
86 	int			 sock, retval;
87 	struct sockaddr_storage	 sockaddr;
88 	socklen_t		 sockaddrlen;
89 	RADIUS_PACKET		*reqpkt, *respkt;
90 	struct sockaddr_in	*sin4;
91 	struct sockaddr_in6	*sin6;
92 	uint32_t		 u32val;
93 	uint8_t			 id;
94 
95 	reqpkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST);
96 	if (reqpkt == NULL)
97 		err(1, "radius_new_request_packet");
98 	id = arc4random();
99 	radius_set_id(reqpkt, id);
100 
101 	memset(&hints, 0, sizeof(hints));
102 	hints.ai_family = PF_UNSPEC;
103 	hints.ai_socktype = SOCK_DGRAM;
104 
105 	retval = getaddrinfo(res->hostname, "radius", &hints, &ai);
106 	if (retval)
107 		errx(1, "%s %s", res->hostname, gai_strerror(retval));
108 
109 	if (res->port != 0)
110 		((struct sockaddr_in *)ai->ai_addr)->sin_port =
111 		    htons(res->port);
112 
113 	sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
114 	if (sock == -1)
115 		err(1, "socket");
116 
117 	/* Prepare NAS-IP{,V6}-ADDRESS attribute */
118 	if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1)
119 		err(1, "connect");
120 	sockaddrlen = sizeof(sockaddr);
121 	if (getsockname(sock, (struct sockaddr *)&sockaddr, &sockaddrlen) == -1)
122 		err(1, "getsockname");
123 	sin4 = (struct sockaddr_in *)&sockaddr;
124 	sin6 = (struct sockaddr_in6 *)&sockaddr;
125 	switch (sockaddr.ss_family) {
126 	case AF_INET:
127 		radius_put_ipv4_attr(reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
128 		    sin4->sin_addr);
129 		break;
130 	case AF_INET6:
131 		radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
132 		    sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr));
133 		break;
134 	}
135 
136 	/* User-Name and User-Password */
137 	radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME,
138 	    res->username);
139 
140 	switch (res->auth_method) {
141 	case PAP:
142 		if (res->password != NULL)
143 			radius_put_user_password_attr(reqpkt, res->password,
144 			    res->secret);
145 		break;
146 	case CHAP:
147 	    {
148 		u_char	 chal[16];
149 		u_char	 resp[1 + MD5_DIGEST_LENGTH]; /* "1 + " for CHAP Id */
150 		MD5_CTX	 md5ctx;
151 
152 		arc4random_buf(resp, 1);	/* CHAP Id is random */
153 		MD5Init(&md5ctx);
154 		MD5Update(&md5ctx, resp, 1);
155 		if (res->password != NULL)
156 			MD5Update(&md5ctx, res->password,
157 			    strlen(res->password));
158 		MD5Update(&md5ctx, chal, sizeof(chal));
159 		MD5Final(resp + 1, &md5ctx);
160 		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_CHALLENGE,
161 		    chal, sizeof(chal));
162 		radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_PASSWORD,
163 		    resp, sizeof(resp));
164 	    }
165 		break;
166 	case MSCHAPV2:
167 	    {
168 		u_char	pass[256], chal[16];
169 		u_int	i, lpass;
170 		struct _resp {
171 			u_int8_t ident;
172 			u_int8_t flags;
173 			char peer_challenge[16];
174 			char reserved[8];
175 			char response[24];
176 		} __packed resp;
177 
178 		if (res->password == NULL) {
179 			lpass = 0;
180 		} else {
181 			lpass = strlen(res->password);
182 			if (lpass * 2 >= sizeof(pass))
183 				err(1, "password too long");
184 			for (i = 0; i < lpass; i++) {
185 				pass[i * 2] = res->password[i];
186 				pass[i * 2 + 1] = 0;
187 			}
188 		}
189 
190 		memset(&resp, 0, sizeof(resp));
191 		resp.ident = arc4random();
192 		arc4random_buf(chal, sizeof(chal));
193 		arc4random_buf(resp.peer_challenge,
194 		    sizeof(resp.peer_challenge));
195 
196 		mschap_nt_response(chal, resp.peer_challenge,
197 		    (char *)res->username, strlen(res->username), pass,
198 		    lpass * 2, resp.response);
199 
200 		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
201 		    RADIUS_VTYPE_MS_CHAP_CHALLENGE, chal, sizeof(chal));
202 		radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT,
203 		    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, sizeof(resp));
204 		explicit_bzero(pass, sizeof(pass));
205 	    }
206 		break;
207 
208 	}
209 	u32val = htonl(res->nas_port);
210 	radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_PORT, &u32val, 4);
211 
212 	radius_put_message_authenticator(reqpkt, res->secret);
213 
214 	/* Send! */
215 	fprintf(stderr, "Sending:\n");
216 	radius_dump(stdout, reqpkt, false, res->secret);
217 	if (send(sock, radius_get_data(reqpkt), radius_get_length(reqpkt), 0)
218 	    == -1)
219 		warn("send");
220 	if ((respkt = radius_recv(sock, 0)) == NULL)
221 		warn("recv");
222 	else {
223 		radius_set_request_packet(respkt, reqpkt);
224 		fprintf(stderr, "\nReceived:\n");
225 		radius_dump(stdout, respkt, true, res->secret);
226 	}
227 
228 	/* Release the resources */
229 	radius_delete_packet(reqpkt);
230 	if (respkt)
231 		radius_delete_packet(respkt);
232 	close(sock);
233 	freeaddrinfo(ai);
234 
235 	explicit_bzero((char *)res->secret, strlen(res->secret));
236 	if (res->password)
237 		explicit_bzero((char *)res->password, strlen(res->password));
238 
239 	return;
240 }
241 
242 static void
243 radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret)
244 {
245 	size_t		 len;
246 	char		 buf[256], buf1[256];
247 	uint32_t	 u32val;
248 	struct in_addr	 ipv4;
249 
250 	fprintf(out,
251 	    "    Id                        = %d\n"
252 	    "    Code                      = %s(%d)\n",
253 	    (int)radius_get_id(pkt), radius_code_str((int)radius_get_code(pkt)),
254 	    (int)radius_get_code(pkt));
255 	if (resp && secret) {
256 		fprintf(out, "    Authenticator             = %s\n",
257 		    (radius_check_response_authenticator(pkt, secret) == 0)
258 		    ? "Verified" : "NG");
259 		fprintf(out, "    Message-Authenticator     = %s\n",
260 		    (!radius_has_attr(pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR))
261 		    ? "(Not present)"
262 		    : (radius_check_message_authenticator(pkt, secret) == 0)
263 		    ? "Verified" : "NG");
264 	}
265 
266 	if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME, buf,
267 	    sizeof(buf)) == 0)
268 		fprintf(out, "    User-Name                 = \"%s\"\n", buf);
269 
270 	if (secret &&
271 	    radius_get_user_password_attr(pkt, buf, sizeof(buf), secret) == 0)
272 		fprintf(out, "    User-Password             = \"%s\"\n", buf);
273 
274 	memset(buf, 0, sizeof(buf));
275 	len = sizeof(buf);
276 	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_PASSWORD, buf, &len)
277 	    == 0)
278 		fprintf(out, "    CHAP-Password             = %s\n",
279 		    (hexstr(buf, len, buf1, sizeof(buf1)))
280 			    ? buf1 : "(too long)");
281 
282 	memset(buf, 0, sizeof(buf));
283 	len = sizeof(buf);
284 	if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_CHALLENGE, buf, &len)
285 	    == 0)
286 		fprintf(out, "    CHAP-Challenge            = %s\n",
287 		    (hexstr(buf, len, buf1, sizeof(buf1)))
288 			? buf1 : "(too long)");
289 
290 	memset(buf, 0, sizeof(buf));
291 	len = sizeof(buf);
292 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
293 	    RADIUS_VTYPE_MS_CHAP_CHALLENGE, buf, &len) == 0)
294 		fprintf(out, "    MS-CHAP-Challenge         = %s\n",
295 		    (hexstr(buf, len, buf1, sizeof(buf1)))
296 			? buf1 : "(too long)");
297 
298 	memset(buf, 0, sizeof(buf));
299 	len = sizeof(buf);
300 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
301 	    RADIUS_VTYPE_MS_CHAP2_RESPONSE, buf, &len) == 0)
302 		fprintf(out, "    MS-CHAP2-Response         = %s\n",
303 		    (hexstr(buf, len, buf1, sizeof(buf1)))
304 		    ? buf1 : "(too long)");
305 
306 	memset(buf, 0, sizeof(buf));
307 	len = sizeof(buf) - 1;
308 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
309 	    RADIUS_VTYPE_MS_CHAP2_SUCCESS, buf, &len) == 0) {
310 		fprintf(out, "    MS-CHAP-Success           = Id=%u \"%s\"\n",
311 		    (u_int)(u_char)buf[0], buf + 1);
312 	}
313 
314 	memset(buf, 0, sizeof(buf));
315 	len = sizeof(buf) - 1;
316 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
317 	    RADIUS_VTYPE_MS_CHAP_ERROR, buf, &len) == 0) {
318 		fprintf(out, "    MS-CHAP-Error             = Id=%u \"%s\"\n",
319 		    (u_int)(u_char)buf[0], buf + 1);
320 	}
321 
322 	memset(buf, 0, sizeof(buf));
323 	len = sizeof(buf);
324 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
325 	    RADIUS_VTYPE_MPPE_SEND_KEY, buf, &len) == 0)
326 		fprintf(out, "    MS-MPPE-Send-Key          = %s\n",
327 		    (hexstr(buf, len, buf1, sizeof(buf1)))
328 		    ? buf1 : "(too long)");
329 
330 	memset(buf, 0, sizeof(buf));
331 	len = sizeof(buf);
332 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
333 	    RADIUS_VTYPE_MPPE_RECV_KEY, buf, &len) == 0)
334 		fprintf(out, "    MS-MPPE-Recv-Key          = %s\n",
335 		    (hexstr(buf, len, buf1, sizeof(buf1)))
336 		    ? buf1 : "(too long)");
337 
338 	memset(buf, 0, sizeof(buf));
339 	len = sizeof(buf);
340 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
341 	    RADIUS_VTYPE_MPPE_ENCRYPTION_POLICY, buf, &len) == 0)
342 		fprintf(out, "    MS-MPPE-Encryption-Policy = 0x%08x\n",
343 		    ntohl(*(u_long *)buf));
344 
345 
346 	memset(buf, 0, sizeof(buf));
347 	len = sizeof(buf);
348 	if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
349 	    RADIUS_VTYPE_MPPE_ENCRYPTION_TYPES, buf, &len) == 0)
350 		fprintf(out, "    MS-MPPE-Encryption-Types  = 0x%08x\n",
351 		    ntohl(*(u_long *)buf));
352 
353 	if (radius_get_string_attr(pkt, RADIUS_TYPE_REPLY_MESSAGE, buf,
354 	    sizeof(buf)) == 0)
355 		fprintf(out, "    Reply-Message             = \"%s\"\n", buf);
356 
357 	memset(buf, 0, sizeof(buf));
358 	len = sizeof(buf);
359 	if (radius_get_uint32_attr(pkt, RADIUS_TYPE_NAS_PORT, &u32val) == 0)
360 		fprintf(out, "    NAS-Port                  = %lu\n",
361 		    (u_long)u32val);
362 
363 	memset(buf, 0, sizeof(buf));
364 	len = sizeof(buf);
365 	if (radius_get_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS, &ipv4) == 0)
366 		fprintf(out, "    NAS-IP-Address            = %s\n",
367 		    inet_ntoa(ipv4));
368 
369 	memset(buf, 0, sizeof(buf));
370 	len = sizeof(buf);
371 	if (radius_get_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, buf, &len)
372 	    == 0)
373 		fprintf(out, "    NAS-IPv6-Address          = %s\n",
374 		    inet_ntop(AF_INET6, buf, buf1, len));
375 
376 }
377 
378 static const char *
379 radius_code_str(int code)
380 {
381 	int i;
382 	static struct _codestr {
383 		int		 code;
384 		const char	*str;
385 	} codestr[] = {
386 	    { RADIUS_CODE_ACCESS_REQUEST,	"Access-Request" },
387 	    { RADIUS_CODE_ACCESS_ACCEPT,	"Access-Accept" },
388 	    { RADIUS_CODE_ACCESS_REJECT,	"Access-Reject" },
389 	    { RADIUS_CODE_ACCOUNTING_REQUEST,	"Accounting-Request" },
390 	    { RADIUS_CODE_ACCOUNTING_RESPONSE,	"Accounting-Response" },
391 	    { RADIUS_CODE_ACCESS_CHALLENGE,	"Access-Challenge" },
392 	    { RADIUS_CODE_STATUS_SERVER,	"Status-Server" },
393 	    { RADIUS_CODE_STATUS_CLIENT,	"Status-Client" },
394 	    { -1, NULL }
395 	};
396 
397 	for (i = 0; codestr[i].code != -1; i++) {
398 		if (codestr[i].code == code)
399 			return (codestr[i].str);
400 	}
401 
402 	return ("Unknown");
403 }
404 
405 static const char *
406 hexstr(const u_char *data, int len, char *str, int strsiz)
407 {
408 	int			 i, off = 0;
409 	static const char	 hex[] = "0123456789abcdef";
410 
411 	for (i = 0; i < len; i++) {
412 		if (strsiz - off < 3)
413 			return (NULL);
414 		str[off++] = hex[(data[i] & 0xf0) >> 4];
415 		str[off++] = hex[(data[i] & 0x0f)];
416 		str[off++] = ' ';
417 	}
418 	if (strsiz - off < 1)
419 		return (NULL);
420 
421 	str[off++] = '\0';
422 
423 	return (str);
424 }
425