/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <limits.h>
#include <ctype.h>
#include <libgen.h>
#include <sys/isa_defs.h>
#include <sys/sysmacros.h>
#include <libinetutil.h>
#include <libdlpi.h>
#include "dhcp_symbol.h"
#include "dhcp_inittab.h"
static void inittab_msg(const char *, ...);
static uchar_t category_to_code(const char *);
const char *, uint8_t *, int *);
const uint8_t *, char *, int *);
size_t *);
static boolean_t parse_entry(char *, char **);
/*
* forward declaration of our internal inittab_table[]. too bulky to put
* up front -- check the end of this file for its definition.
*
* Note: we have only an IPv4 version here. The inittab_verify() function is
* used by the DHCP server and manager. We'll need a new function if the
* server is extended to DHCPv6.
*/
/*
* the number of fields in the inittab and names for the fields. note that
* this order is meaningful to parse_entry(); other functions should just
* use them as indexes into the array returned from parse_entry().
*/
ITAB_CAT };
/*
* the category_map_entry_t is used to map the inittab category codes to
* the dsym codes. the reason the codes are different is that the inittab
* needs to have the codes be ORable such that queries can retrieve more
* than one category at a time. this map is also used to map the inittab
* string representation of a category to its numerical code.
*/
typedef struct category_map_entry {
char *cme_name;
};
/*
* inittab_load(): returns all inittab entries with the specified criteria
*
* input: uchar_t: the categories the consumer is interested in
* char: the consumer type of the caller
* size_t *: set to the number of entries returned
* output: dhcp_symbol_t *: an array of dynamically allocated entries
* on success, NULL upon failure
*/
{
}
/*
* inittab_getbyname(): returns an inittab entry with the specified criteria
*
* input: int: the categories the consumer is interested in
* char: the consumer type of the caller
* char *: the name of the inittab entry the consumer wants
* output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure
* on success, NULL upon failure
*/
{
}
/*
* inittab_getbycode(): returns an inittab entry with the specified criteria
*
* input: uchar_t: the categories the consumer is interested in
* char: the consumer type of the caller
* uint16_t: the code of the inittab entry the consumer wants
* output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure
* on success, NULL upon failure
*/
{
}
/*
* inittab_lookup(): returns inittab entries with the specified criteria
*
* input: uchar_t: the categories the consumer is interested in
* char: the consumer type of the caller
* const char *: the name of the entry the caller is interested
* in, or NULL if the caller doesn't care
* int32_t: the code the caller is interested in, or -1 if the
* caller doesn't care
* size_t *: set to the number of entries returned
* output: dhcp_symbol_t *: dynamically allocated dhcp_symbol structures
* on success, NULL upon failure
*/
static dhcp_symbol_t *
{
unsigned long line = 0;
const char *inittab_path;
if (categories & ITAB_CAT_V6) {
if (inittab_path == NULL)
} else {
if (inittab_path == NULL)
}
if (inittab_fp == NULL) {
inittab_msg("inittab_lookup: fopen: %s: %s",
return (NULL);
}
line++;
/*
* make sure the string didn't overflow our buffer
*/
inittab_msg("inittab_lookup: line %li: too long, "
"skipping", line);
continue;
}
/*
* skip `pure comment' lines
*/
for (i = 0; buffer[i] != '\0'; i++)
break;
continue;
/*
* parse the entry out into fields.
*/
inittab_msg("inittab_lookup: line %li: syntax error, "
"skipping", line);
continue;
}
/*
* validate the values in the entries; skip if invalid.
*/
inittab_msg("inittab_lookup: line %li: granularity `%s'"
continue;
}
inittab_msg("inittab_lookup: line %li: maximum `%s' "
continue;
}
DSYM_SUCCESS) {
inittab_msg("inittab_lookup: line %li: type `%s' "
continue;
}
/*
* find out whether this entry of interest to our consumer,
* and if so, throw it onto the set of entries we'll return.
* check categories last since it's the most expensive check.
*/
continue;
continue;
continue;
if ((category_code & categories) == 0)
continue;
/*
* looks like a match. allocate an entry and fill it in
*/
sizeof (dhcp_symbol_t));
/*
* if we run out of memory, might as well return what we can
*/
if (new_entries == NULL) {
inittab_msg("inittab_lookup: ran out of memory "
"allocating dhcp_symbol_t's");
break;
}
}
if (ferror(inittab_fp) != 0) {
inittab_msg("inittab_lookup: error on inittab stream");
}
(void) fclose(inittab_fp);
if (n_entriesp != NULL)
*n_entriesp = n_entries;
return (entries);
}
/*
* parse_entry(): parses an entry out into its constituent fields
*
* input: char *: the entry
* char **: an array of ITAB_FIELDS length which contains
* pointers into the entry on upon return
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
static boolean_t
{
/*
* due to a mistake made long ago, the first and second fields of
* each entry are not separated by a comma, but rather by
* whitespace -- have bufsplit() treat the two fields as one, then
* pull them apart afterwards.
*/
return (B_FALSE);
/*
* pull the first and second fields apart. this is complicated
* since the first field can contain embedded whitespace (so we
* must separate the two fields by the last span of whitespace).
*
* first, find the initial span of whitespace. if there isn't one,
* then the entry is malformed.
*/
return (B_FALSE);
/*
* find the last span of whitespace.
*/
do {
category++;
/*
* NUL-terminate the first byte of the last span of whitespace, so
* that the first field doesn't have any residual trailing
* whitespace.
*/
spacep--;
return (B_FALSE);
*++spacep = '\0';
/*
* remove any whitespace from the fields.
*/
for (i = 0; i < n_fields; i++) {
fields[i]++;
}
return (B_TRUE);
}
/*
* inittab_verify(): verifies that a given inittab entry matches an internal
* definition
*
* input: dhcp_symbol_t *: the inittab entry to verify
* dhcp_symbol_t *: if non-NULL, a place to store the internal
* inittab entry upon return
* output: int: ITAB_FAILURE, ITAB_SUCCESS, or ITAB_UNKNOWN
*
* notes: IPv4 only
*/
int
{
unsigned int i;
continue;
if (internal_ent != NULL)
*internal_ent = inittab_table[i];
return (ITAB_FAILURE);
return (ITAB_SUCCESS);
}
}
return (ITAB_UNKNOWN);
}
/*
* get_hw_type(): interpret ",hwtype" in the input string, as part of a DUID.
* The hwtype string is optional, and must be 0-65535 if
* present.
*
* input: char **: pointer to string pointer
* int *: error return value
* output: int: hardware type, or -1 for empty, or -2 for error.
*/
static int
{
if (*str++ != ',') {
return (-2);
}
return (-1);
}
return (-2);
} else {
return ((int)hwtype);
}
}
/*
* get_mac_addr(): interpret ",macaddr" in the input string, as part of a DUID.
* The 'macaddr' may be a hex string (in any standard format),
* or the name of a physical interface. If an interface name
* is given, then the interface type is extracted as well.
*
* input: const char *: input string
* int *: error return value
* uint16_t *: hardware type output (network byte order)
* int: hardware type input; -1 for empty
* uchar_t *: output buffer for MAC address
* output: int: length of MAC address, or -1 for error
*/
static int
{
int maclen;
char chr;
if (*str != '\0') {
if (*str++ != ',')
goto failed;
maclen = 0;
/*
* Allow MAC addresses with separators matching regexp
* (:|-| *).
*/
continue;
dig = 1;
} else {
goto failed;
}
if (++dig == 2) {
maclen++;
}
}
} else {
DLPI_SUCCESS) {
dlpi_close(dh);
goto failed;
}
dlpi_close(dh);
if (hwtype == -1)
}
}
if (hwtype == -1)
goto failed;
return (maclen);
return (-1);
}
/*
* inittab_encode_e(): converts a string representation of a given datatype into
* binary; used for encoding ascii values into a form that
* can be put in DHCP packets to be sent on the wire.
*
* input: const dhcp_symbol_t *: the entry describing the value option
* const char *: the value to convert
* uint16_t *: set to the length of the binary data returned
* boolean_t: if false, return a full DHCP option
* int *: error return value
* output: uchar_t *: a dynamically allocated byte array with converted data
*/
uchar_t *
{
int hlen = 0;
const char *valuep;
char *currp;
unsigned int i;
int type;
char *cp2;
*ierrnop = 0;
if (type_size == 0) {
return (NULL);
}
case DSYM_ASCII:
break;
case DSYM_OCTET:
break;
case DSYM_DOMAIN:
/*
* Maximum (worst-case) encoded length is one byte more than
* the number of characters on input.
*/
break;
case DSYM_DUID:
/* Worst case is ":::::" */
if (n_entries < DLPI_PHYSADDR_MAX)
n_entries += sizeof (duid_llt_t);
break;
default:
/*
* figure out the number of entries by counting the spaces
* in the value string
*/
break;
}
/*
* if we're gonna return a complete option, then include the
* option length and code in the size of the packet we allocate
*/
if (!just_payload)
case DSYM_ASCII:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
break;
case DSYM_DOMAIN:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
/*
* Note that this encoder always presents the trailing 0-octet
* when dealing with a list. This means that you can't have
* non-fully-qualified members anywhere but at the end of a
* list (or as the only member of the list).
*/
while (*valuep != '\0') {
/*
* Skip over whitespace that delimits list members.
*/
valuep++;
continue;
}
valuep++;
/*
* Just copy non-ASCII text directly to the
* output string. This simplifies the use of
* other ctype macros below, as, unlike the
* special isascii function, they don't handle
* non-ASCII.
*/
continue;
}
if (escape) {
/*
* Handle any of \D, \DD, or \DDD for
* a digit escape.
*/
if (++dig == 3) {
}
continue;
} else if (dig > 0) {
/*
* User terminated \D or \DD
* with non-digit. An error,
* but we can assume he means
* to treat as \00D or \0DD.
*/
}
/* Fall through and copy character */
} else if (inchr == '\\') {
continue;
} else if (inchr == '.') {
/*
* End of component. Write the length
* prefix. If the component is zero
* length (i.e., ".."), the just omit
* it.
*/
if (*flen > 0)
continue;
/*
* Unescaped space; end of domain name
* in list.
*/
break;
}
}
/*
* Handle trailing escape sequence. If string ends
* with \, then assume user wants \ at end of encoded
* string. If it ends with \D or \DD, assume \00D or
* \0DD.
*/
if (escape)
/*
* If user specified FQDN with trailing '.', then above
* will result in zero for the last component length.
* We're done, and optstart already points to the start
* of the next in list. Otherwise, we need to write a
* single zero byte to end the entry, if there are more
* entries that will be decoded.
*/
valuep++;
*optstart++ = '\0';
}
break;
case DSYM_DUID:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
errno = 0;
return (NULL);
}
switch (type) {
case DHCPV6_DUID_LLT: {
int hwtype;
int maclen;
return (NULL);
}
if (*currp++ != ',') {
return (NULL);
}
} else {
return (NULL);
}
}
if (maclen == -1) {
return (NULL);
}
break;
}
case DHCPV6_DUID_EN: {
if (*currp++ != ',') {
return (NULL);
}
return (NULL);
}
if (*cp2 == ',')
cp2++;
return (NULL);
}
break;
}
case DHCPV6_DUID_LL: {
int hwtype;
int maclen;
return (NULL);
}
if (maclen == -1) {
return (NULL);
}
break;
}
default:
if (*currp == ',')
currp++;
&reslen) != 0) {
return (NULL);
}
break;
}
break;
case DSYM_OCTET:
return (NULL);
}
/* Call libinetutil function to decode */
return (NULL);
}
break;
case DSYM_IP:
case DSYM_IPV6:
return (NULL);
}
*ierrnop = ITAB_BAD_GRAN;
inittab_msg("inittab_encode: number of entries "
"not compatible with option granularity");
return (NULL);
}
*currp = '\0';
inittab_msg("inittab_encode: bogus ip address");
return (NULL);
}
if (i < (n_entries - 1)) {
inittab_msg("inittab_encode: too few "
"ip addresses");
return (NULL);
}
break;
}
}
break;
case DSYM_NUMBER: /* FALLTHRU */
case DSYM_UNUMBER8: /* FALLTHRU */
case DSYM_SNUMBER8: /* FALLTHRU */
case DSYM_UNUMBER16: /* FALLTHRU */
case DSYM_SNUMBER16: /* FALLTHRU */
case DSYM_UNUMBER24: /* FALLTHRU */
case DSYM_UNUMBER32: /* FALLTHRU */
case DSYM_SNUMBER32: /* FALLTHRU */
case DSYM_UNUMBER64: /* FALLTHRU */
case DSYM_SNUMBER64:
return (NULL);
}
return (NULL);
}
break;
default:
else
inittab_msg("inittab_encode: unsupported type `%d'",
return (NULL);
}
/*
* if just_payload is false, then we need to add the option
* code and length fields in.
*/
if (!just_payload) {
/* LINTED: alignment */
} else {
}
}
return (result);
}
/*
* inittab_decode_e(): converts a binary representation of a given datatype into
* a string; used for decoding DHCP options in a packet off
* the wire into ascii
*
* input: dhcp_symbol_t *: the entry describing the payload option
* uchar_t *: the payload to convert
* uint16_t: the payload length (only used if just_payload is true)
* boolean_t: if false, payload is assumed to be a DHCP option
* int *: set to extended error code if error occurs.
* output: char *: a dynamically allocated string containing the converted data
*/
char *
{
int type;
*ierrnop = 0;
if (type_size == 0) {
return (NULL);
}
if (!just_payload) {
} else {
payload += 2;
}
}
/*
* figure out the number of elements to convert. note that
* for ds_type NUMBER, the granularity is really 1 since the
* value of ds_gran is the number of bytes in the number.
*/
else
if (n_entries == 0)
inittab_msg("inittab_decode: length of string not compatible "
return (NULL);
}
case DSYM_ASCII:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
break;
case DSYM_DOMAIN:
/*
* A valid, decoded RFC 1035 domain string or sequence of
* strings is always the same size as the encoded form, but we
* allow for RFC 1035 \DDD and \\ and \. escaping.
*
* Decoding stops at the end of the input or the first coding
* violation. Coding violations result in discarding the
* offending list entry entirely. Note that we ignore the 255
* character overall limit on domain names.
*/
*ierrnop = ITAB_NOMEM;
return (NULL);
}
while (length > 0) {
char *dstart;
int slen;
while (length > 0) {
length--;
/* Upper two bits of length must be zero */
length = 0;
break;
}
*resultp++ = '.';
if (slen == 0)
break;
while (slen > 0) {
"\\%03d",
*(unsigned char *)payload);
resultp += 4;
payload++;
} else {
if (*payload == '.' ||
*payload == '\\')
*resultp++ = '\\';
}
slen--;
}
}
*resultp++ = ' ';
}
*resultp = '\0';
break;
case DSYM_DUID:
/*
* First, determine the type of DUID. We need at least two
* octets worth of data to grab the type code. Once we have
* that, the number of octets required for representation
* depends on the type.
*/
if (length < 2) {
*ierrnop = ITAB_BAD_GRAN;
return (NULL);
}
switch (type) {
case DHCPV6_DUID_LLT: {
*ierrnop = ITAB_BAD_GRAN;
return (NULL);
}
n_entries = sizeof ("1,65535,4294967295,") +
length * 3;
*ierrnop = ITAB_NOMEM;
return (NULL);
}
break;
}
case DHCPV6_DUID_EN: {
*ierrnop = ITAB_BAD_GRAN;
return (NULL);
}
*ierrnop = ITAB_NOMEM;
return (NULL);
}
DHCPV6_GET_ENTNUM(&den));
break;
}
case DHCPV6_DUID_LL: {
*ierrnop = ITAB_BAD_GRAN;
return (NULL);
}
*ierrnop = ITAB_NOMEM;
return (NULL);
}
break;
}
default:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
break;
}
if (length > 0) {
*payload++);
length--;
}
while (length-- > 0) {
*payload++);
}
} else {
while (length-- > 0) {
*payload++);
}
}
break;
case DSYM_OCTET:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
result[0] = '\0';
if (n_entries > 0) {
n_entries--;
}
while (n_entries-- > 0)
break;
case DSYM_IP:
case DSYM_IPV6:
*ierrnop = ITAB_BAD_GRAN;
inittab_msg("inittab_decode: number of entries "
"not compatible with option granularity");
return (NULL);
}
*ierrnop = ITAB_NOMEM;
return (NULL);
}
sizeof (ipaddr_t));
} else {
sizeof (in6_addr));
}
if (n_entries > 1)
*resultp++ = ' ';
}
*resultp = '\0';
break;
case DSYM_NUMBER: /* FALLTHRU */
case DSYM_UNUMBER8: /* FALLTHRU */
case DSYM_SNUMBER8: /* FALLTHRU */
case DSYM_UNUMBER16: /* FALLTHRU */
case DSYM_SNUMBER16: /* FALLTHRU */
case DSYM_UNUMBER32: /* FALLTHRU */
case DSYM_SNUMBER32: /* FALLTHRU */
case DSYM_UNUMBER64: /* FALLTHRU */
case DSYM_SNUMBER64:
*ierrnop = ITAB_NOMEM;
return (NULL);
}
return (NULL);
}
break;
default:
inittab_msg("inittab_decode: unsupported type `%d'",
break;
}
return (result);
}
/*
* inittab_encode(): converts a string representation of a given datatype into
* binary; used for encoding ascii values into a form that
* can be put in DHCP packets to be sent on the wire.
*
* input: dhcp_symbol_t *: the entry describing the value option
* const char *: the value to convert
* uint16_t *: set to the length of the binary data returned
* boolean_t: if false, return a full DHCP option
* output: uchar_t *: a dynamically allocated byte array with converted data
*/
uchar_t *
{
int ierrno;
}
/*
* inittab_decode(): converts a binary representation of a given datatype into
* a string; used for decoding DHCP options in a packet off
* the wire into ascii
*
* input: dhcp_symbol_t *: the entry describing the payload option
* uchar_t *: the payload to convert
* uint16_t: the payload length (only used if just_payload is true)
* boolean_t: if false, payload is assumed to be a DHCP option
* output: char *: a dynamically allocated string containing the converted data
*/
char *
{
int ierrno;
}
/*
* inittab_msg(): prints diagnostic messages if INITTAB_DEBUG is set
*
* const char *: a printf-like format string
* ...: arguments to the format string
* output: void
*/
/*PRINTFLIKE1*/
static void
{
/*
* check DHCP_INITTAB_DEBUG the first time in; thereafter, use
* the the cached result (stored in `action').
*/
switch (action) {
case INITTAB_MSG_CHECK:
return;
}
/* FALLTHRU into INITTAB_MSG_OUTPUT */
case INITTAB_MSG_OUTPUT:
break;
case INITTAB_MSG_RETURN:
return;
}
}
/*
* decode_number(): decodes a sequence of numbers from binary into ascii;
* binary is coming off of the network, so it is in nbo
*
* input: uint8_t: the number of "granularity" numbers to decode
* uint8_t: the length of each number
* boolean_t: whether the numbers should be considered signed
* uint8_t: the number of numbers per granularity
* const uint8_t *: where to decode the numbers from
* char *: where to decode the numbers to
* output: boolean_t: true on successful conversion, false on failure
*/
static boolean_t
{
if (granularity != 0) {
if ((granularity % n_entries) != 0) {
inittab_msg("decode_number: number of entries "
"not compatible with option granularity");
*ierrnop = ITAB_BAD_GRAN;
return (B_FALSE);
}
}
switch (size) {
case 1:
break;
case 2:
break;
case 3:
uint32 = 0;
break;
case 4:
break;
case 8:
break;
default:
inittab_msg("decode_number: unknown integer size `%d'",
size);
return (B_FALSE);
}
if (n_entries > 0)
*to++ = ' ';
}
*to = '\0';
return (B_TRUE);
}
/*
* encode_number(): encodes a sequence of numbers from ascii into binary;
* number will end up on the wire so it needs to be in nbo
*
* input: uint8_t: the number of "granularity" numbers to encode
* uint8_t: the length of each number
* boolean_t: whether the numbers should be considered signed
* uint8_t: the number of numbers per granularity
* const uint8_t *: where to encode the numbers from
* char *: where to encode the numbers to
* int *: set to extended error code if error occurs.
* output: boolean_t: true on successful conversion, false on failure
*/
static boolean_t /* ARGSUSED */
{
uint8_t i;
char *endptr;
if (granularity != 0) {
if ((granularity % n_entries) != 0) {
*ierrnop = ITAB_BAD_GRAN;
inittab_msg("encode_number: number of entries "
"not compatible with option granularity");
return (B_FALSE);
}
}
/*
* totally obscure c factoid: it is legal to pass a
* string representing a negative number to strtoul().
* in this case, strtoul() will return an unsigned
* long that if cast to a long, would represent the
* negative number. we take advantage of this to
* cut down on code here.
*/
errno = 0;
switch (size) {
case 1:
goto error;
}
break;
case 2:
goto error;
}
break;
case 3:
goto error;
}
break;
case 4:
goto error;
}
break;
case 8:
goto error;
}
break;
default:
inittab_msg("encode_number: unsupported integer "
"size `%d'", size);
return (B_FALSE);
}
break;
}
return (B_TRUE);
inittab_msg("encode_number: cannot convert to integer");
return (B_FALSE);
}
/*
* inittab_type_to_size(): given an inittab entry, returns size of one entry of
* its type
*
* input: dhcp_symbol_t *: an entry of the given type
* output: uint8_t: the size in bytes of an entry of that type
*/
{
case DSYM_DUID:
case DSYM_DOMAIN:
case DSYM_ASCII:
case DSYM_OCTET:
case DSYM_SNUMBER8:
case DSYM_UNUMBER8:
return (1);
case DSYM_SNUMBER16:
case DSYM_UNUMBER16:
return (2);
case DSYM_UNUMBER24:
return (3);
case DSYM_SNUMBER32:
case DSYM_UNUMBER32:
case DSYM_IP:
return (4);
case DSYM_SNUMBER64:
case DSYM_UNUMBER64:
return (8);
case DSYM_NUMBER:
case DSYM_IPV6:
return (sizeof (in6_addr_t));
}
return (0);
}
/*
* itabcode_to_dsymcode(): maps an inittab category code to its dsym
* representation
*
* input: uchar_t: the inittab category code
* output: dsym_category_t: the dsym category code
*/
static dsym_category_t
{
unsigned int i;
for (i = 0; i < ITAB_CAT_COUNT; i++)
return (category_map[i].cme_dsymcode);
return (DSYM_BAD_CAT);
}
/*
* category_to_code(): maps a category name to its numeric representation
*
* input: const char *: the category name
* output: uchar_t: its internal code (numeric representation)
*/
static uchar_t
{
unsigned int i;
for (i = 0; i < ITAB_CAT_COUNT; i++)
return (category_map[i].cme_itabcode);
return (0);
}
/*
* our internal table of DHCP option values, used by inittab_verify()
*/
{
{ 0, 0, "", 0, 0, 0 }
};