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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <unistd.h> 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <stdlib.h> 32 #include <netinet/in.h> /* struct in_addr */ 33 #include <netinet/dhcp.h> 34 #include <signal.h> 35 #include <sys/socket.h> 36 #include <net/route.h> 37 #include <net/if_arp.h> 38 #include <string.h> 39 #include <dhcpmsg.h> 40 #include <ctype.h> 41 #include <netdb.h> 42 #include <fcntl.h> 43 #include <stdio.h> 44 45 #include "states.h" 46 #include "agent.h" 47 #include "interface.h" 48 #include "util.h" 49 #include "packet.h" 50 51 /* 52 * this file contains utility functions that have no real better home 53 * of their own. they can largely be broken into six categories: 54 * 55 * o conversion functions -- functions to turn integers into strings, 56 * or to convert between units of a similar measure. 57 * 58 * o time and timer functions -- functions to handle time measurement 59 * and events. 60 * 61 * o ipc-related functions -- functions to simplify the generation of 62 * ipc messages to the agent's clients. 63 * 64 * o signal-related functions -- functions to clean up the agent when 65 * it receives a signal. 66 * 67 * o routing table manipulation functions 68 * 69 * o true miscellany -- anything else 70 */ 71 72 /* 73 * pkt_type_to_string(): stringifies a packet type 74 * 75 * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 76 * boolean_t: B_TRUE if IPv6 77 * output: const char *: the stringified packet type 78 */ 79 80 const char * 81 pkt_type_to_string(uchar_t type, boolean_t isv6) 82 { 83 /* 84 * note: the ordering in these arrays allows direct indexing of the 85 * table based on the RFC packet type value passed in. 86 */ 87 88 static const char *v4types[] = { 89 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", 90 "ACK", "NAK", "RELEASE", "INFORM" 91 }; 92 static const char *v6types[] = { 93 NULL, "SOLICIT", "ADVERTISE", "REQUEST", 94 "CONFIRM", "RENEW", "REBIND", "REPLY", 95 "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", 96 "RELAY-FORW", "RELAY-REPL" 97 }; 98 99 if (isv6) { 100 if (type >= sizeof (v6types) / sizeof (*v6types) || 101 v6types[type] == NULL) 102 return ("<unknown>"); 103 else 104 return (v6types[type]); 105 } else { 106 if (type >= sizeof (v4types) / sizeof (*v4types) || 107 v4types[type] == NULL) 108 return ("<unknown>"); 109 else 110 return (v4types[type]); 111 } 112 } 113 114 /* 115 * monosec_to_string(): converts a monosec_t into a date string 116 * 117 * input: monosec_t: the monosec_t to convert 118 * output: const char *: the corresponding date string 119 */ 120 121 const char * 122 monosec_to_string(monosec_t monosec) 123 { 124 time_t time = monosec_to_time(monosec); 125 char *time_string = ctime(&time); 126 127 /* strip off the newline -- ugh, why, why, why.. */ 128 time_string[strlen(time_string) - 1] = '\0'; 129 return (time_string); 130 } 131 132 /* 133 * monosec(): returns a monotonically increasing time in seconds that 134 * is not affected by stime(2) or adjtime(2). 135 * 136 * input: void 137 * output: monosec_t: the number of seconds since some time in the past 138 */ 139 140 monosec_t 141 monosec(void) 142 { 143 return (gethrtime() / NANOSEC); 144 } 145 146 /* 147 * monosec_to_time(): converts a monosec_t into real wall time 148 * 149 * input: monosec_t: the absolute monosec_t to convert 150 * output: time_t: the absolute time that monosec_t represents in wall time 151 */ 152 153 time_t 154 monosec_to_time(monosec_t abs_monosec) 155 { 156 return (abs_monosec - monosec()) + time(NULL); 157 } 158 159 /* 160 * hrtime_to_monosec(): converts a hrtime_t to monosec_t 161 * 162 * input: hrtime_t: the time to convert 163 * output: monosec_t: the time in monosec_t 164 */ 165 166 monosec_t 167 hrtime_to_monosec(hrtime_t hrtime) 168 { 169 return (hrtime / NANOSEC); 170 } 171 172 /* 173 * print_server_msg(): prints a message from a DHCP server 174 * 175 * input: dhcp_smach_t *: the state machine the message is associated with 176 * const char *: the string to display 177 * uint_t: length of string 178 * output: void 179 */ 180 181 void 182 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) 183 { 184 if (msglen > 0) { 185 dhcpmsg(MSG_INFO, "%s: message from server: %.*s", 186 dsmp->dsm_name, msglen, msg); 187 } 188 } 189 190 /* 191 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. 192 * 193 * input: int: signal the handler was called with. 194 * 195 * output: void 196 */ 197 198 static void 199 alrm_exit(int sig) 200 { 201 int exitval; 202 203 if (sig == SIGALRM && grandparent != 0) 204 exitval = EXIT_SUCCESS; 205 else 206 exitval = EXIT_FAILURE; 207 208 _exit(exitval); 209 } 210 211 /* 212 * daemonize(): daemonizes the process 213 * 214 * input: void 215 * output: int: 1 on success, 0 on failure 216 */ 217 218 int 219 daemonize(void) 220 { 221 /* 222 * We've found that adoption takes sufficiently long that 223 * a dhcpinfo run after dhcpagent -a is started may occur 224 * before the agent is ready to process the request. 225 * The result is an error message and an unhappy user. 226 * 227 * The initial process now sleeps for DHCP_ADOPT_SLEEP, 228 * unless interrupted by a SIGALRM, in which case it 229 * exits immediately. This has the effect that the 230 * grandparent doesn't exit until the dhcpagent is ready 231 * to process requests. This defers the the balance of 232 * the system start-up script processing until the 233 * dhcpagent is ready to field requests. 234 * 235 * grandparent is only set for the adopt case; other 236 * cases do not require the wait. 237 */ 238 239 if (grandparent != 0) 240 (void) signal(SIGALRM, alrm_exit); 241 242 switch (fork()) { 243 244 case -1: 245 return (0); 246 247 case 0: 248 if (grandparent != 0) 249 (void) signal(SIGALRM, SIG_DFL); 250 251 /* 252 * setsid() makes us lose our controlling terminal, 253 * and become both a session leader and a process 254 * group leader. 255 */ 256 257 (void) setsid(); 258 259 /* 260 * under POSIX, a session leader can accidentally 261 * (through open(2)) acquire a controlling terminal if 262 * it does not have one. just to be safe, fork again 263 * so we are not a session leader. 264 */ 265 266 switch (fork()) { 267 268 case -1: 269 return (0); 270 271 case 0: 272 (void) signal(SIGHUP, SIG_IGN); 273 (void) chdir("/"); 274 (void) umask(022); 275 closefrom(0); 276 break; 277 278 default: 279 _exit(EXIT_SUCCESS); 280 } 281 break; 282 283 default: 284 if (grandparent != 0) { 285 (void) signal(SIGCHLD, SIG_IGN); 286 /* 287 * Note that we're not the agent here, so the DHCP 288 * logging subsystem hasn't been configured yet. 289 */ 290 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 291 "waiting for adoption to complete."); 292 if (sleep(DHCP_ADOPT_SLEEP) == 0) { 293 syslog(LOG_WARNING | LOG_DAEMON, 294 "dhcpagent: daemonize: timed out awaiting " 295 "adoption."); 296 } 297 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 298 "wait finished"); 299 } 300 _exit(EXIT_SUCCESS); 301 } 302 303 return (1); 304 } 305 306 /* 307 * update_default_route(): update the interface's default route 308 * 309 * input: int: the type of message; either RTM_ADD or RTM_DELETE 310 * struct in_addr: the default gateway to use 311 * const char *: the interface associated with the route 312 * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) 313 * output: boolean_t: B_TRUE on success, B_FALSE on failure 314 */ 315 316 static boolean_t 317 update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo, 318 int flags) 319 { 320 struct { 321 struct rt_msghdr rm_mh; 322 struct sockaddr_in rm_dst; 323 struct sockaddr_in rm_gw; 324 struct sockaddr_in rm_mask; 325 struct sockaddr_dl rm_ifp; 326 } rtmsg; 327 328 (void) memset(&rtmsg, 0, sizeof (rtmsg)); 329 rtmsg.rm_mh.rtm_version = RTM_VERSION; 330 rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); 331 rtmsg.rm_mh.rtm_type = type; 332 rtmsg.rm_mh.rtm_pid = getpid(); 333 rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags; 334 rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; 335 336 rtmsg.rm_gw.sin_family = AF_INET; 337 rtmsg.rm_gw.sin_addr = *gateway_nbo; 338 339 rtmsg.rm_dst.sin_family = AF_INET; 340 rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY); 341 342 rtmsg.rm_mask.sin_family = AF_INET; 343 rtmsg.rm_mask.sin_addr.s_addr = htonl(0); 344 345 rtmsg.rm_ifp.sdl_family = AF_LINK; 346 rtmsg.rm_ifp.sdl_index = if_nametoindex(ifname); 347 348 return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); 349 } 350 351 /* 352 * add_default_route(): add the default route to the given gateway 353 * 354 * input: const char *: the name of the interface associated with the route 355 * struct in_addr: the default gateway to add 356 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 357 */ 358 359 boolean_t 360 add_default_route(const char *ifname, struct in_addr *gateway_nbo) 361 { 362 if (strchr(ifname, ':') != NULL) /* see README */ 363 return (B_TRUE); 364 365 return (update_default_route(ifname, RTM_ADD, gateway_nbo, RTF_UP)); 366 } 367 368 /* 369 * del_default_route(): deletes the default route to the given gateway 370 * 371 * input: const char *: the name of the interface associated with the route 372 * struct in_addr: if not INADDR_ANY, the default gateway to remove 373 * output: boolean_t: B_TRUE on success, B_FALSE on failure 374 */ 375 376 boolean_t 377 del_default_route(const char *ifname, struct in_addr *gateway_nbo) 378 { 379 if (strchr(ifname, ':') != NULL) 380 return (B_TRUE); 381 382 if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ 383 return (B_TRUE); 384 385 return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0)); 386 } 387 388 /* 389 * inactivity_shutdown(): shuts down agent if there are no state machines left 390 * to manage 391 * 392 * input: iu_tq_t *: unused 393 * void *: unused 394 * output: void 395 */ 396 397 /* ARGSUSED */ 398 void 399 inactivity_shutdown(iu_tq_t *tqp, void *arg) 400 { 401 if (smach_count() > 0) /* shouldn't happen, but... */ 402 return; 403 404 dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); 405 406 iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); 407 } 408 409 /* 410 * graceful_shutdown(): shuts down the agent gracefully 411 * 412 * input: int: the signal that caused graceful_shutdown to be called 413 * output: void 414 */ 415 416 void 417 graceful_shutdown(int sig) 418 { 419 iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : 420 DHCP_REASON_SIGNAL), drain_script, NULL); 421 } 422 423 /* 424 * bind_sock(): binds a socket to a given IP address and port number 425 * 426 * input: int: the socket to bind 427 * in_port_t: the port number to bind to, host byte order 428 * in_addr_t: the address to bind to, host byte order 429 * output: boolean_t: B_TRUE on success, B_FALSE on failure 430 */ 431 432 boolean_t 433 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) 434 { 435 struct sockaddr_in sin; 436 int on = 1; 437 438 (void) memset(&sin, 0, sizeof (struct sockaddr_in)); 439 sin.sin_family = AF_INET; 440 sin.sin_port = htons(port_hbo); 441 sin.sin_addr.s_addr = htonl(addr_hbo); 442 443 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 444 445 return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); 446 } 447 448 /* 449 * bind_sock_v6(): binds a socket to a given IP address and port number 450 * 451 * input: int: the socket to bind 452 * in_port_t: the port number to bind to, host byte order 453 * in6_addr_t: the address to bind to, network byte order 454 * output: boolean_t: B_TRUE on success, B_FALSE on failure 455 */ 456 457 boolean_t 458 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) 459 { 460 struct sockaddr_in6 sin6; 461 int on = 1; 462 463 (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); 464 sin6.sin6_family = AF_INET6; 465 sin6.sin6_port = htons(port_hbo); 466 if (addr_nbo != NULL) { 467 (void) memcpy(&sin6.sin6_addr, addr_nbo, 468 sizeof (sin6.sin6_addr)); 469 } 470 471 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 472 473 return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); 474 } 475 476 /* 477 * valid_hostname(): check whether a string is a valid hostname 478 * 479 * input: const char *: the string to verify as a hostname 480 * output: boolean_t: B_TRUE if the string is a valid hostname 481 * 482 * Note that we accept both host names beginning with a digit and 483 * those containing hyphens. Neither is strictly legal according 484 * to the RFCs, but both are in common practice, so we endeavour 485 * to not break what customers are using. 486 */ 487 488 static boolean_t 489 valid_hostname(const char *hostname) 490 { 491 unsigned int i; 492 493 for (i = 0; hostname[i] != '\0'; i++) { 494 495 if (isalpha(hostname[i]) || isdigit(hostname[i]) || 496 (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0))) 497 continue; 498 499 return (B_FALSE); 500 } 501 502 return (i > 0); 503 } 504 505 /* 506 * iffile_to_hostname(): return the hostname contained on a line of the form 507 * 508 * [ ^I]*inet[ ^I]+hostname[\n]*\0 509 * 510 * in the file located at the specified path 511 * 512 * input: const char *: the path of the file to look in for the hostname 513 * output: const char *: the hostname at that path, or NULL on failure 514 */ 515 516 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */ 517 518 const char * 519 iffile_to_hostname(const char *path) 520 { 521 FILE *fp; 522 static char ifline[IFLINE_MAX]; 523 524 fp = fopen(path, "r"); 525 if (fp == NULL) 526 return (NULL); 527 528 /* 529 * /etc/hostname.<if> may contain multiple ifconfig commands, but each 530 * such command is on a separate line (see the "while read ifcmds" code 531 * in /etc/init.d/inetinit). Thus we will read the file a line at a 532 * time, searching for a line of the form 533 * 534 * [ ^I]*inet[ ^I]+hostname[\n]*\0 535 * 536 * extract the host name from it, and check it for validity. 537 */ 538 while (fgets(ifline, sizeof (ifline), fp) != NULL) { 539 char *p; 540 541 if ((p = strstr(ifline, "inet")) != NULL) { 542 if ((p != ifline) && !isspace(p[-1])) { 543 (void) fclose(fp); 544 return (NULL); 545 } 546 p += 4; /* skip over "inet" and expect spaces or tabs */ 547 if ((*p == '\n') || (*p == '\0')) { 548 (void) fclose(fp); 549 return (NULL); 550 } 551 if (isspace(*p)) { 552 char *nlptr; 553 554 /* no need to read more of the file */ 555 (void) fclose(fp); 556 557 while (isspace(*p)) 558 p++; 559 if ((nlptr = strrchr(p, '\n')) != NULL) 560 *nlptr = '\0'; 561 if (strlen(p) > MAXHOSTNAMELEN) { 562 dhcpmsg(MSG_WARNING, 563 "iffile_to_hostname:" 564 " host name too long"); 565 return (NULL); 566 } 567 if (valid_hostname(p)) { 568 return (p); 569 } else { 570 dhcpmsg(MSG_WARNING, 571 "iffile_to_hostname:" 572 " host name not valid"); 573 return (NULL); 574 } 575 } else { 576 (void) fclose(fp); 577 return (NULL); 578 } 579 } 580 } 581 582 (void) fclose(fp); 583 return (NULL); 584 } 585 586 /* 587 * init_timer(): set up a DHCP timer 588 * 589 * input: dhcp_timer_t *: the timer to set up 590 * output: void 591 */ 592 593 void 594 init_timer(dhcp_timer_t *dt, lease_t startval) 595 { 596 dt->dt_id = -1; 597 dt->dt_start = startval; 598 } 599 600 /* 601 * cancel_timer(): cancel a DHCP timer 602 * 603 * input: dhcp_timer_t *: the timer to cancel 604 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 605 */ 606 607 boolean_t 608 cancel_timer(dhcp_timer_t *dt) 609 { 610 if (dt->dt_id == -1) 611 return (B_TRUE); 612 613 if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { 614 dt->dt_id = -1; 615 return (B_TRUE); 616 } 617 618 return (B_FALSE); 619 } 620 621 /* 622 * schedule_timer(): schedule a DHCP timer. Note that it must not be already 623 * running, and that we can't cancel here. If it were, and 624 * we did, we'd leak a reference to the callback argument. 625 * 626 * input: dhcp_timer_t *: the timer to schedule 627 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 628 */ 629 630 boolean_t 631 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) 632 { 633 if (dt->dt_id != -1) 634 return (B_FALSE); 635 dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); 636 return (dt->dt_id != -1); 637 } 638 639 /* 640 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option 641 * buffer. 642 * 643 * input: const dhcpv6_option_t *: pointer to option 644 * uint_t: option length 645 * const char **: error string (nul-terminated) 646 * const char **: message from server (unterminated) 647 * uint_t *: length of server message 648 * output: int: -1 on error, or >= 0 for a DHCPv6 status code 649 */ 650 651 int 652 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, 653 const char **msg, uint_t *msglenp) 654 { 655 uint16_t status; 656 static const char *v6_status[] = { 657 NULL, 658 "Unknown reason", 659 "Server has no addresses available", 660 "Client record unavailable", 661 "Prefix inappropriate for link", 662 "Client must use multicast", 663 "No prefix available" 664 }; 665 static char sbuf[32]; 666 667 *estr = ""; 668 *msg = ""; 669 *msglenp = 0; 670 if (d6o == NULL) 671 return (0); 672 olen -= sizeof (*d6o); 673 if (olen < 2) { 674 *estr = "garbled status code"; 675 return (-1); 676 } 677 678 *msg = (const char *)(d6o + 1) + 2; 679 *msglenp = olen - 2; 680 681 (void) memcpy(&status, d6o + 1, sizeof (status)); 682 status = ntohs(status); 683 if (status > 0) { 684 if (status > DHCPV6_STAT_NOPREFIX) { 685 (void) snprintf(sbuf, sizeof (sbuf), "status %u", 686 status); 687 *estr = sbuf; 688 } else { 689 *estr = v6_status[status]; 690 } 691 } 692 return (status); 693 } 694