xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.sadm/dhcpmgr/lib/dd_opt.c (revision 11262:b7ebfbf2359e)
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
dd_freeopt(struct dhcp_option * opt)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 *
newopt(enum option_type ot,ushort_t count)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 *
malloc_failure()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 *
errno_opt()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 *
get_default_routers(const char * arg)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 *
get_dns_domain(const char * arg)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 *
get_dns_servers(const char * arg)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 *
get_if_param(int code,const char * arg)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 *
get_router_discovery(const char * arg)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 *
get_nis_domain(const char * arg)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 *
get_nis_servers(const char * arg)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 *
dd_getopt(ushort_t code,const char * arg,const char * context)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