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 }