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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
24 */
25
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/utsname.h>
30 #include <stdlib.h>
31 #include <netinet/in.h> /* struct in_addr */
32 #include <netinet/dhcp.h>
33 #include <signal.h>
34 #include <sys/socket.h>
35 #include <net/route.h>
36 #include <net/if_arp.h>
37 #include <string.h>
38 #include <dhcpmsg.h>
39 #include <ctype.h>
40 #include <arpa/inet.h>
41 #include <arpa/nameser.h>
42 #include <resolv.h>
43 #include <netdb.h>
44 #include <fcntl.h>
45 #include <stdio.h>
46 #include <dhcp_hostconf.h>
47 #include <limits.h>
48 #include <strings.h>
49 #include <libipadm.h>
50
51 #include "states.h"
52 #include "agent.h"
53 #include "interface.h"
54 #include "util.h"
55 #include "packet.h"
56 #include "defaults.h"
57
58 /*
59 * this file contains utility functions that have no real better home
60 * of their own. they can largely be broken into six categories:
61 *
62 * o conversion functions -- functions to turn integers into strings,
63 * or to convert between units of a similar measure.
64 *
65 * o time and timer functions -- functions to handle time measurement
66 * and events.
67 *
68 * o ipc-related functions -- functions to simplify the generation of
69 * ipc messages to the agent's clients.
70 *
71 * o signal-related functions -- functions to clean up the agent when
72 * it receives a signal.
73 *
74 * o routing table manipulation functions
75 *
76 * o true miscellany -- anything else
77 */
78
79 #define ETCNODENAME "/etc/nodename"
80 #define ETCDEFAULTDOMAIN "/etc/defaultdomain"
81
82 static boolean_t is_fqdn(const char *);
83 static int dhcp_assemble_fqdn(dhcp_smach_t *dsmp);
84
85 /*
86 * pkt_type_to_string(): stringifies a packet type
87 *
88 * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
89 * boolean_t: B_TRUE if IPv6
90 * output: const char *: the stringified packet type
91 */
92
93 const char *
94 pkt_type_to_string(uchar_t type, boolean_t isv6)
95 {
96 /*
97 * note: the ordering in these arrays allows direct indexing of the
98 * table based on the RFC packet type value passed in.
99 */
100
101 static const char *v4types[] = {
102 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE",
103 "ACK", "NAK", "RELEASE", "INFORM"
104 };
105 static const char *v6types[] = {
106 NULL, "SOLICIT", "ADVERTISE", "REQUEST",
107 "CONFIRM", "RENEW", "REBIND", "REPLY",
108 "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST",
109 "RELAY-FORW", "RELAY-REPL"
110 };
111
112 if (isv6) {
113 if (type >= sizeof (v6types) / sizeof (*v6types) ||
114 v6types[type] == NULL)
115 return ("<unknown>");
116 else
117 return (v6types[type]);
118 } else {
119 if (type >= sizeof (v4types) / sizeof (*v4types) ||
120 v4types[type] == NULL)
121 return ("<unknown>");
122 else
123 return (v4types[type]);
124 }
125 }
126
127 /*
128 * monosec_to_string(): converts a monosec_t into a date string
129 *
130 * input: monosec_t: the monosec_t to convert
131 * output: const char *: the corresponding date string
132 */
133
134 const char *
135 monosec_to_string(monosec_t monosec)
136 {
137 time_t time = monosec_to_time(monosec);
138 char *time_string = ctime(&time);
139
140 /* strip off the newline -- ugh, why, why, why.. */
141 time_string[strlen(time_string) - 1] = '\0';
142 return (time_string);
143 }
144
145 /*
146 * monosec(): returns a monotonically increasing time in seconds that
147 * is not affected by stime(2) or adjtime(2).
148 *
149 * input: void
150 * output: monosec_t: the number of seconds since some time in the past
151 */
152
153 monosec_t
154 monosec(void)
155 {
156 return (gethrtime() / NANOSEC);
157 }
158
159 /*
160 * monosec_to_time(): converts a monosec_t into real wall time
161 *
162 * input: monosec_t: the absolute monosec_t to convert
163 * output: time_t: the absolute time that monosec_t represents in wall time
164 */
165
166 time_t
167 monosec_to_time(monosec_t abs_monosec)
168 {
169 return (abs_monosec - monosec()) + time(NULL);
170 }
171
172 /*
173 * hrtime_to_monosec(): converts a hrtime_t to monosec_t
174 *
175 * input: hrtime_t: the time to convert
176 * output: monosec_t: the time in monosec_t
177 */
178
179 monosec_t
180 hrtime_to_monosec(hrtime_t hrtime)
181 {
182 return (hrtime / NANOSEC);
183 }
184
185 /*
186 * print_server_msg(): prints a message from a DHCP server
187 *
188 * input: dhcp_smach_t *: the state machine the message is associated with
189 * const char *: the string to display
190 * uint_t: length of string
191 * output: void
192 */
193
194 void
195 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen)
196 {
197 if (msglen > 0) {
198 dhcpmsg(MSG_INFO, "%s: message from server: %.*s",
199 dsmp->dsm_name, msglen, msg);
200 }
201 }
202
203 /*
204 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
205 *
206 * input: int: signal the handler was called with.
207 *
208 * output: void
209 */
210
211 static void
212 alrm_exit(int sig)
213 {
214 int exitval;
215
216 if (sig == SIGALRM && grandparent != 0)
217 exitval = EXIT_SUCCESS;
218 else
219 exitval = EXIT_FAILURE;
220
221 _exit(exitval);
222 }
223
224 /*
225 * daemonize(): daemonizes the process
226 *
227 * input: void
228 * output: int: 1 on success, 0 on failure
229 */
230
231 int
232 daemonize(void)
233 {
234 /*
235 * We've found that adoption takes sufficiently long that
236 * a dhcpinfo run after dhcpagent -a is started may occur
237 * before the agent is ready to process the request.
238 * The result is an error message and an unhappy user.
239 *
240 * The initial process now sleeps for DHCP_ADOPT_SLEEP,
241 * unless interrupted by a SIGALRM, in which case it
242 * exits immediately. This has the effect that the
243 * grandparent doesn't exit until the dhcpagent is ready
244 * to process requests. This defers the the balance of
245 * the system start-up script processing until the
246 * dhcpagent is ready to field requests.
247 *
248 * grandparent is only set for the adopt case; other
249 * cases do not require the wait.
250 */
251
252 if (grandparent != 0)
253 (void) signal(SIGALRM, alrm_exit);
254
255 switch (fork()) {
256
257 case -1:
258 return (0);
259
260 case 0:
261 if (grandparent != 0)
262 (void) signal(SIGALRM, SIG_DFL);
263
264 /*
265 * setsid() makes us lose our controlling terminal,
266 * and become both a session leader and a process
267 * group leader.
268 */
269
270 (void) setsid();
271
272 /*
273 * under POSIX, a session leader can accidentally
274 * (through open(2)) acquire a controlling terminal if
275 * it does not have one. just to be safe, fork again
276 * so we are not a session leader.
277 */
278
279 switch (fork()) {
280
281 case -1:
282 return (0);
283
284 case 0:
285 (void) signal(SIGHUP, SIG_IGN);
286 (void) chdir("/");
287 (void) umask(022);
288 closefrom(0);
289 break;
290
291 default:
292 _exit(EXIT_SUCCESS);
293 }
294 break;
295
296 default:
297 if (grandparent != 0) {
298 (void) signal(SIGCHLD, SIG_IGN);
299 /*
300 * Note that we're not the agent here, so the DHCP
301 * logging subsystem hasn't been configured yet.
302 */
303 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
304 "waiting for adoption to complete.");
305 if (sleep(DHCP_ADOPT_SLEEP) == 0) {
306 syslog(LOG_WARNING | LOG_DAEMON,
307 "dhcpagent: daemonize: timed out awaiting "
308 "adoption.");
309 }
310 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
311 "wait finished");
312 }
313 _exit(EXIT_SUCCESS);
314 }
315
316 return (1);
317 }
318
319 /*
320 * update_default_route(): update the interface's default route
321 *
322 * input: int: the type of message; either RTM_ADD or RTM_DELETE
323 * struct in_addr: the default gateway to use
324 * const char *: the interface associated with the route
325 * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY)
326 * output: boolean_t: B_TRUE on success, B_FALSE on failure
327 */
328
329 static boolean_t
330 update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo,
331 int flags)
332 {
333 struct {
334 struct rt_msghdr rm_mh;
335 struct sockaddr_in rm_dst;
336 struct sockaddr_in rm_gw;
337 struct sockaddr_in rm_mask;
338 struct sockaddr_dl rm_ifp;
339 } rtmsg;
340
341 (void) memset(&rtmsg, 0, sizeof (rtmsg));
342 rtmsg.rm_mh.rtm_version = RTM_VERSION;
343 rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg);
344 rtmsg.rm_mh.rtm_type = type;
345 rtmsg.rm_mh.rtm_pid = getpid();
346 rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags;
347 rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP;
348
349 rtmsg.rm_gw.sin_family = AF_INET;
350 rtmsg.rm_gw.sin_addr = *gateway_nbo;
351
352 rtmsg.rm_dst.sin_family = AF_INET;
353 rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
354
355 rtmsg.rm_mask.sin_family = AF_INET;
356 rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
357
358 rtmsg.rm_ifp.sdl_family = AF_LINK;
359 rtmsg.rm_ifp.sdl_index = ifindex;
360
361 return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
362 }
363
364 /*
365 * add_default_route(): add the default route to the given gateway
366 *
367 * input: const char *: the name of the interface associated with the route
368 * struct in_addr: the default gateway to add
369 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
370 */
371
372 boolean_t
373 add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
374 {
375 return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP));
376 }
377
378 /*
379 * del_default_route(): deletes the default route to the given gateway
380 *
381 * input: const char *: the name of the interface associated with the route
382 * struct in_addr: if not INADDR_ANY, the default gateway to remove
383 * output: boolean_t: B_TRUE on success, B_FALSE on failure
384 */
385
386 boolean_t
387 del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
388 {
389 if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
390 return (B_TRUE);
391
392 return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0));
393 }
394
395 /*
396 * inactivity_shutdown(): shuts down agent if there are no state machines left
397 * to manage
398 *
399 * input: iu_tq_t *: unused
400 * void *: unused
401 * output: void
402 */
403
404 /* ARGSUSED */
405 void
406 inactivity_shutdown(iu_tq_t *tqp, void *arg)
407 {
408 if (smach_count() > 0) /* shouldn't happen, but... */
409 return;
410
411 dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
412
413 iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
414 }
415
416 /*
417 * graceful_shutdown(): shuts down the agent gracefully
418 *
419 * input: int: the signal that caused graceful_shutdown to be called
420 * output: void
421 */
422
423 void
424 graceful_shutdown(int sig)
425 {
426 iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
427 DHCP_REASON_SIGNAL), drain_script, NULL);
428 }
429
430 /*
431 * bind_sock(): binds a socket to a given IP address and port number
432 *
433 * input: int: the socket to bind
434 * in_port_t: the port number to bind to, host byte order
435 * in_addr_t: the address to bind to, host byte order
436 * output: boolean_t: B_TRUE on success, B_FALSE on failure
437 */
438
439 boolean_t
440 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
441 {
442 struct sockaddr_in sin;
443 int on = 1;
444
445 (void) memset(&sin, 0, sizeof (struct sockaddr_in));
446 sin.sin_family = AF_INET;
447 sin.sin_port = htons(port_hbo);
448 sin.sin_addr.s_addr = htonl(addr_hbo);
449
450 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
451
452 return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
453 }
454
455 /*
456 * bind_sock_v6(): binds a socket to a given IP address and port number
457 *
458 * input: int: the socket to bind
459 * in_port_t: the port number to bind to, host byte order
460 * in6_addr_t: the address to bind to, network byte order
461 * output: boolean_t: B_TRUE on success, B_FALSE on failure
462 */
463
464 boolean_t
465 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
466 {
467 struct sockaddr_in6 sin6;
468 int on = 1;
469
470 (void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
471 sin6.sin6_family = AF_INET6;
472 sin6.sin6_port = htons(port_hbo);
473 if (addr_nbo != NULL) {
474 (void) memcpy(&sin6.sin6_addr, addr_nbo,
475 sizeof (sin6.sin6_addr));
476 }
477
478 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
479
480 return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
481 }
482
483 /*
484 * iffile_to_hostname(): return the hostname contained on a line of the form
485 *
486 * [ ^I]*inet[ ^I]+hostname[\n]*\0
487 *
488 * in the file located at the specified path
489 *
490 * input: const char *: the path of the file to look in for the hostname
491 * output: const char *: the hostname at that path, or NULL on failure
492 */
493
494 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */
495
496 const char *
497 iffile_to_hostname(const char *path)
498 {
499 FILE *fp;
500 static char ifline[IFLINE_MAX];
501
502 fp = fopen(path, "r");
503 if (fp == NULL)
504 return (NULL);
505
506 /*
507 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
508 * such command is on a separate line (see the "while read ifcmds" code
509 * in /etc/init.d/inetinit). Thus we will read the file a line at a
510 * time, searching for a line of the form
511 *
512 * [ ^I]*inet[ ^I]+hostname[\n]*\0
513 *
514 * extract the host name from it, and check it for validity.
515 */
516 while (fgets(ifline, sizeof (ifline), fp) != NULL) {
517 char *p;
518
519 if ((p = strstr(ifline, "inet")) != NULL) {
520 if ((p != ifline) && !isspace(p[-1])) {
521 (void) fclose(fp);
522 return (NULL);
523 }
524 p += 4; /* skip over "inet" and expect spaces or tabs */
525 if ((*p == '\n') || (*p == '\0')) {
526 (void) fclose(fp);
527 return (NULL);
528 }
529 if (isspace(*p)) {
530 char *nlptr;
531
532 /* no need to read more of the file */
533 (void) fclose(fp);
534
535 while (isspace(*p))
536 p++;
537 if ((nlptr = strrchr(p, '\n')) != NULL)
538 *nlptr = '\0';
539 if (strlen(p) > MAXHOSTNAMELEN) {
540 dhcpmsg(MSG_WARNING,
541 "iffile_to_hostname:"
542 " host name too long");
543 return (NULL);
544 }
545 if (ipadm_is_valid_hostname(p)) {
546 return (p);
547 } else {
548 dhcpmsg(MSG_WARNING,
549 "iffile_to_hostname:"
550 " host name not valid");
551 return (NULL);
552 }
553 } else {
554 (void) fclose(fp);
555 return (NULL);
556 }
557 }
558 }
559
560 (void) fclose(fp);
561 return (NULL);
562 }
563
564 /*
565 * init_timer(): set up a DHCP timer
566 *
567 * input: dhcp_timer_t *: the timer to set up
568 * output: void
569 */
570
571 void
572 init_timer(dhcp_timer_t *dt, lease_t startval)
573 {
574 dt->dt_id = -1;
575 dt->dt_start = startval;
576 }
577
578 /*
579 * cancel_timer(): cancel a DHCP timer
580 *
581 * input: dhcp_timer_t *: the timer to cancel
582 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
583 */
584
585 boolean_t
586 cancel_timer(dhcp_timer_t *dt)
587 {
588 if (dt->dt_id == -1)
589 return (B_TRUE);
590
591 if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
592 dt->dt_id = -1;
593 return (B_TRUE);
594 }
595
596 return (B_FALSE);
597 }
598
599 /*
600 * schedule_timer(): schedule a DHCP timer. Note that it must not be already
601 * running, and that we can't cancel here. If it were, and
602 * we did, we'd leak a reference to the callback argument.
603 *
604 * input: dhcp_timer_t *: the timer to schedule
605 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
606 */
607
608 boolean_t
609 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
610 {
611 if (dt->dt_id != -1)
612 return (B_FALSE);
613 dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg);
614 return (dt->dt_id != -1);
615 }
616
617 /*
618 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
619 * buffer.
620 *
621 * input: const dhcpv6_option_t *: pointer to option
622 * uint_t: option length
623 * const char **: error string (nul-terminated)
624 * const char **: message from server (unterminated)
625 * uint_t *: length of server message
626 * output: int: -1 on error, or >= 0 for a DHCPv6 status code
627 */
628
629 int
630 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
631 const char **msg, uint_t *msglenp)
632 {
633 uint16_t status;
634 static const char *v6_status[] = {
635 NULL,
636 "Unknown reason",
637 "Server has no addresses available",
638 "Client record unavailable",
639 "Prefix inappropriate for link",
640 "Client must use multicast",
641 "No prefix available"
642 };
643 static char sbuf[32];
644
645 *estr = "";
646 *msg = "";
647 *msglenp = 0;
648 if (d6o == NULL)
649 return (0);
650 olen -= sizeof (*d6o);
651 if (olen < 2) {
652 *estr = "garbled status code";
653 return (-1);
654 }
655
656 *msg = (const char *)(d6o + 1) + 2;
657 *msglenp = olen - 2;
658
659 (void) memcpy(&status, d6o + 1, sizeof (status));
660 status = ntohs(status);
661 if (status > 0) {
662 if (status > DHCPV6_STAT_NOPREFIX) {
663 (void) snprintf(sbuf, sizeof (sbuf), "status %u",
664 status);
665 *estr = sbuf;
666 } else {
667 *estr = v6_status[status];
668 }
669 }
670 return (status);
671 }
672
673 void
674 write_lease_to_hostconf(dhcp_smach_t *dsmp)
675 {
676 PKT_LIST *plp[2];
677 const char *hcfile;
678
679 hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
680 plp[0] = dsmp->dsm_ack;
681 plp[1] = dsmp->dsm_orig_ack;
682 if (write_hostconf(dsmp->dsm_name, plp, 2,
683 monosec_to_time(dsmp->dsm_curstart_monosec),
684 dsmp->dsm_isv6) != -1) {
685 dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
686 } else if (errno == EROFS) {
687 dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
688 "system; not saving lease", hcfile);
689 } else {
690 dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
691 "not use cached configuration)", hcfile);
692 }
693 }
694
695 /*
696 * Try to get a string from the first line of a file, up to but not
697 * including any space (0x20) or newline.
698 */
699
700 static int
701 dhcp_get_oneline(const char *filename, char *buf, size_t buflen)
702 {
703 char value[SYS_NMLN], *c;
704 int fd, i;
705
706 if ((fd = open(filename, O_RDONLY)) <= 0) {
707 dhcpmsg(MSG_DEBUG, "dhcp_get_oneline: could not open %s",
708 filename);
709 *buf = '\0';
710 } else {
711 if ((i = read(fd, value, SYS_NMLN - 1)) <= 0) {
712 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: no line in %s",
713 filename);
714 *buf = '\0';
715 } else {
716 value[i] = '\0';
717 if ((c = strchr(value, '\n')) != NULL)
718 *c = '\0';
719 if ((c = strchr(value, ' ')) != NULL)
720 *c = '\0';
721
722 if (strlcpy(buf, value, buflen) >= buflen) {
723 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too long value, %s",
724 value);
725 *buf = '\0';
726 }
727 }
728 (void) close(fd);
729 }
730
731 return (*buf != '\0' ? 0 : -1);
732 }
733
734 /*
735 * Try to get the hostname from the /etc/nodename file. uname(2) cannot
736 * be used, because that is initialized after DHCP has solicited, in order
737 * to allow for the possibility that utsname.nodename can be set from
738 * DHCP Hostname. Here, though, we want to send a value specified
739 * advance of DHCP, so read /etc/nodename directly.
740 */
741
742 static int
743 dhcp_get_nodename(char *buf, size_t buflen)
744 {
745 return dhcp_get_oneline(ETCNODENAME, buf, buflen);
746 }
747
748 /*
749 * Try to get the value from the /etc/defaultdomain file. libnsl's
750 * domainname() cannot be used, because that is initialized after DHCP has
751 * solicited. Here, though, we want to send a value specified in advance
752 * of DHCP, so read /etc/defaultdomain directly.
753 */
754
755 static int
756 dhcp_get_defaultdomain(char *buf, size_t buflen)
757 {
758 return dhcp_get_oneline(ETCDEFAULTDOMAIN, buf, buflen);
759 }
760
761 /*
762 * dhcp_add_hostname_opt(): Set CD_HOSTNAME option if REQUEST_HOSTNAME is
763 * affirmative and if 1) dsm_msg_reqhost is available; or
764 * 2) hostname is read from an extant /etc/hostname.<ifname>
765 * file; or 3) interface is primary and nodename(4) is defined.
766 *
767 * input: dhcp_pkt_t *: pointer to DHCP message being constructed;
768 * dhcp_smach_t *: pointer to interface DHCP state machine;
769 */
770
771 int
772 dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
773 {
774 const char *reqhost;
775 char nodename[MAXNAMELEN];
776
777 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME))
778 return (0);
779
780 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME");
781
782 if (dsmp->dsm_msg_reqhost != NULL
783 && ipadm_is_valid_hostname(dsmp->dsm_msg_reqhost)) {
784 reqhost = dsmp->dsm_msg_reqhost;
785 } else {
786 char hostfile[PATH_MAX + 1];
787
788 (void) snprintf(hostfile, sizeof (hostfile),
789 "/etc/hostname.%s", dsmp->dsm_name);
790 reqhost = iffile_to_hostname(hostfile);
791 }
792
793 if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
794 dhcp_get_nodename(nodename, sizeof (nodename)) == 0) {
795 reqhost = nodename;
796 }
797
798 if (reqhost != NULL) {
799 free(dsmp->dsm_reqhost);
800 if ((dsmp->dsm_reqhost = strdup(reqhost)) == NULL)
801 dhcpmsg(MSG_WARNING, "dhcp_add_hostname_opt: cannot allocate "
802 "memory for host name option");
803 }
804
805 if (dsmp->dsm_reqhost != NULL) {
806 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: host %s for %s",
807 dsmp->dsm_reqhost, dsmp->dsm_name);
808 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
809 strlen(dsmp->dsm_reqhost));
810 return 1;
811 }
812 else {
813 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: no hostname for %s",
814 dsmp->dsm_name);
815 }
816
817 return (0);
818 }
819
820 /*
821 * dhcp_add_fqdn_opt(): Set CD_CLIENTFQDN option if dsm_reqfqdn is not NULL
822 * or if dhcp_assemble_fqdn() initializes it. If dsm_reqfqdn
823 * cannot be set, no option will be added.
824 *
825 * input: dhcp_pkt_t *: pointer to DHCP message being constructed;
826 * dhcp_smach_t *: pointer to interface DHCP state machine;
827 * output: 0 if a CLIENT_FQDN was added; non-zero if otherwise;
828 */
829
830 int
831 dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
832 {
833 /*
834 * RFC 4702 section 2:
835 *
836 * The format of the Client FQDN option is:
837 *
838 * Code Len Flags RCODE1 RCODE2 Domain Name
839 * +------+------+------+------+------+------+--
840 * | 81 | n | | | | ...
841 * +------+------+------+------+------+------+--
842 *
843 * Code and Len are distinct, and the remainder is in a single buffer,
844 * opt81, for Flags + (unused) RCODE1 and RCODE2 (all octets) and a
845 * potentially maximum-length domain name.
846 *
847 * The format of the Flags field is:
848 *
849 * 0 1 2 3 4 5 6 7
850 * +-+-+-+-+-+-+-+-+
851 * | MBZ |N|E|O|S|
852 * +-+-+-+-+-+-+-+-+
853 *
854 * where MBZ is ignored and NEOS are:
855 *
856 * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to-
857 * address) DNS updates;
858 *
859 * O = 0, for a server-only response bit;
860 *
861 * E = 1 to indicate the domain name is in "canonical wire format,
862 * without compression (i.e., ns_name_pton2) .... This encoding SHOULD
863 * be used by clients ....";
864 *
865 * N = 0 to request that "the server SHALL perform DNS updates [of the
866 * PTR RR]." (1 would request SHALL NOT update).
867 *
868 * The format of the DHCPv6 Client FQDN option is shown below:
869 *
870 * 0 1 2 3
871 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
872 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
873 * | OPTION_FQDN | option-len |
874 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
875 * | flags | |
876 * +-+-+-+-+-+-+-+-+ |
877 * . .
878 * . domain-name .
879 * . .
880 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
881 *
882 * option-code OPTION_CLIENT_FQDN (39)
883 *
884 * option-len 1 + length of domain name
885 *
886 * flags flag bits used between client and server to
887 * negotiate who performs which updates
888 *
889 * domain-name the partial or fully qualified domain name
890 * (with length option-len - 1)
891 *
892 * The DHCPv6 format of the Flags field is:
893 *
894 * 0 1 2 3 4 5 6 7
895 * +-+-+-+-+-+-+-+-+
896 * | MBZ |N|O|S|
897 * +-+-+-+-+-+-+-+-+
898 */
899
900 const uint8_t S_BITPOS = 7 - 7;
901 const uint8_t E_BITPOS = 7 - 5;
902 const size_t OPT_FQDN_METALEN = 3, OPT_V6_FQDN_METALEN = 1;
903 uint_t fqdncode;
904 u_char enc_fqdnbuf[MAXNAMELEN];
905 uint8_t fqdnopt[MAXNAMELEN + OPT_FQDN_METALEN];
906 size_t len, metalen;
907
908 if (dsmp->dsm_reqfqdn == NULL && dhcp_assemble_fqdn(dsmp) != 0)
909 return (-1);
910
911 /* encode the FQDN in canonical wire format */
912
913 if (ns_name_pton2(dsmp->dsm_reqfqdn, enc_fqdnbuf, sizeof (enc_fqdnbuf),
914 &len) < 0) {
915 dhcpmsg(MSG_WARNING, "dhcp_add_fqdn_opt: error encoding domain"
916 " name %s", dsmp->dsm_reqfqdn);
917 return (-1);
918 }
919
920 dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s"
921 " for %s", dsmp->dsm_reqfqdn, dsmp->dsm_name);
922
923 bzero(fqdnopt, sizeof (fqdnopt));
924 if (dsmp->dsm_isv6) {
925 fqdncode = DHCPV6_OPT_CLIENT_FQDN;
926 metalen = OPT_V6_FQDN_METALEN;
927 *fqdnopt = (uint8_t)(1 << S_BITPOS);
928 } else {
929 fqdncode = CD_CLIENTFQDN;
930 metalen = OPT_FQDN_METALEN;
931 *fqdnopt = (uint8_t)((1 << S_BITPOS) | (1 << E_BITPOS));
932 }
933 (void) memcpy(fqdnopt + metalen, enc_fqdnbuf, len);
934 (void) add_pkt_opt(dpkt, fqdncode, fqdnopt, metalen + len);
935
936 return (0);
937 }
938
939 /*
940 * dhcp_assemble_fqdn(): Set dsm_reqfqdn if REQUEST_FQDN is set and
941 * either a host name was sent in the IPC message (e.g., from
942 * ipadm(1M) -h,--reqhost) or the interface is primary and a
943 * nodename(4) is defined. If the host name is not already fully
944 * qualified per is_fqdn(), then a value from
945 * dhcp_get_defaultdomain() or from resolv.conf(4)--if defined--
946 * is used to construct an FQDN.
947 *
948 * If no FQDN can be determined, dsm_reqfqdn will be NULL.
949 *
950 * input: dhcp_smach_t *: pointer to interface DHCP state machine;
951 * output: 0 if dsm_reqfqdn was assigned; non-zero if otherwise;
952 */
953
954 static int
955 dhcp_assemble_fqdn(dhcp_smach_t *dsmp)
956 {
957 char fqdnbuf[MAXNAMELEN], nodename[MAXNAMELEN], *reqhost;
958 size_t pos, len;
959
960 if (dsmp->dsm_reqfqdn != NULL) {
961 free(dsmp->dsm_reqfqdn);
962 dsmp->dsm_reqfqdn = NULL;
963 }
964
965 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN))
966 return (-1);
967
968 dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN");
969
970 bzero(fqdnbuf, sizeof (fqdnbuf));
971
972 reqhost = dsmp->dsm_msg_reqhost;
973 if (ipadm_is_nil_hostname(reqhost) &&
974 (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
975 dhcp_get_nodename(nodename, sizeof (nodename)) == 0) {
976 reqhost = nodename;
977 }
978
979 if (ipadm_is_nil_hostname(reqhost)) {
980 dhcpmsg(MSG_DEBUG,
981 "dhcp_assemble_fqdn: no interface reqhost for %s",
982 dsmp->dsm_name);
983 return (-1);
984 }
985
986 if ((pos = strlcpy(fqdnbuf, reqhost, sizeof (fqdnbuf))) >=
987 sizeof (fqdnbuf)) {
988 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long reqhost %s"
989 " for %s", reqhost, dsmp->dsm_name);
990 return (-1);
991 }
992
993 if (!is_fqdn(reqhost)) {
994 char dnamebuf[MAXHOSTNAMELEN];
995 size_t needdots;
996 int lasterrno;
997 char *domainname = NULL;
998
999 /*
1000 * determine FQDN domain name, if possible
1001 */
1002
1003 if (dhcp_get_defaultdomain(dnamebuf, sizeof (dnamebuf)) == 0 &&
1004 !ipadm_is_nil_hostname(dnamebuf)) {
1005 domainname = dnamebuf;
1006 } else {
1007 /*
1008 * fall back to resolv's "default domain (deprecated)"
1009 */
1010
1011 struct __res_state res_state;
1012 bzero(&res_state, sizeof (struct __res_state));
1013
1014 /* initialize resolver or warn */
1015 if ((lasterrno = res_ninit(&res_state)) != 0) {
1016 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: error %d"
1017 " initializing resolver", lasterrno);
1018 }
1019 else {
1020 if (*res_state.defdname != '\0') {
1021 (void) strlcpy(dnamebuf, res_state.defdname,
1022 sizeof (dnamebuf));
1023 domainname = dnamebuf;
1024 }
1025 res_ndestroy(&res_state);
1026 }
1027 }
1028
1029 if (ipadm_is_nil_hostname(domainname)) {
1030 dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: no domain name for %s",
1031 dsmp->dsm_name);
1032 return (-1);
1033 }
1034
1035 /*
1036 * Finish constructing FQDN. Account for space needed to hold a
1037 * separator '.' and a terminating '.'.
1038 */
1039 len = strlen(domainname);
1040 needdots = 1 + (domainname[len - 1] != '.');
1041
1042 if (pos + len + needdots >= sizeof (fqdnbuf)) {
1043 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long FQDN %s.%s"
1044 " for %s", fqdnbuf, domainname, dsmp->dsm_name);
1045 return (-1);
1046 }
1047
1048 /* add separator and then domain name */
1049 fqdnbuf[pos++] = '.';
1050 (void) strlcpy(fqdnbuf + pos, domainname, sizeof (fqdnbuf) - pos);
1051 pos += len;
1052
1053 /* ensure the final character is '.' */
1054 if (needdots > 1)
1055 fqdnbuf[pos++] = '.'; /* following is already zeroed */
1056 }
1057
1058 if (!ipadm_is_valid_hostname(fqdnbuf)) {
1059 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: invalid FQDN %s for %s",
1060 fqdnbuf, dsmp->dsm_name);
1061 return (-1);
1062 }
1063
1064 free(dsmp->dsm_reqfqdn);
1065 if ((dsmp->dsm_reqfqdn = strdup(fqdnbuf)) == NULL) {
1066 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: cannot allocate memory");
1067 return (-1);
1068 }
1069 return (0);
1070 }
1071
1072 /*
1073 * is_fqdn() : Determine if the `hostname' can be considered as a Fully
1074 * Qualified Domain Name by being "rooted" (i.e., ending in '.')
1075 * or by containing at least three DNS labels (e.g.,
1076 * srv.example.com).
1077 *
1078 * input: const char *: the hostname to inspect;
1079 * output: boolean_t: B_TRUE if `hostname' is not NULL satisfies the
1080 * criteria above; otherwise, B_FALSE;
1081 */
1082
1083 boolean_t
1084 is_fqdn(const char *hostname)
1085 {
1086 const char *c;
1087 size_t i;
1088
1089 if (hostname == NULL)
1090 return (B_FALSE);
1091
1092 i = strlen(hostname);
1093 if (i > 0 && hostname[i - 1] == '.')
1094 return (B_TRUE);
1095
1096 c = hostname;
1097 i = 0;
1098 while ((c = strchr(c, '.')) != NULL) {
1099 ++i;
1100 ++c;
1101 }
1102
1103 /* at least two separators is inferred to be fully-qualified */
1104 return (i >= 2);
1105 }