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