1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/types.h> 27 #include <netinet/in.h> 28 #include <arpa/nameser.h> 29 #include <resolv.h> 30 #include <string.h> 31 #include <malloc.h> 32 #include <libintl.h> 33 #include <stdio.h> 34 #include <netinet/dhcp.h> 35 #include <rpcsvc/nis.h> 36 #include <netdb.h> 37 #include <errno.h> 38 #include <sys/sockio.h> 39 #include <dirent.h> 40 #include <procfs.h> 41 #include <netdir.h> 42 #include <arpa/inet.h> 43 #include <rpcsvc/ypclnt.h> 44 45 #include "dd_misc.h" 46 #include "dd_opt.h" 47 48 #define RDISC_FNAME "in.rdisc" 49 50 static struct dhcp_option opt_nomem = { ENOMEM, NULL }; 51 52 /* 53 * Free an allocated dhcp option structure. 54 */ 55 void 56 dd_freeopt(struct dhcp_option *opt) 57 { 58 int i; 59 60 if (opt->error_code == 0) { 61 switch (opt->u.ret.datatype) { 62 case ASCII_OPTION: 63 for (i = 0; i < opt->u.ret.count; ++i) { 64 free(opt->u.ret.data.strings[i]); 65 } 66 free(opt->u.ret.data.strings); 67 break; 68 case BOOLEAN_OPTION: 69 break; 70 case IP_OPTION: 71 for (i = 0; i < opt->u.ret.count; ++i) { 72 free(opt->u.ret.data.addrs[i]); 73 } 74 free(opt->u.ret.data.addrs); 75 break; 76 case NUMBER_OPTION: 77 free(opt->u.ret.data.numbers); 78 break; 79 case OCTET_OPTION: 80 for (i = 0; i < opt->u.ret.count; ++i) { 81 free(opt->u.ret.data.octets[i]); 82 } 83 free(opt->u.ret.data.octets); 84 break; 85 default: 86 return; 87 } 88 } 89 /* Don't free the static no-memory error return */ 90 if (opt != &opt_nomem) { 91 free(opt); 92 } 93 } 94 95 /* 96 * Allocate an option structure. 97 */ 98 99 static struct dhcp_option * 100 newopt(enum option_type ot, ushort_t count) 101 { 102 struct dhcp_option *opt; 103 104 opt = malloc(sizeof (struct dhcp_option)); 105 if ((opt != NULL) && (ot != ERROR_OPTION)) { 106 opt->error_code = 0; 107 opt->u.ret.datatype = ot; 108 switch (ot) { 109 case ASCII_OPTION: 110 opt->u.ret.data.strings = 111 calloc(count, sizeof (char *)); 112 if (opt->u.ret.data.strings == NULL) { 113 free(opt); 114 opt = NULL; 115 } else { 116 opt->u.ret.count = count; 117 } 118 break; 119 case BOOLEAN_OPTION: 120 opt->u.ret.count = count; 121 break; 122 case IP_OPTION: 123 opt->u.ret.data.addrs = calloc(count, 124 sizeof (struct in_addr *)); 125 if (opt->u.ret.data.addrs == NULL) { 126 free(opt); 127 opt = NULL; 128 } else { 129 opt->u.ret.count = count; 130 } 131 break; 132 case NUMBER_OPTION: 133 opt->u.ret.data.numbers = calloc(count, 134 sizeof (int64_t)); 135 if (opt->u.ret.data.numbers == NULL) { 136 free(opt); 137 opt = NULL; 138 } else { 139 opt->u.ret.count = count; 140 } 141 break; 142 case OCTET_OPTION: 143 opt->u.ret.data.octets = calloc(count, 144 sizeof (uchar_t *)); 145 if (opt->u.ret.data.octets == NULL) { 146 free(opt); 147 opt = NULL; 148 } else { 149 opt->u.ret.count = count; 150 } 151 break; 152 default: 153 free(opt); 154 opt = NULL; 155 } 156 } 157 return (opt); 158 } 159 160 /* 161 * Return an out of memory error 162 */ 163 static struct dhcp_option * 164 malloc_failure() 165 { 166 if (opt_nomem.u.msg == NULL) { 167 opt_nomem.u.msg = strerror(opt_nomem.error_code); 168 } 169 return (&opt_nomem); 170 } 171 172 /* 173 * Return an error based on errno value 174 */ 175 static struct dhcp_option * 176 errno_opt() { 177 struct dhcp_option *opt; 178 int serrno; 179 180 /* Save errno value before allocation attempt */ 181 serrno = errno; 182 opt = newopt(ERROR_OPTION, 0); 183 if (opt == NULL) { 184 return (malloc_failure()); 185 } 186 opt->error_code = serrno; 187 opt->u.msg = strerror(serrno); 188 return (opt); 189 } 190 /* 191 * Construct list of default routers. 192 */ 193 /*ARGSUSED*/ 194 static struct dhcp_option * 195 get_default_routers(const char *arg) 196 { 197 struct dhcp_option *opt; 198 FILE *fp; 199 char rbuff[BUFSIZ]; 200 struct in_addr **addrs = NULL; 201 struct in_addr **tmpaddrs; 202 int addrcnt = 0; 203 char *cp; 204 int i; 205 206 /* 207 * Method here is completely bogus; read output from netstat and 208 * grab lines with destination of 'default'. Look at the netstat 209 * code if you think there's a better way... 210 */ 211 if ((fp = popen("netstat -r -n -f inet", "r")) == NULL) { 212 return (errno_opt()); 213 } 214 215 while (fgets(rbuff, BUFSIZ, fp) != NULL) { 216 cp = strtok(rbuff, " \t"); 217 if (cp == NULL) 218 continue; 219 if (strcmp(cp, "default") == 0) { 220 /* got one, add to list */ 221 tmpaddrs = realloc(addrs, 222 (addrcnt+1) * sizeof (struct in_addr *)); 223 if (tmpaddrs == NULL) { 224 opt = errno_opt(); 225 for (i = addrcnt - 1; i >= 0; --i) { 226 free(addrs[i]); 227 } 228 free(addrs); 229 (void) pclose(fp); 230 return (opt); 231 } 232 addrs = tmpaddrs; 233 addrs[addrcnt] = malloc(sizeof (struct in_addr)); 234 if (addrs[addrcnt] == NULL) { 235 opt = errno_opt(); 236 for (i = addrcnt - 1; i >= 0; --i) { 237 free(addrs[i]); 238 } 239 free(addrs); 240 (void) pclose(fp); 241 return (opt); 242 } 243 244 cp = strtok(NULL, " \t"); 245 addrs[addrcnt]->s_addr = inet_addr(cp); 246 /* LINTED - comparison */ 247 if (addrs[addrcnt]->s_addr == -1) { 248 /* inet_addr didn't like it */ 249 opt = newopt(ERROR_OPTION, 0); 250 if (opt != NULL) { 251 opt->error_code = EINVAL; 252 opt->u.msg = strerror(EINVAL); 253 } 254 while (--addrcnt >= 0) 255 free(addrs[addrcnt]); 256 free(addrs); 257 (void) pclose(fp); 258 return (opt); 259 } 260 ++addrcnt; 261 } 262 } 263 (void) pclose(fp); 264 /* 265 * Return all the routers we found. 266 */ 267 if (addrcnt != 0) { 268 opt = newopt(IP_OPTION, addrcnt); 269 if (opt == NULL) { 270 for (i = 0; i < addrcnt; ++i) { 271 free(addrs[i]); 272 free(addrs); 273 } 274 return (opt); 275 } 276 for (i = 0; i < addrcnt; ++i) { 277 opt->u.ret.data.addrs[i] = addrs[i]; 278 } 279 free(addrs); 280 } else { 281 opt = newopt(ERROR_OPTION, 0); 282 if (opt != NULL) { 283 opt->error_code = 1; 284 opt->u.msg = gettext("No default router found"); 285 } 286 } 287 return (opt); 288 } 289 290 /*ARGSUSED*/ 291 static struct dhcp_option * 292 get_dns_domain(const char *arg) 293 { 294 struct dhcp_option *opt; 295 res_state statp; 296 297 statp = calloc(1, sizeof (*statp)); 298 if (statp == NULL) { 299 opt = malloc_failure(); 300 } else if (res_ninit(statp) == -1) { 301 /* Resolver failed initialization */ 302 opt = errno_opt(); 303 } else { 304 /* Initialized OK, copy domain name to return structure */ 305 opt = newopt(ASCII_OPTION, 1); 306 if (opt != NULL) { 307 /* 308 * If first one is loopback address, we return empty 309 * as this almost certainly means that DNS is not 310 * configured. 311 */ 312 if (statp->nsaddr_list[0].sin_family == AF_INET && 313 statp->nsaddr_list[0].sin_addr.s_addr == 314 ntohl(INADDR_LOOPBACK)) 315 opt->u.ret.data.strings[0] = strdup(""); 316 else 317 opt->u.ret.data.strings[0] = 318 strdup(statp->defdname); 319 if (opt->u.ret.data.strings[0] == NULL) { 320 /* Couldn't allocate return memory */ 321 dd_freeopt(opt); 322 opt = malloc_failure(); 323 } 324 } 325 } 326 if (statp != NULL) { 327 (void) res_ndestroy(statp); 328 free(statp); 329 } 330 return (opt); 331 } 332 333 /*ARGSUSED*/ 334 static struct dhcp_option * 335 get_dns_servers(const char *arg) 336 { 337 struct dhcp_option *opt; 338 int i, j; 339 res_state statp; 340 341 statp = calloc(1, sizeof (*statp)); 342 if (statp == NULL) { 343 opt = malloc_failure(); 344 } else if (res_ninit(statp) == -1) { 345 /* Resolver initialization failed */ 346 opt = errno_opt(); 347 } else if (statp->nsaddr_list[0].sin_family == AF_INET && 348 statp->nsaddr_list[0].sin_addr.s_addr == ntohl(INADDR_LOOPBACK)) { 349 /* 350 * If first one is loopback address, we ignore as this 351 * almost certainly means that DNS is not configured. 352 */ 353 opt = newopt(IP_OPTION, 0); 354 } else { 355 /* Success, copy the data into our return structure */ 356 opt = newopt(IP_OPTION, statp->nscount); 357 if (opt != NULL) { 358 for (i = 0, j = 0; i < statp->nscount; ++i) { 359 if (statp->nsaddr_list[i].sin_family != AF_INET) 360 /* IPv4 only, thanks */ 361 continue; 362 opt->u.ret.data.addrs[j] = malloc( 363 sizeof (struct in_addr)); 364 if (opt->u.ret.data.addrs[j] == NULL) { 365 /* Out of memory, return immediately */ 366 dd_freeopt(opt); 367 free(statp); 368 return (malloc_failure()); 369 } 370 *opt->u.ret.data.addrs[j++] = 371 statp->nsaddr_list[i].sin_addr; 372 } 373 /* Adjust number of addresses returned to real count */ 374 opt->u.ret.count = j; 375 } 376 } 377 if (statp != NULL) { 378 (void) res_ndestroy(statp); 379 free(statp); 380 } 381 return (opt); 382 } 383 384 /* Get parameters related to a specific interface */ 385 static struct dhcp_option * 386 get_if_param(int code, const char *arg) 387 { 388 int s; 389 struct ifconf ifc; 390 int num_ifs; 391 int i; 392 struct ifreq *ifr; 393 struct dhcp_option *opt; 394 #define MY_TRUE 1 395 #define MY_FALSE 0 396 int found = MY_FALSE; 397 struct sockaddr_in *sin; 398 399 /* 400 * Open socket, needed for doing the ioctls. Then get number of 401 * interfaces so we know how much memory to allocate, then get 402 * all the interface configurations. 403 */ 404 s = socket(AF_INET, SOCK_DGRAM, 0); 405 if (ioctl(s, SIOCGIFNUM, &num_ifs) < 0) { 406 return (errno_opt()); 407 } 408 ifc.ifc_len = num_ifs * sizeof (struct ifreq); 409 ifc.ifc_buf = malloc(ifc.ifc_len); 410 if (ifc.ifc_buf == NULL) { 411 return (malloc_failure()); 412 } 413 if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { 414 opt = errno_opt(); 415 free(ifc.ifc_buf); 416 (void) close(s); 417 return (opt); 418 } 419 420 /* 421 * Find the interface which matches the one requested, and then 422 * return the parameter requested. 423 */ 424 for (i = 0, ifr = ifc.ifc_req; i < num_ifs; ++i, ++ifr) { 425 if (strcmp(ifr->ifr_name, arg) != 0) { 426 continue; 427 } 428 found = MY_TRUE; 429 switch (code) { 430 case CD_SUBNETMASK: 431 if (ioctl(s, SIOCGIFNETMASK, ifr) < 0) { 432 opt = errno_opt(); 433 free(ifc.ifc_buf); 434 (void) close(s); 435 return (opt); 436 } 437 opt = newopt(IP_OPTION, 1); 438 if (opt == NULL) { 439 free(ifc.ifc_buf); 440 (void) close(s); 441 return (malloc_failure()); 442 } 443 opt->u.ret.data.addrs[0] = 444 malloc(sizeof (struct in_addr)); 445 if (opt->u.ret.data.addrs[0] == NULL) { 446 free(ifc.ifc_buf); 447 (void) close(s); 448 return (malloc_failure()); 449 } 450 /*LINTED - alignment*/ 451 sin = (struct sockaddr_in *)&ifr->ifr_addr; 452 *opt->u.ret.data.addrs[0] = sin->sin_addr; 453 break; 454 case CD_MTU: 455 if (ioctl(s, SIOCGIFMTU, ifr) < 0) { 456 opt = errno_opt(); 457 free(ifc.ifc_buf); 458 (void) close(s); 459 return (opt); 460 } 461 opt = newopt(NUMBER_OPTION, 1); 462 if (opt == NULL) { 463 free(ifc.ifc_buf); 464 (void) close(s); 465 return (malloc_failure()); 466 } 467 opt->u.ret.data.numbers[0] = ifr->ifr_metric; 468 break; 469 case CD_BROADCASTADDR: 470 if (ioctl(s, SIOCGIFBRDADDR, ifr) < 0) { 471 opt = errno_opt(); 472 free(ifc.ifc_buf); 473 (void) close(s); 474 return (opt); 475 } 476 opt = newopt(IP_OPTION, 1); 477 if (opt == NULL) { 478 free(ifc.ifc_buf); 479 (void) close(s); 480 return (malloc_failure()); 481 } 482 opt->u.ret.data.addrs[0] = 483 malloc(sizeof (struct in_addr)); 484 if (opt->u.ret.data.addrs[0] == NULL) { 485 free(ifc.ifc_buf); 486 (void) close(s); 487 return (malloc_failure()); 488 } 489 /*LINTED - alignment*/ 490 sin = (struct sockaddr_in *)&ifr->ifr_addr; 491 *opt->u.ret.data.addrs[0] = sin->sin_addr; 492 break; 493 default: 494 opt = newopt(ERROR_OPTION, 0); 495 opt->error_code = 1; 496 opt->u.msg = gettext("Bad option code in get_if_param"); 497 } 498 break; 499 } 500 free(ifc.ifc_buf); 501 (void) close(s); 502 if (found == MY_FALSE) { 503 opt = newopt(ERROR_OPTION, 0); 504 opt->error_code = 1; 505 opt->u.msg = gettext("No such interface"); 506 } 507 return (opt); 508 } 509 510 /* 511 * See if we are using router discovery on this system. Method is to 512 * read procfs and find out if the in.rdisc daemon is running. 513 */ 514 /*ARGSUSED*/ 515 static struct dhcp_option * 516 get_router_discovery(const char *arg) 517 { 518 struct dhcp_option *opt; 519 520 opt = newopt(NUMBER_OPTION, 1); 521 if (opt == NULL) { 522 return (malloc_failure()); 523 } 524 if (dd_getpid(RDISC_FNAME) != -1) { 525 opt->u.ret.data.numbers[0] = 1; 526 } else { 527 opt->u.ret.data.numbers[0] = 0; 528 } 529 return (opt); 530 } 531 532 /*ARGSUSED*/ 533 static struct dhcp_option * 534 get_nis_domain(const char *arg) 535 { 536 struct dhcp_option *opt; 537 char *d; 538 int err; 539 540 err = yp_get_default_domain(&d); 541 if (err != 0) { 542 opt = newopt(ERROR_OPTION, 0); 543 if (opt != NULL) { 544 opt->error_code = err; 545 opt->u.msg = gettext("Error in yp_get_default_domain"); 546 } 547 } else { 548 opt = newopt(ASCII_OPTION, 1); 549 if (opt == NULL) { 550 return (malloc_failure()); 551 } 552 opt->u.ret.data.strings[0] = strdup(d); 553 if (opt->u.ret.data.strings[0] == NULL) { 554 dd_freeopt(opt); 555 return (malloc_failure()); 556 } 557 } 558 return (opt); 559 } 560 561 /* 562 * Provide a default for the NISserv option. We can only reliably 563 * find out the master (as that's the only API) so that's what we provide. 564 */ 565 /*ARGSUSED*/ 566 static struct dhcp_option * 567 get_nis_servers(const char *arg) 568 { 569 struct dhcp_option *opt; 570 int err; 571 char *d; 572 char *m; 573 struct hostent *hent; 574 575 /* 576 * Get the default domain name, ask for master of hosts table, 577 * look master up in hosts table to get address. 578 */ 579 err = yp_get_default_domain(&d); 580 if (err != 0) { 581 opt = newopt(ERROR_OPTION, 0); 582 if (opt != NULL) { 583 opt->error_code = err; 584 opt->u.msg = gettext("Error in yp_get_default_domain"); 585 } 586 } else if ((err = yp_master(d, "hosts.byname", &m)) != 0) { 587 opt = newopt(ERROR_OPTION, 0); 588 if (opt != NULL) { 589 opt->error_code = err; 590 opt->u.msg = gettext("Error in yp_master"); 591 } 592 } else if ((hent = gethostbyname(m)) == NULL) { 593 free(m); 594 opt = newopt(ERROR_OPTION, 0); 595 if (opt != NULL) { 596 opt->error_code = h_errno; 597 opt->u.msg = gettext("Error in gethostbyname()"); 598 } 599 } else { 600 free(m); 601 opt = newopt(IP_OPTION, 1); 602 if (opt == NULL) { 603 return (malloc_failure()); 604 } 605 opt->u.ret.data.addrs[0] = malloc(sizeof (struct in_addr)); 606 if (opt->u.ret.data.addrs[0] == NULL) { 607 dd_freeopt(opt); 608 return (malloc_failure()); 609 } 610 /*LINTED - alignment*/ 611 *opt->u.ret.data.addrs[0] = *(struct in_addr *)hent->h_addr; 612 } 613 return (opt); 614 } 615 616 /* 617 * Retrieve the default value for a specified DHCP option. Option code is 618 * from the lst in dhcp.h, arg is an option-specific string argument, and 619 * context is a presently unused parameter intended to allow this mechanism 620 * to extend to vendor options in the future. For now, only standard options 621 * are supported. Note that in some cases the returned pointer may be NULL, 622 * so the caller must check for this case. 623 */ 624 625 /*ARGSUSED*/ 626 struct dhcp_option * 627 dd_getopt(ushort_t code, const char *arg, const char *context) 628 { 629 struct dhcp_option *opt; 630 631 switch (code) { 632 case CD_SUBNETMASK: 633 case CD_MTU: 634 case CD_BROADCASTADDR: 635 return (get_if_param(code, arg)); 636 case CD_ROUTER: 637 return (get_default_routers(arg)); 638 case CD_DNSSERV: 639 return (get_dns_servers(arg)); 640 case CD_DNSDOMAIN: 641 return (get_dns_domain(arg)); 642 case CD_ROUTER_DISCVRY_ON: 643 return (get_router_discovery(arg)); 644 case CD_NIS_DOMAIN: 645 return (get_nis_domain(arg)); 646 case CD_NIS_SERV: 647 return (get_nis_servers(arg)); 648 default: 649 opt = newopt(ERROR_OPTION, 0); 650 if (opt != NULL) { 651 opt->error_code = 1; 652 opt->u.msg = gettext("Unimplemented option requested"); 653 } 654 return (opt); 655 } 656 }