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