Home | History | Annotate | Download | only in lib
      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 }
    657