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