1 /* $OpenBSD: radiusctl.c,v 1.6 2015/12/31 16:22:27 millert 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, " Message-Authenticator = %s\n", 257 (radius_check_response_authenticator(pkt, secret) == 0) 258 ? "Verified" : "NG"); 259 260 if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME, buf, 261 sizeof(buf)) == 0) 262 fprintf(out, " User-Name = \"%s\"\n", buf); 263 264 if (secret && 265 radius_get_user_password_attr(pkt, buf, sizeof(buf), secret) == 0) 266 fprintf(out, " User-Password = \"%s\"\n", buf); 267 268 memset(buf, 0, sizeof(buf)); 269 len = sizeof(buf); 270 if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_PASSWORD, buf, &len) 271 == 0) 272 fprintf(out, " CHAP-Password = %s\n", 273 (hexstr(buf, len, buf1, sizeof(buf1))) 274 ? buf1 : "(too long)"); 275 276 memset(buf, 0, sizeof(buf)); 277 len = sizeof(buf); 278 if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_CHALLENGE, buf, &len) 279 == 0) 280 fprintf(out, " CHAP-Challenge = %s\n", 281 (hexstr(buf, len, buf1, sizeof(buf1))) 282 ? buf1 : "(too long)"); 283 284 memset(buf, 0, sizeof(buf)); 285 len = sizeof(buf); 286 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 287 RADIUS_VTYPE_MS_CHAP_CHALLENGE, buf, &len) == 0) 288 fprintf(out, " MS-CHAP-Challenge = %s\n", 289 (hexstr(buf, len, buf1, sizeof(buf1))) 290 ? buf1 : "(too long)"); 291 292 memset(buf, 0, sizeof(buf)); 293 len = sizeof(buf); 294 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 295 RADIUS_VTYPE_MS_CHAP2_RESPONSE, buf, &len) == 0) 296 fprintf(out, " MS-CHAP2-Response = %s\n", 297 (hexstr(buf, len, buf1, sizeof(buf1))) 298 ? buf1 : "(too long)"); 299 300 memset(buf, 0, sizeof(buf)); 301 len = sizeof(buf) - 1; 302 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 303 RADIUS_VTYPE_MS_CHAP2_SUCCESS, buf, &len) == 0) { 304 fprintf(out, " MS-CHAP-Success = Id=%u \"%s\"\n", 305 (u_int)(u_char)buf[0], buf + 1); 306 } 307 308 memset(buf, 0, sizeof(buf)); 309 len = sizeof(buf) - 1; 310 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 311 RADIUS_VTYPE_MS_CHAP_ERROR, buf, &len) == 0) { 312 fprintf(out, " MS-CHAP-Error = Id=%u \"%s\"\n", 313 (u_int)(u_char)buf[0], buf + 1); 314 } 315 316 memset(buf, 0, sizeof(buf)); 317 len = sizeof(buf); 318 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 319 RADIUS_VTYPE_MPPE_SEND_KEY, buf, &len) == 0) 320 fprintf(out, " MS-MPPE-Send-Key = %s\n", 321 (hexstr(buf, len, buf1, sizeof(buf1))) 322 ? buf1 : "(too long)"); 323 324 memset(buf, 0, sizeof(buf)); 325 len = sizeof(buf); 326 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 327 RADIUS_VTYPE_MPPE_RECV_KEY, buf, &len) == 0) 328 fprintf(out, " MS-MPPE-Recv-Key = %s\n", 329 (hexstr(buf, len, buf1, sizeof(buf1))) 330 ? buf1 : "(too long)"); 331 332 memset(buf, 0, sizeof(buf)); 333 len = sizeof(buf); 334 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 335 RADIUS_VTYPE_MPPE_ENCRYPTION_POLICY, buf, &len) == 0) 336 fprintf(out, " MS-MPPE-Encryption-Policy = 0x%08x\n", 337 ntohl(*(u_long *)buf)); 338 339 340 memset(buf, 0, sizeof(buf)); 341 len = sizeof(buf); 342 if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, 343 RADIUS_VTYPE_MPPE_ENCRYPTION_TYPES, buf, &len) == 0) 344 fprintf(out, " MS-MPPE-Encryption-Types = 0x%08x\n", 345 ntohl(*(u_long *)buf)); 346 347 if (radius_get_string_attr(pkt, RADIUS_TYPE_REPLY_MESSAGE, buf, 348 sizeof(buf)) == 0) 349 fprintf(out, " Reply-Message = \"%s\"\n", buf); 350 351 memset(buf, 0, sizeof(buf)); 352 len = sizeof(buf); 353 if (radius_get_uint32_attr(pkt, RADIUS_TYPE_NAS_PORT, &u32val) == 0) 354 fprintf(out, " NAS-Port = %lu\n", 355 (u_long)u32val); 356 357 memset(buf, 0, sizeof(buf)); 358 len = sizeof(buf); 359 if (radius_get_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS, &ipv4) == 0) 360 fprintf(out, " NAS-IP-Address = %s\n", 361 inet_ntoa(ipv4)); 362 363 memset(buf, 0, sizeof(buf)); 364 len = sizeof(buf); 365 if (radius_get_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, buf, &len) 366 == 0) 367 fprintf(out, " NAS-IPv6-Address = %s\n", 368 inet_ntop(AF_INET6, buf, buf1, len)); 369 370 } 371 372 static const char * 373 radius_code_str(int code) 374 { 375 int i; 376 static struct _codestr { 377 int code; 378 const char *str; 379 } codestr[] = { 380 { RADIUS_CODE_ACCESS_REQUEST, "Access-Request" }, 381 { RADIUS_CODE_ACCESS_ACCEPT, "Access-Accept" }, 382 { RADIUS_CODE_ACCESS_REJECT, "Access-Reject" }, 383 { RADIUS_CODE_ACCOUNTING_REQUEST, "Accounting-Request" }, 384 { RADIUS_CODE_ACCOUNTING_RESPONSE, "Accounting-Response" }, 385 { RADIUS_CODE_ACCESS_CHALLENGE, "Access-Challenge" }, 386 { RADIUS_CODE_STATUS_SERVER, "Status-Server" }, 387 { RADIUS_CODE_STATUS_CLIENT, "Status-Client" }, 388 { -1, NULL } 389 }; 390 391 for (i = 0; codestr[i].code != -1; i++) { 392 if (codestr[i].code == code) 393 return (codestr[i].str); 394 } 395 396 return ("Unknown"); 397 } 398 399 static const char * 400 hexstr(const u_char *data, int len, char *str, int strsiz) 401 { 402 int i, off = 0; 403 static const char hex[] = "0123456789abcdef"; 404 405 for (i = 0; i < len; i++) { 406 if (strsiz - off < 3) 407 return (NULL); 408 str[off++] = hex[(data[i] & 0xf0) >> 4]; 409 str[off++] = hex[(data[i] & 0x0f)]; 410 str[off++] = ' '; 411 } 412 if (strsiz - off < 1) 413 return (NULL); 414 415 str[off++] = '\0'; 416 417 return (str); 418 } 419