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 <string.h>
  27 #include <unistd.h>
  28 #include <stdlib.h>
  29 #include <sys/uio.h>
  30 #include <sys/socket.h>
  31 #include <sys/types.h>
  32 #include <fcntl.h>
  33 #include <errno.h>
  34 #include <limits.h>
  35 #include <netinet/in.h>
  36 #include <netinet/tcp.h>
  37 #include <net/if.h>
  38 #include <sys/sockio.h>
  39 #include <sys/fcntl.h>
  40 #include <sys/time.h>
  41 #include <stdio.h>                /* snprintf */
  42 #include <arpa/inet.h>            /* ntohl, ntohs, etc */
  43 
  44 #include "dhcpagent_ipc.h"
  45 #include "dhcpagent_util.h"
  46 
  47 /*
  48  * the protocol used here is a simple request/reply scheme: a client
  49  * sends a dhcp_ipc_request_t message to the agent, and the agent
  50  * sends a dhcp_ipc_reply_t back to the client.  since the requests
  51  * and replies can be variable-length, they are prefixed on "the wire"
  52  * by a 32-bit number that tells the other end how many bytes to
  53  * expect.
  54  *
  55  * the format of a request consists of a single dhcp_ipc_request_t;
  56  * note that the length of this dhcp_ipc_request_t is variable (using
  57  * the standard c array-of-size-1 trick).  the type of the payload is
  58  * given by `data_type', which is guaranteed to be `data_length' bytes
  59  * long starting at `buffer'.  note that `buffer' is guaranteed to be
  60  * 32-bit aligned but it is poor taste to rely on this.
  61  *
  62  * the format of a reply is much the same: a single dhcp_ipc_reply_t;
  63  * note again that the length of the dhcp_ipc_reply_t is variable.
  64  * the type of the payload is given by `data_type', which is
  65  * guaranteed to be `data_length' bytes long starting at `buffer'.
  66  * once again, note that `buffer' is guaranteed to be 32-bit aligned
  67  * but it is poor taste to rely on this.
  68  *
  69  * requests and replies can be paired up by comparing `ipc_id' fields.
  70  */
  71 
  72 #define BUFMAX  256
  73 
  74 static int      dhcp_ipc_timed_read(int, void *, unsigned int, int *);
  75 static int      getinfo_ifnames(const char *, dhcp_optnum_t *, DHCP_OPT **);
  76 static char     *get_ifnames(int, int);
  77 
  78 /* must be kept in sync with enum in dhcpagent_ipc.h */
  79 static const char *ipc_typestr[] = {
  80         "drop", "extend", "ping", "release", "start", "status",
  81         "inform", "get_tag"
  82 };
  83 
  84 /*
  85  * dhcp_ipc_alloc_request(): allocates a dhcp_ipc_request_t of the given type
  86  *                           and interface, with a timeout of 0.
  87  *
  88  *   input: dhcp_ipc_type_t: the type of ipc request to allocate
  89  *          const char *: the interface to associate the request with
  90  *          const void *: the payload to send with the message (NULL if none)
  91  *          uint32_t: the payload size (0 if none)
  92  *          dhcp_data_type_t: the description of the type of payload
  93  *  output: dhcp_ipc_request_t *: the request on success, NULL on failure
  94  */
  95 
  96 dhcp_ipc_request_t *
  97 dhcp_ipc_alloc_request(dhcp_ipc_type_t type, const char *ifname,
  98     const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type)
  99 {
 100         dhcp_ipc_request_t *request = calloc(1, DHCP_IPC_REQUEST_SIZE +
 101             buffer_size);
 102 
 103         if (request == NULL)
 104                 return (NULL);
 105 
 106         request->message_type   = type;
 107         request->data_length    = buffer_size;
 108         request->data_type   = data_type;
 109 
 110         if (ifname != NULL)
 111                 (void) strlcpy(request->ifname, ifname, LIFNAMSIZ);
 112 
 113         if (buffer != NULL)
 114                 (void) memcpy(request->buffer, buffer, buffer_size);
 115 
 116         return (request);
 117 }
 118 
 119 /*
 120  * dhcp_ipc_alloc_reply(): allocates a dhcp_ipc_reply_t
 121  *
 122  *   input: dhcp_ipc_request_t *: the request the reply is for
 123  *          int: the return code (0 for success, DHCP_IPC_E_* otherwise)
 124  *          const void *: the payload to send with the message (NULL if none)
 125  *          uint32_t: the payload size (0 if none)
 126  *          dhcp_data_type_t: the description of the type of payload
 127  *  output: dhcp_ipc_reply_t *: the reply on success, NULL on failure
 128  */
 129 
 130 dhcp_ipc_reply_t *
 131 dhcp_ipc_alloc_reply(dhcp_ipc_request_t *request, int return_code,
 132     const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type)
 133 {
 134         dhcp_ipc_reply_t *reply = calloc(1, DHCP_IPC_REPLY_SIZE + buffer_size);
 135 
 136         if (reply == NULL)
 137                 return (NULL);
 138 
 139         reply->message_type  = request->message_type;
 140         reply->ipc_id                = request->ipc_id;
 141         reply->return_code   = return_code;
 142         reply->data_length   = buffer_size;
 143         reply->data_type     = data_type;
 144 
 145         if (buffer != NULL)
 146                 (void) memcpy(reply->buffer, buffer, buffer_size);
 147 
 148         return (reply);
 149 }
 150 
 151 /*
 152  * dhcp_ipc_get_data(): gets the data and data type from a dhcp_ipc_reply_t
 153  *
 154  *   input: dhcp_ipc_reply_t *: the reply to get data from
 155  *          size_t *: the size of the resulting data
 156  *          dhcp_data_type_t *: the type of the message (returned)
 157  *  output: void *: a pointer to the data, if there is any.
 158  */
 159 
 160 void *
 161 dhcp_ipc_get_data(dhcp_ipc_reply_t *reply, size_t *size, dhcp_data_type_t *type)
 162 {
 163         if (reply == NULL || reply->data_length == 0) {
 164                 *size = 0;
 165                 return (NULL);
 166         }
 167 
 168         if (type != NULL)
 169                 *type = reply->data_type;
 170 
 171         *size = reply->data_length;
 172         return (reply->buffer);
 173 }
 174 
 175 /*
 176  * dhcp_ipc_recv_msg(): gets a message using the agent's ipc protocol
 177  *
 178  *   input: int: the file descriptor to get the message from
 179  *          void **: the address of a pointer to store the message
 180  *                   (dynamically allocated)
 181  *          uint32_t: the minimum length of the packet
 182  *          int: the # of milliseconds to wait for the message (-1 is forever)
 183  *  output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
 184  */
 185 
 186 static int
 187 dhcp_ipc_recv_msg(int fd, void **msg, uint32_t base_length, int msec)
 188 {
 189         int                     retval;
 190         dhcp_ipc_reply_t        *ipc_msg;
 191         uint32_t                length;
 192 
 193         retval = dhcp_ipc_timed_read(fd, &length, sizeof (uint32_t), &msec);
 194         if (retval != DHCP_IPC_SUCCESS)
 195                 return (retval);
 196 
 197         if (length == 0)
 198                 return (DHCP_IPC_E_PROTO);
 199 
 200         *msg = malloc(length);
 201         if (*msg == NULL)
 202                 return (DHCP_IPC_E_MEMORY);
 203 
 204         retval = dhcp_ipc_timed_read(fd, *msg, length, &msec);
 205         if (retval != DHCP_IPC_SUCCESS) {
 206                 free(*msg);
 207                 return (retval);
 208         }
 209 
 210         if (length < base_length) {
 211                 free(*msg);
 212                 return (DHCP_IPC_E_PROTO);
 213         }
 214 
 215         /*
 216          * the data_length field is in the same place in either ipc message.
 217          */
 218 
 219         ipc_msg = (dhcp_ipc_reply_t *)(*msg);
 220         if (ipc_msg->data_length + base_length != length) {
 221                 free(*msg);
 222                 return (DHCP_IPC_E_PROTO);
 223         }
 224 
 225         return (DHCP_IPC_SUCCESS);
 226 }
 227 
 228 /*
 229  * dhcp_ipc_recv_request(): gets a request using the agent's ipc protocol
 230  *
 231  *   input: int: the file descriptor to get the message from
 232  *          dhcp_ipc_request_t **: address of a pointer to store the request
 233  *                               (dynamically allocated)
 234  *          int: the # of milliseconds to wait for the message (-1 is forever)
 235  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 236  */
 237 
 238 int
 239 dhcp_ipc_recv_request(int fd, dhcp_ipc_request_t **request, int msec)
 240 {
 241         int     retval;
 242 
 243         retval = dhcp_ipc_recv_msg(fd, (void **)request, DHCP_IPC_REQUEST_SIZE,
 244             msec);
 245 
 246         /* guarantee that ifname will be NUL-terminated */
 247         if (retval == 0)
 248                 (*request)->ifname[LIFNAMSIZ - 1] = '\0';
 249 
 250         return (retval);
 251 }
 252 
 253 /*
 254  * dhcp_ipc_recv_reply(): gets a reply using the agent's ipc protocol
 255  *
 256  *   input: int: the file descriptor to get the message from
 257  *          dhcp_ipc_reply_t **: address of a pointer to store the reply
 258  *                               (dynamically allocated)
 259  *          int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
 260  *                   or DHCP_IPC_WAIT_DEFAULT
 261  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 262  */
 263 
 264 static int
 265 dhcp_ipc_recv_reply(int fd, dhcp_ipc_reply_t **reply, int32_t timeout)
 266 {
 267         /*
 268          * If the caller doesn't want to wait forever, and the amount of time
 269          * he wants to wait is expressible as an integer number of milliseconds
 270          * (as needed by the msg function), then we wait that amount of time
 271          * plus an extra two seconds for the daemon to do its work.  The extra
 272          * two seconds is arbitrary; it should allow plenty of time for the
 273          * daemon to respond within the existing timeout, as specified in the
 274          * original request, so the only time we give up is when the daemon is
 275          * stopped or otherwise malfunctioning.
 276          *
 277          * Note that the wait limit (milliseconds in an 'int') is over 24 days,
 278          * so it's unlikely that any request will actually be that long, and
 279          * it's unlikely that anyone will care if we wait forever on a request
 280          * for a 30 day timer.  The point is to protect against daemon
 281          * malfunction in the usual cases, not to provide an absolute command
 282          * timer.
 283          */
 284         if (timeout == DHCP_IPC_WAIT_DEFAULT)
 285                 timeout = DHCP_IPC_DEFAULT_WAIT;
 286         if (timeout != DHCP_IPC_WAIT_FOREVER && timeout < INT_MAX / 1000 - 2)
 287                 timeout = (timeout + 2) * 1000;
 288         else
 289                 timeout = -1;
 290         return (dhcp_ipc_recv_msg(fd, (void **)reply, DHCP_IPC_REPLY_SIZE,
 291             timeout));
 292 }
 293 
 294 /*
 295  * dhcp_ipc_send_msg(): transmits a message using the agent's ipc protocol
 296  *
 297  *   input: int: the file descriptor to transmit on
 298  *          void *: the message to send
 299  *          uint32_t: the message length
 300  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 301  */
 302 
 303 static int
 304 dhcp_ipc_send_msg(int fd, void *msg, uint32_t message_length)
 305 {
 306         struct iovec    iovec[2];
 307 
 308         iovec[0].iov_base = (caddr_t)&message_length;
 309         iovec[0].iov_len  = sizeof (uint32_t);
 310         iovec[1].iov_base = msg;
 311         iovec[1].iov_len  = message_length;
 312 
 313         if (writev(fd, iovec, sizeof (iovec) / sizeof (*iovec)) == -1)
 314                 return (DHCP_IPC_E_WRITEV);
 315 
 316         return (0);
 317 }
 318 
 319 /*
 320  * dhcp_ipc_send_reply(): transmits a reply using the agent's ipc protocol
 321  *
 322  *   input: int: the file descriptor to transmit on
 323  *          dhcp_ipc_reply_t *: the reply to send
 324  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 325  */
 326 
 327 int
 328 dhcp_ipc_send_reply(int fd, dhcp_ipc_reply_t *reply)
 329 {
 330         return (dhcp_ipc_send_msg(fd, reply, DHCP_IPC_REPLY_SIZE +
 331             reply->data_length));
 332 }
 333 
 334 /*
 335  * dhcp_ipc_send_request(): transmits a request using the agent's ipc protocol
 336  *
 337  *   input: int: the file descriptor to transmit on
 338  *          dhcp_ipc_request_t *: the request to send
 339  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 340  */
 341 
 342 static int
 343 dhcp_ipc_send_request(int fd, dhcp_ipc_request_t *request)
 344 {
 345         /*
 346          * for now, ipc_ids aren't really used, but they're intended
 347          * to make it easy to send several requests and then collect
 348          * all of the replies (and pair them with the requests).
 349          */
 350 
 351         request->ipc_id = gethrtime();
 352 
 353         return (dhcp_ipc_send_msg(fd, request, DHCP_IPC_REQUEST_SIZE +
 354             request->data_length));
 355 }
 356 
 357 /*
 358  * dhcp_ipc_make_request(): sends the provided request to the agent and reaps
 359  *                          the reply
 360  *
 361  *   input: dhcp_ipc_request_t *: the request to make
 362  *          dhcp_ipc_reply_t **: the reply (dynamically allocated)
 363  *          int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
 364  *                   or DHCP_IPC_WAIT_DEFAULT
 365  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 366  */
 367 
 368 int
 369 dhcp_ipc_make_request(dhcp_ipc_request_t *request, dhcp_ipc_reply_t **reply,
 370     int32_t timeout)
 371 {
 372         int                     fd, on, retval;
 373         struct sockaddr_in      sinv;
 374 
 375         fd = socket(AF_INET, SOCK_STREAM, 0);
 376         if (fd == -1)
 377                 return (DHCP_IPC_E_SOCKET);
 378 
 379         /*
 380          * Bind a privileged port if we have sufficient privilege to do so.
 381          * Continue as non-privileged otherwise.
 382          */
 383         on = 1;
 384         (void) setsockopt(fd, IPPROTO_TCP, TCP_ANONPRIVBIND, &on, sizeof (on));
 385 
 386         (void) memset(&sinv, 0, sizeof (sinv));
 387         sinv.sin_family  = AF_INET;
 388         if (bind(fd, (struct sockaddr *)&sinv, sizeof (sinv)) == -1) {
 389                 (void) dhcp_ipc_close(fd);
 390                 return (DHCP_IPC_E_BIND);
 391         }
 392 
 393         sinv.sin_port = htons(IPPORT_DHCPAGENT);
 394         sinv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 395         retval = connect(fd, (struct sockaddr *)&sinv, sizeof (sinv));
 396         if (retval == -1) {
 397                 (void) dhcp_ipc_close(fd);
 398                 return (DHCP_IPC_E_CONNECT);
 399         }
 400 
 401         request->timeout = timeout;
 402 
 403         retval = dhcp_ipc_send_request(fd, request);
 404         if (retval == 0)
 405                 retval = dhcp_ipc_recv_reply(fd, reply, timeout);
 406 
 407         (void) dhcp_ipc_close(fd);
 408 
 409         return (retval);
 410 }
 411 
 412 /*
 413  * dhcp_ipc_init(): initializes the ipc channel for use by the agent
 414  *
 415  *   input: int *: the file descriptor to accept on (returned)
 416  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 417  */
 418 
 419 int
 420 dhcp_ipc_init(int *listen_fd)
 421 {
 422         struct sockaddr_in      sin;
 423         int                     on = 1;
 424 
 425         (void) memset(&sin, 0, sizeof (struct sockaddr_in));
 426 
 427         sin.sin_family          = AF_INET;
 428         sin.sin_port            = htons(IPPORT_DHCPAGENT);
 429         sin.sin_addr.s_addr     = htonl(INADDR_LOOPBACK);
 430 
 431         *listen_fd = socket(AF_INET, SOCK_STREAM, 0);
 432         if (*listen_fd == -1)
 433                 return (DHCP_IPC_E_SOCKET);
 434 
 435         /*
 436          * we use SO_REUSEADDR here since in the case where there
 437          * really is another daemon running that is using the agent's
 438          * port, bind(3N) will fail.  so we can't lose.
 439          */
 440 
 441         (void) setsockopt(*listen_fd, SOL_SOCKET, SO_REUSEADDR, &on,
 442             sizeof (on));
 443 
 444         if (bind(*listen_fd, (struct sockaddr *)&sin, sizeof (sin)) == -1) {
 445                 (void) close(*listen_fd);
 446                 return (DHCP_IPC_E_BIND);
 447         }
 448 
 449         if (listen(*listen_fd, DHCP_IPC_LISTEN_BACKLOG) == -1) {
 450                 (void) close(*listen_fd);
 451                 return (DHCP_IPC_E_LISTEN);
 452         }
 453 
 454         return (0);
 455 }
 456 
 457 /*
 458  * dhcp_ipc_accept(): accepts an incoming connection for the agent
 459  *
 460  *   input: int: the file descriptor to accept on
 461  *          int *: the accepted file descriptor (returned)
 462  *          int *: nonzero if the client is privileged (returned)
 463  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 464  *    note: sets the socket into nonblocking mode
 465  */
 466 
 467 int
 468 dhcp_ipc_accept(int listen_fd, int *fd, int *is_priv)
 469 {
 470         struct sockaddr_in      sin_peer;
 471         int                     sin_len = sizeof (sin_peer);
 472         int                     sockflags;
 473 
 474         /*
 475          * if we were extremely concerned with portability, we would
 476          * set the socket into nonblocking mode before doing the
 477          * accept(3N), since on BSD-based networking stacks, there is
 478          * a potential race that can occur if the socket which
 479          * connected to us performs a TCP RST before we accept, since
 480          * BSD handles this case entirely in the kernel and as a
 481          * result even though select said we will not block, we can
 482          * end up blocking since there is no longer a connection to
 483          * accept.  on SVR4-based systems, this should be okay,
 484          * and we will get EPROTO back, even though POSIX.1g says
 485          * we should get ECONNABORTED.
 486          */
 487 
 488         *fd = accept(listen_fd, (struct sockaddr *)&sin_peer, &sin_len);
 489         if (*fd == -1)
 490                 return (DHCP_IPC_E_ACCEPT);
 491 
 492         /* get credentials */
 493         *is_priv = ntohs(sin_peer.sin_port) < IPPORT_RESERVED;
 494 
 495         /*
 496          * kick the socket into non-blocking mode so that later
 497          * operations on the socket don't block and hold up the whole
 498          * application.  with the event demuxing approach, this may
 499          * seem unnecessary, but in order to get partial reads/writes
 500          * and to handle our internal protocol for passing data
 501          * between the agent and its consumers, this is needed.
 502          */
 503 
 504         if ((sockflags = fcntl(*fd, F_GETFL, 0)) == -1) {
 505                 (void) close(*fd);
 506                 return (DHCP_IPC_E_FCNTL);
 507         }
 508 
 509         if (fcntl(*fd, F_SETFL, sockflags | O_NONBLOCK) == -1) {
 510                 (void) close(*fd);
 511                 return (DHCP_IPC_E_FCNTL);
 512         }
 513 
 514         return (0);
 515 }
 516 
 517 /*
 518  * dhcp_ipc_close(): closes an ipc descriptor
 519  *
 520  *   input: int: the file descriptor to close
 521  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
 522  */
 523 
 524 int
 525 dhcp_ipc_close(int fd)
 526 {
 527         return ((close(fd) == -1) ? DHCP_IPC_E_CLOSE : 0);
 528 }
 529 
 530 /*
 531  * dhcp_ipc_strerror(): maps an ipc error code into a human-readable string
 532  *
 533  *   input: int: the ipc error code to map
 534  *  output: const char *: the corresponding human-readable string
 535  */
 536 
 537 const char *
 538 dhcp_ipc_strerror(int error)
 539 {
 540         /* note: this must be kept in sync with DHCP_IPC_E_* definitions */
 541         const char *syscalls[] = {
 542                 "<unknown>", "socket", "fcntl", "read", "accept", "close",
 543                 "bind", "listen", "malloc", "connect", "writev", "poll"
 544         };
 545 
 546         const char      *error_string;
 547         static char     buffer[BUFMAX];
 548 
 549         switch (error) {
 550 
 551         /*
 552          * none of these errors actually go over the wire.
 553          * hence, we assume that errno is still fresh.
 554          */
 555 
 556         case DHCP_IPC_E_SOCKET:                 /* FALLTHRU */
 557         case DHCP_IPC_E_FCNTL:                  /* FALLTHRU */
 558         case DHCP_IPC_E_READ:                   /* FALLTHRU */
 559         case DHCP_IPC_E_ACCEPT:                 /* FALLTHRU */
 560         case DHCP_IPC_E_CLOSE:                  /* FALLTHRU */
 561         case DHCP_IPC_E_BIND:                   /* FALLTHRU */
 562         case DHCP_IPC_E_LISTEN:                 /* FALLTHRU */
 563         case DHCP_IPC_E_CONNECT:                /* FALLTHRU */
 564         case DHCP_IPC_E_WRITEV:                 /* FALLTHRU */
 565         case DHCP_IPC_E_POLL:
 566 
 567                 error_string = strerror(errno);
 568                 if (error_string == NULL)
 569                         error_string = "unknown error";
 570 
 571                 (void) snprintf(buffer, sizeof (buffer), "%s: %s",
 572                     syscalls[error], error_string);
 573 
 574                 error_string = buffer;
 575                 break;
 576 
 577         case DHCP_IPC_E_MEMORY:
 578                 error_string = "out of memory";
 579                 break;
 580 
 581         case DHCP_IPC_E_TIMEOUT:
 582                 error_string = "wait timed out, operation still pending...";
 583                 break;
 584 
 585         case DHCP_IPC_E_INVIF:
 586                 error_string = "interface does not exist or cannot be managed "
 587                     "using DHCP";
 588                 break;
 589 
 590         case DHCP_IPC_E_INT:
 591                 error_string = "internal error (might work later)";
 592                 break;
 593 
 594         case DHCP_IPC_E_PERM:
 595                 error_string = "permission denied";
 596                 break;
 597 
 598         case DHCP_IPC_E_OUTSTATE:
 599                 error_string = "interface not in appropriate state for command";
 600                 break;
 601 
 602         case DHCP_IPC_E_PEND:
 603                 error_string = "interface currently has a pending command "
 604                     "(try later)";
 605                 break;
 606 
 607         case DHCP_IPC_E_BOOTP:
 608                 error_string = "interface is administered with BOOTP, not DHCP";
 609                 break;
 610 
 611         case DHCP_IPC_E_CMD_UNKNOWN:
 612                 error_string = "unknown command";
 613                 break;
 614 
 615         case DHCP_IPC_E_UNKIF:
 616                 error_string = "interface is not under DHCP control";
 617                 break;
 618 
 619         case DHCP_IPC_E_PROTO:
 620                 error_string = "ipc protocol violation";
 621                 break;
 622 
 623         case DHCP_IPC_E_FAILEDIF:
 624                 error_string = "interface is in a FAILED state and must be "
 625                     "manually restarted";
 626                 break;
 627 
 628         case DHCP_IPC_E_NOPRIMARY:
 629                 error_string = "primary interface requested but no primary "
 630                     "interface is set";
 631                 break;
 632 
 633         case DHCP_IPC_E_NOIPIF:
 634                 error_string = "interface currently has no IP address";
 635                 break;
 636 
 637         case DHCP_IPC_E_DOWNIF:
 638                 error_string = "interface is currently down";
 639                 break;
 640 
 641         case DHCP_IPC_E_NOVALUE:
 642                 error_string = "no value was found for this option";
 643                 break;
 644 
 645         case DHCP_IPC_E_RUNNING:
 646                 error_string = "DHCP is already running";
 647                 break;
 648 
 649         case DHCP_IPC_E_SRVFAILED:
 650                 error_string = "DHCP server refused request";
 651                 break;
 652 
 653         case DHCP_IPC_E_EOF:
 654                 error_string = "ipc connection closed";
 655                 break;
 656 
 657         default:
 658                 error_string = "unknown error";
 659                 break;
 660         }
 661 
 662         /*
 663          * TODO: internationalize this error string
 664          */
 665 
 666         return (error_string);
 667 }
 668 
 669 /*
 670  * dhcp_string_to_request(): maps a string into a request code
 671  *
 672  *    input: const char *: the string to map
 673  *   output: dhcp_ipc_type_t: the request code, or -1 if unknown
 674  */
 675 
 676 dhcp_ipc_type_t
 677 dhcp_string_to_request(const char *request)
 678 {
 679         unsigned int    i;
 680 
 681         for (i = 0; i < DHCP_NIPC; i++)
 682                 if (strcmp(ipc_typestr[i], request) == 0)
 683                         return ((dhcp_ipc_type_t)i);
 684 
 685         return ((dhcp_ipc_type_t)-1);
 686 }
 687 
 688 /*
 689  * dhcp_ipc_type_to_string(): maps an ipc command code into a human-readable
 690  *                            string
 691  *
 692  *   input: int: the ipc command code to map
 693  *  output: const char *: the corresponding human-readable string
 694  */
 695 
 696 const char *
 697 dhcp_ipc_type_to_string(dhcp_ipc_type_t type)
 698 {
 699         if (type < 0 || type >= DHCP_NIPC)
 700                 return ("unknown");
 701         else
 702                 return (ipc_typestr[(int)type]);
 703 }
 704 
 705 /*
 706  * getinfo_ifnames(): checks the value of a specified option on a list of
 707  *                    interface names.
 708  *   input: const char *: a list of interface names to query (in order) for
 709  *                        the option; "" queries the primary interface
 710  *          dhcp_optnum_t *: a description of the desired option
 711  *          DHCP_OPT **:  filled in with the (dynamically allocated) value of
 712  *                        the option upon success.
 713  *  output: int: DHCP_IPC_E_* on error, 0 on success or if no value was
 714  *               found but no error occurred either (*result will be NULL)
 715  */
 716 
 717 static int
 718 getinfo_ifnames(const char *ifn, dhcp_optnum_t *optnum, DHCP_OPT **result)
 719 {
 720         dhcp_ipc_request_t      *request;
 721         dhcp_ipc_reply_t        *reply;
 722         char                    *ifnames, *ifnames_head;
 723         DHCP_OPT                *opt;
 724         size_t                  opt_size;
 725         int                     retval = 0;
 726 
 727         *result = NULL;
 728         ifnames_head = ifnames = strdup(ifn);
 729         if (ifnames == NULL)
 730                 return (DHCP_IPC_E_MEMORY);
 731 
 732         request = dhcp_ipc_alloc_request(DHCP_GET_TAG, "", optnum,
 733             sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM);
 734 
 735         if (request == NULL) {
 736                 free(ifnames_head);
 737                 return (DHCP_IPC_E_MEMORY);
 738         }
 739 
 740         ifnames = strtok(ifnames, " ");
 741         if (ifnames == NULL)
 742                 ifnames = "";
 743 
 744         for (; ifnames != NULL; ifnames = strtok(NULL, " ")) {
 745 
 746                 (void) strlcpy(request->ifname, ifnames, LIFNAMSIZ);
 747                 retval = dhcp_ipc_make_request(request, &reply, 0);
 748                 if (retval != 0)
 749                         break;
 750 
 751                 if (reply->return_code == 0) {
 752                         opt = dhcp_ipc_get_data(reply, &opt_size, NULL);
 753                         if (opt_size > 2 && (opt->len == opt_size - 2)) {
 754                                 *result = malloc(opt_size);
 755                                 if (*result == NULL)
 756                                         retval = DHCP_IPC_E_MEMORY;
 757                                 else
 758                                         (void) memcpy(*result, opt, opt_size);
 759 
 760                                 free(reply);
 761                                 break;
 762                         }
 763                 }
 764 
 765                 free(reply);
 766                 if (ifnames[0] == '\0')
 767                         break;
 768         }
 769 
 770         free(request);
 771         free(ifnames_head);
 772 
 773         return (retval);
 774 }
 775 
 776 /*
 777  * get_ifnames(): returns a space-separated list of interface names that
 778  *                match the specified flags
 779  *
 780  *   input: int: flags which must be on in each interface returned
 781  *          int: flags which must be off in each interface returned
 782  *  output: char *: a dynamically-allocated list of interface names, or
 783  *                  NULL upon failure.
 784  */
 785 
 786 static char *
 787 get_ifnames(int flags_on, int flags_off)
 788 {
 789         struct ifconf   ifc;
 790         int             n_ifs, i, sock_fd;
 791         char            *ifnames;
 792 
 793 
 794         sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
 795         if (sock_fd == -1)
 796                 return (NULL);
 797 
 798         if ((ioctl(sock_fd, SIOCGIFNUM, &n_ifs) == -1) || (n_ifs <= 0)) {
 799                 (void) close(sock_fd);
 800                 return (NULL);
 801         }
 802 
 803         ifnames = calloc(1, n_ifs * (LIFNAMSIZ + 1));
 804         ifc.ifc_len = n_ifs * sizeof (struct ifreq);
 805         ifc.ifc_req = calloc(n_ifs, sizeof (struct ifreq));
 806         if (ifc.ifc_req != NULL && ifnames != NULL) {
 807 
 808                 if (ioctl(sock_fd, SIOCGIFCONF, &ifc) == -1) {
 809                         (void) close(sock_fd);
 810                         free(ifnames);
 811                         free(ifc.ifc_req);
 812                         return (NULL);
 813                 }
 814 
 815                 for (i = 0; i < n_ifs; i++) {
 816 
 817                         if (ioctl(sock_fd, SIOCGIFFLAGS, &ifc.ifc_req[i]) == 0)
 818                                 if ((ifc.ifc_req[i].ifr_flags &
 819                                     (flags_on | flags_off)) != flags_on)
 820                                         continue;
 821 
 822                         (void) strcat(ifnames, ifc.ifc_req[i].ifr_name);
 823                         (void) strcat(ifnames, " ");
 824                 }
 825 
 826                 if (strlen(ifnames) > 1)
 827                         ifnames[strlen(ifnames) - 1] = '\0';
 828         }
 829 
 830         (void) close(sock_fd);
 831         free(ifc.ifc_req);
 832         return (ifnames);
 833 }
 834 
 835 /*
 836  * dhcp_ipc_getinfo(): attempts to retrieve a value for the specified DHCP
 837  *                     option; tries primary interface, then all DHCP-owned
 838  *                     interfaces, then INFORMs on the remaining interfaces
 839  *                     (these interfaces are dropped prior to returning).
 840  *   input: dhcp_optnum_t *: a description of the desired option
 841  *          DHCP_OPT **:  filled in with the (dynamically allocated) value of
 842  *                        the option upon success.
 843  *          int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
 844  *                   or DHCP_IPC_WAIT_DEFAULT.
 845  *  output: int: DHCP_IPC_E_* on error, 0 upon success.
 846  */
 847 
 848 int
 849 dhcp_ipc_getinfo(dhcp_optnum_t *optnum, DHCP_OPT **result, int32_t timeout)
 850 {
 851         dhcp_ipc_request_t      *request;
 852         dhcp_ipc_reply_t        *reply;
 853         char                    *ifnames, *ifnames_copy, *ifnames_head;
 854         int                     retval;
 855         time_t                  start_time = time(NULL);
 856 
 857         if (timeout == DHCP_IPC_WAIT_DEFAULT)
 858                 timeout = DHCP_IPC_DEFAULT_WAIT;
 859 
 860         /*
 861          * wait at most 5 seconds for the agent to start.
 862          */
 863 
 864         if (dhcp_start_agent((timeout > 5 || timeout < 0) ? 5 : timeout) == -1)
 865                 return (DHCP_IPC_E_INT);
 866 
 867         /*
 868          * check the primary interface for the option value first.
 869          */
 870 
 871         retval = getinfo_ifnames("", optnum, result);
 872         if ((retval != 0) || (retval == 0 && *result != NULL))
 873                 return (retval);
 874 
 875         /*
 876          * no luck.  get a list of the interfaces under DHCP control
 877          * and perform a GET_TAG on each one.
 878          */
 879 
 880         ifnames = get_ifnames(IFF_DHCPRUNNING, 0);
 881         if (ifnames != NULL && strlen(ifnames) != 0) {
 882                 retval = getinfo_ifnames(ifnames, optnum, result);
 883                 if ((retval != 0) || (retval == 0 && *result != NULL)) {
 884                         free(ifnames);
 885                         return (retval);
 886                 }
 887         }
 888         free(ifnames);
 889 
 890         /*
 891          * still no luck.  retrieve a list of all interfaces on the
 892          * system that could use DHCP but aren't.  send INFORMs out on
 893          * each one. after that, sit in a loop for the next `timeout'
 894          * seconds, trying every second to see if a response for the
 895          * option we want has come in on one of the interfaces.
 896          */
 897 
 898         ifnames = get_ifnames(IFF_UP|IFF_RUNNING, IFF_LOOPBACK|IFF_DHCPRUNNING);
 899         if (ifnames == NULL || strlen(ifnames) == 0) {
 900                 free(ifnames);
 901                 return (DHCP_IPC_E_NOVALUE);
 902         }
 903 
 904         ifnames_head = ifnames_copy = strdup(ifnames);
 905         if (ifnames_copy == NULL) {
 906                 free(ifnames);
 907                 return (DHCP_IPC_E_MEMORY);
 908         }
 909 
 910         request = dhcp_ipc_alloc_request(DHCP_INFORM, "", NULL, 0,
 911             DHCP_TYPE_NONE);
 912         if (request == NULL) {
 913                 free(ifnames);
 914                 free(ifnames_head);
 915                 return (DHCP_IPC_E_MEMORY);
 916         }
 917 
 918         ifnames_copy = strtok(ifnames_copy, " ");
 919         for (; ifnames_copy != NULL; ifnames_copy = strtok(NULL, " ")) {
 920                 (void) strlcpy(request->ifname, ifnames_copy, LIFNAMSIZ);
 921                 if (dhcp_ipc_make_request(request, &reply, 0) == 0)
 922                         free(reply);
 923         }
 924 
 925         for (;;) {
 926                 if ((timeout != DHCP_IPC_WAIT_FOREVER) &&
 927                     (time(NULL) - start_time > timeout)) {
 928                         retval = DHCP_IPC_E_TIMEOUT;
 929                         break;
 930                 }
 931 
 932                 retval = getinfo_ifnames(ifnames, optnum, result);
 933                 if (retval != 0 || (retval == 0 && *result != NULL))
 934                         break;
 935 
 936                 (void) sleep(1);
 937         }
 938 
 939         /*
 940          * drop any interfaces that weren't under DHCP control before
 941          * we got here; this keeps this function more of a black box
 942          * and the behavior more consistent from call to call.
 943          */
 944 
 945         request->message_type = DHCP_DROP;
 946 
 947         ifnames_copy = strcpy(ifnames_head, ifnames);
 948         ifnames_copy = strtok(ifnames_copy, " ");
 949         for (; ifnames_copy != NULL; ifnames_copy = strtok(NULL, " ")) {
 950                 (void) strlcpy(request->ifname, ifnames_copy, LIFNAMSIZ);
 951                 if (dhcp_ipc_make_request(request, &reply, 0) == 0)
 952                         free(reply);
 953         }
 954 
 955         free(request);
 956         free(ifnames_head);
 957         free(ifnames);
 958         return (retval);
 959 }
 960 
 961 /*
 962  * dhcp_ipc_timed_read(): reads from a descriptor using a maximum timeout
 963  *
 964  *   input: int: the file descriptor to read from
 965  *          void *: the buffer to read into
 966  *          unsigned int: the total length of data to read
 967  *          int *: the number of milliseconds to wait; the number of
 968  *                 milliseconds left are returned (-1 is "forever")
 969  *  output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
 970  */
 971 
 972 static int
 973 dhcp_ipc_timed_read(int fd, void *buffer, unsigned int length, int *msec)
 974 {
 975         unsigned int    n_total = 0;
 976         ssize_t         n_read;
 977         struct pollfd   pollfd;
 978         hrtime_t        start, end;
 979         int             retv;
 980 
 981         pollfd.fd       = fd;
 982         pollfd.events   = POLLIN;
 983 
 984         while (n_total < length) {
 985 
 986                 start = gethrtime();
 987 
 988                 retv = poll(&pollfd, 1, *msec);
 989                 if (retv == 0) {
 990                         /* This can happen only if *msec is not -1 */
 991                         *msec = 0;
 992                         return (DHCP_IPC_E_TIMEOUT);
 993                 }
 994 
 995                 if (*msec != -1) {
 996                         end = gethrtime();
 997                         *msec -= NSEC2MSEC(end - start);
 998                         if (*msec < 0)
 999                                 *msec = 0;
1000                 }
1001 
1002                 if (retv == -1) {
1003                         if (errno != EINTR)
1004                                 return (DHCP_IPC_E_POLL);
1005                         else if (*msec == 0)
1006                                 return (DHCP_IPC_E_TIMEOUT);
1007                         continue;
1008                 }
1009 
1010                 if (!(pollfd.revents & POLLIN)) {
1011                         errno = EINVAL;
1012                         return (DHCP_IPC_E_POLL);
1013                 }
1014 
1015                 n_read = read(fd, (caddr_t)buffer + n_total, length - n_total);
1016 
1017                 if (n_read == -1) {
1018                         if (errno != EINTR)
1019                                 return (DHCP_IPC_E_READ);
1020                         else if (*msec == 0)
1021                                 return (DHCP_IPC_E_TIMEOUT);
1022                         continue;
1023                 }
1024 
1025                 if (n_read == 0) {
1026                         return (n_total == 0 ? DHCP_IPC_E_EOF :
1027                             DHCP_IPC_E_PROTO);
1028                 }
1029 
1030                 n_total += n_read;
1031 
1032                 if (*msec == 0 && n_total < length)
1033                         return (DHCP_IPC_E_TIMEOUT);
1034         }
1035 
1036         return (DHCP_IPC_SUCCESS);
1037 }