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