2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <sys/types.h>
2N/A#include <string.h>
2N/A#include <stdlib.h>
2N/A#include <stdio.h>
2N/A#include <errno.h>
2N/A#include <stdarg.h>
2N/A#include <limits.h>
2N/A#include <ctype.h>
2N/A#include <libgen.h>
2N/A#include <sys/isa_defs.h>
2N/A#include <sys/socket.h>
2N/A#include <net/if_arp.h>
2N/A#include <netinet/in.h>
2N/A#include <arpa/inet.h>
2N/A#include <sys/sysmacros.h>
2N/A#include <libinetutil.h>
2N/A#include <libdlpi.h>
2N/A#include <netinet/dhcp6.h>
2N/A
2N/A#include "dhcp_symbol.h"
2N/A#include "dhcp_inittab.h"
2N/A
2N/Astatic void inittab_msg(const char *, ...);
2N/Astatic uchar_t category_to_code(const char *);
2N/Astatic boolean_t encode_number(uint8_t, uint8_t, boolean_t, uint8_t,
2N/A const char *, uint8_t *, int *);
2N/Astatic boolean_t decode_number(uint8_t, uint8_t, boolean_t, uint8_t,
2N/A const uint8_t *, char *, int *);
2N/Astatic dhcp_symbol_t *inittab_lookup(uchar_t, char, const char *, int32_t,
2N/A size_t *);
2N/Astatic dsym_category_t itabcode_to_dsymcode(uchar_t);
2N/Astatic boolean_t parse_entry(char *, char **);
2N/A
2N/A/*
2N/A * forward declaration of our internal inittab_table[]. too bulky to put
2N/A * up front -- check the end of this file for its definition.
2N/A *
2N/A * Note: we have only an IPv4 version here. The inittab_verify() function is
2N/A * used by the DHCP server and manager. We'll need a new function if the
2N/A * server is extended to DHCPv6.
2N/A */
2N/Astatic dhcp_symbol_t inittab_table[];
2N/A
2N/A/*
2N/A * the number of fields in the inittab and names for the fields. note that
2N/A * this order is meaningful to parse_entry(); other functions should just
2N/A * use them as indexes into the array returned from parse_entry().
2N/A */
2N/A#define ITAB_FIELDS 7
2N/Aenum { ITAB_NAME, ITAB_CODE, ITAB_TYPE, ITAB_GRAN, ITAB_MAX, ITAB_CONS,
2N/A ITAB_CAT };
2N/A
2N/A/*
2N/A * the category_map_entry_t is used to map the inittab category codes to
2N/A * the dsym codes. the reason the codes are different is that the inittab
2N/A * needs to have the codes be ORable such that queries can retrieve more
2N/A * than one category at a time. this map is also used to map the inittab
2N/A * string representation of a category to its numerical code.
2N/A */
2N/Atypedef struct category_map_entry {
2N/A dsym_category_t cme_dsymcode;
2N/A char *cme_name;
2N/A uchar_t cme_itabcode;
2N/A} category_map_entry_t;
2N/A
2N/Astatic category_map_entry_t category_map[] = {
2N/A { DSYM_STANDARD, "STANDARD", ITAB_CAT_STANDARD },
2N/A { DSYM_FIELD, "FIELD", ITAB_CAT_FIELD },
2N/A { DSYM_INTERNAL, "INTERNAL", ITAB_CAT_INTERNAL },
2N/A { DSYM_VENDOR, "VENDOR", ITAB_CAT_VENDOR },
2N/A { DSYM_SITE, "SITE", ITAB_CAT_SITE }
2N/A};
2N/A
2N/Astatic size_t category_map_nent =
2N/A sizeof (category_map) / sizeof (category_map[0]);
2N/A
2N/A/*
2N/A * inittab_load(): returns all inittab entries with the specified criteria
2N/A *
2N/A * input: uchar_t: the categories the consumer is interested in
2N/A * char: the consumer type of the caller
2N/A * size_t *: set to the number of entries returned
2N/A * output: dhcp_symbol_t *: an array of dynamically allocated entries
2N/A * on success, NULL upon failure
2N/A */
2N/A
2N/Adhcp_symbol_t *
2N/Ainittab_load(uchar_t categories, char consumer, size_t *n_entries)
2N/A{
2N/A return (inittab_lookup(categories, consumer, NULL, -1, n_entries));
2N/A}
2N/A
2N/A/*
2N/A * inittab_getbyname(): returns an inittab entry with the specified criteria
2N/A *
2N/A * input: int: the categories the consumer is interested in
2N/A * char: the consumer type of the caller
2N/A * char *: the name of the inittab entry the consumer wants
2N/A * output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure
2N/A * on success, NULL upon failure
2N/A */
2N/A
2N/Adhcp_symbol_t *
2N/Ainittab_getbyname(uchar_t categories, char consumer, const char *name)
2N/A{
2N/A return (inittab_lookup(categories, consumer, name, -1, NULL));
2N/A}
2N/A
2N/A/*
2N/A * inittab_getbycode(): returns an inittab entry with the specified criteria
2N/A *
2N/A * input: uchar_t: the categories the consumer is interested in
2N/A * char: the consumer type of the caller
2N/A * uint16_t: the code of the inittab entry the consumer wants
2N/A * output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure
2N/A * on success, NULL upon failure
2N/A */
2N/A
2N/Adhcp_symbol_t *
2N/Ainittab_getbycode(uchar_t categories, char consumer, uint16_t code)
2N/A{
2N/A return (inittab_lookup(categories, consumer, NULL, code, NULL));
2N/A}
2N/A
2N/A/*
2N/A * inittab_lookup(): returns inittab entries with the specified criteria
2N/A *
2N/A * input: uchar_t: the categories the consumer is interested in
2N/A * char: the consumer type of the caller
2N/A * const char *: the name of the entry the caller is interested
2N/A * in, or NULL if the caller doesn't care
2N/A * int32_t: the code the caller is interested in, or -1 if the
2N/A * caller doesn't care
2N/A * size_t *: set to the number of entries returned
2N/A * output: dhcp_symbol_t *: dynamically allocated dhcp_symbol structures
2N/A * on success, NULL upon failure
2N/A */
2N/A
2N/Astatic dhcp_symbol_t *
2N/Ainittab_lookup(uchar_t categories, char consumer, const char *name,
2N/A int32_t code, size_t *n_entriesp)
2N/A{
2N/A FILE *inittab_fp;
2N/A dhcp_symbol_t *new_entries, *entries = NULL;
2N/A dhcp_symbol_t entry;
2N/A char buffer[ITAB_MAX_LINE_LEN];
2N/A char *fields[ITAB_FIELDS];
2N/A unsigned long line = 0;
2N/A size_t i, n_entries = 0;
2N/A const char *inittab_path;
2N/A uchar_t category_code;
2N/A dsym_cdtype_t type;
2N/A
2N/A if (categories & ITAB_CAT_V6) {
2N/A inittab_path = getenv("DHCP_INITTAB6_PATH");
2N/A if (inittab_path == NULL)
2N/A inittab_path = ITAB_INITTAB6_PATH;
2N/A } else {
2N/A inittab_path = getenv("DHCP_INITTAB_PATH");
2N/A if (inittab_path == NULL)
2N/A inittab_path = ITAB_INITTAB_PATH;
2N/A }
2N/A
2N/A inittab_fp = fopen(inittab_path, "r");
2N/A if (inittab_fp == NULL) {
2N/A inittab_msg("inittab_lookup: fopen: %s: %s",
2N/A inittab_path, strerror(errno));
2N/A return (NULL);
2N/A }
2N/A
2N/A (void) bufsplit(",\n", 0, NULL);
2N/A while (fgets(buffer, sizeof (buffer), inittab_fp) != NULL) {
2N/A
2N/A line++;
2N/A
2N/A /*
2N/A * make sure the string didn't overflow our buffer
2N/A */
2N/A if (strchr(buffer, '\n') == NULL) {
2N/A inittab_msg("inittab_lookup: line %li: too long, "
2N/A "skipping", line);
2N/A continue;
2N/A }
2N/A
2N/A /*
2N/A * skip `pure comment' lines
2N/A */
2N/A for (i = 0; buffer[i] != '\0'; i++)
2N/A if (isspace(buffer[i]) == 0)
2N/A break;
2N/A
2N/A if (buffer[i] == ITAB_COMMENT_CHAR || buffer[i] == '\0')
2N/A continue;
2N/A
2N/A /*
2N/A * parse the entry out into fields.
2N/A */
2N/A if (parse_entry(buffer, fields) == B_FALSE) {
2N/A inittab_msg("inittab_lookup: line %li: syntax error, "
2N/A "skipping", line);
2N/A continue;
2N/A }
2N/A
2N/A /*
2N/A * validate the values in the entries; skip if invalid.
2N/A */
2N/A if (atoi(fields[ITAB_GRAN]) > ITAB_GRAN_MAX) {
2N/A inittab_msg("inittab_lookup: line %li: granularity `%s'"
2N/A " out of range, skipping", line, fields[ITAB_GRAN]);
2N/A continue;
2N/A }
2N/A
2N/A if (atoi(fields[ITAB_MAX]) > ITAB_MAX_MAX) {
2N/A inittab_msg("inittab_lookup: line %li: maximum `%s' "
2N/A "out of range, skipping", line, fields[ITAB_MAX]);
2N/A continue;
2N/A }
2N/A
2N/A if (dsym_get_type_id(fields[ITAB_TYPE], &type, B_FALSE) !=
2N/A DSYM_SUCCESS) {
2N/A inittab_msg("inittab_lookup: line %li: type `%s' "
2N/A "is invalid, skipping", line, fields[ITAB_TYPE]);
2N/A continue;
2N/A }
2N/A
2N/A /*
2N/A * find out whether this entry of interest to our consumer,
2N/A * and if so, throw it onto the set of entries we'll return.
2N/A * check categories last since it's the most expensive check.
2N/A */
2N/A if (strchr(fields[ITAB_CONS], consumer) == NULL)
2N/A continue;
2N/A
2N/A if (code != -1 && atoi(fields[ITAB_CODE]) != code)
2N/A continue;
2N/A
2N/A if (name != NULL && strcasecmp(fields[ITAB_NAME], name) != 0)
2N/A continue;
2N/A
2N/A category_code = category_to_code(fields[ITAB_CAT]);
2N/A if ((category_code & categories) == 0)
2N/A continue;
2N/A
2N/A /*
2N/A * looks like a match. allocate an entry and fill it in
2N/A */
2N/A new_entries = realloc(entries, (n_entries + 1) *
2N/A sizeof (dhcp_symbol_t));
2N/A
2N/A /*
2N/A * if we run out of memory, might as well return what we can
2N/A */
2N/A if (new_entries == NULL) {
2N/A inittab_msg("inittab_lookup: ran out of memory "
2N/A "allocating dhcp_symbol_t's");
2N/A break;
2N/A }
2N/A
2N/A entry.ds_max = atoi(fields[ITAB_MAX]);
2N/A entry.ds_code = atoi(fields[ITAB_CODE]);
2N/A entry.ds_type = type;
2N/A entry.ds_gran = atoi(fields[ITAB_GRAN]);
2N/A entry.ds_category = itabcode_to_dsymcode(category_code);
2N/A entry.ds_classes.dc_cnt = 0;
2N/A entry.ds_classes.dc_names = NULL;
2N/A (void) strlcpy(entry.ds_name, fields[ITAB_NAME],
2N/A sizeof (entry.ds_name));
2N/A entry.ds_dhcpv6 = (categories & ITAB_CAT_V6) ? 1 : 0;
2N/A
2N/A entries = new_entries;
2N/A entries[n_entries++] = entry;
2N/A }
2N/A
2N/A if (ferror(inittab_fp) != 0) {
2N/A inittab_msg("inittab_lookup: error on inittab stream");
2N/A clearerr(inittab_fp);
2N/A }
2N/A
2N/A (void) fclose(inittab_fp);
2N/A
2N/A if (n_entriesp != NULL)
2N/A *n_entriesp = n_entries;
2N/A
2N/A return (entries);
2N/A}
2N/A
2N/A/*
2N/A * parse_entry(): parses an entry out into its constituent fields
2N/A *
2N/A * input: char *: the entry
2N/A * char **: an array of ITAB_FIELDS length which contains
2N/A * pointers into the entry on upon return
2N/A * output: boolean_t: B_TRUE on success, B_FALSE on failure
2N/A */
2N/A
2N/Astatic boolean_t
2N/Aparse_entry(char *entry, char **fields)
2N/A{
2N/A char *category, *spacep;
2N/A size_t n_fields, i;
2N/A
2N/A /*
2N/A * due to a mistake made long ago, the first and second fields of
2N/A * each entry are not separated by a comma, but rather by
2N/A * whitespace -- have bufsplit() treat the two fields as one, then
2N/A * pull them apart afterwards.
2N/A */
2N/A n_fields = bufsplit(entry, ITAB_FIELDS - 1, fields);
2N/A if (n_fields != (ITAB_FIELDS - 1))
2N/A return (B_FALSE);
2N/A
2N/A /*
2N/A * pull the first and second fields apart. this is complicated
2N/A * since the first field can contain embedded whitespace (so we
2N/A * must separate the two fields by the last span of whitespace).
2N/A *
2N/A * first, find the initial span of whitespace. if there isn't one,
2N/A * then the entry is malformed.
2N/A */
2N/A category = strpbrk(fields[ITAB_NAME], " \t");
2N/A if (category == NULL)
2N/A return (B_FALSE);
2N/A
2N/A /*
2N/A * find the last span of whitespace.
2N/A */
2N/A do {
2N/A while (isspace(*category))
2N/A category++;
2N/A
2N/A spacep = strpbrk(category, " \t");
2N/A if (spacep != NULL)
2N/A category = spacep;
2N/A } while (spacep != NULL);
2N/A
2N/A /*
2N/A * NUL-terminate the first byte of the last span of whitespace, so
2N/A * that the first field doesn't have any residual trailing
2N/A * whitespace.
2N/A */
2N/A spacep = category - 1;
2N/A while (isspace(*spacep))
2N/A spacep--;
2N/A
2N/A if (spacep <= fields[0])
2N/A return (B_FALSE);
2N/A
2N/A *++spacep = '\0';
2N/A
2N/A /*
2N/A * remove any whitespace from the fields.
2N/A */
2N/A for (i = 0; i < n_fields; i++) {
2N/A while (isspace(*fields[i]))
2N/A fields[i]++;
2N/A }
2N/A fields[ITAB_CAT] = category;
2N/A
2N/A return (B_TRUE);
2N/A}
2N/A
2N/A/*
2N/A * inittab_verify(): verifies that a given inittab entry matches an internal
2N/A * definition
2N/A *
2N/A * input: dhcp_symbol_t *: the inittab entry to verify
2N/A * dhcp_symbol_t *: if non-NULL, a place to store the internal
2N/A * inittab entry upon return
2N/A * output: int: ITAB_FAILURE, ITAB_SUCCESS, or ITAB_UNKNOWN
2N/A *
2N/A * notes: IPv4 only
2N/A */
2N/A
2N/Aint
2N/Ainittab_verify(const dhcp_symbol_t *inittab_ent, dhcp_symbol_t *internal_ent)
2N/A{
2N/A unsigned int i;
2N/A
2N/A for (i = 0; inittab_table[i].ds_name[0] != '\0'; i++) {
2N/A
2N/A if (inittab_ent->ds_category != inittab_table[i].ds_category)
2N/A continue;
2N/A
2N/A if (inittab_ent->ds_code == inittab_table[i].ds_code) {
2N/A if (internal_ent != NULL)
2N/A *internal_ent = inittab_table[i];
2N/A
2N/A if (inittab_table[i].ds_type != inittab_ent->ds_type ||
2N/A inittab_table[i].ds_gran != inittab_ent->ds_gran ||
2N/A inittab_table[i].ds_max != inittab_ent->ds_max)
2N/A return (ITAB_FAILURE);
2N/A
2N/A return (ITAB_SUCCESS);
2N/A }
2N/A }
2N/A
2N/A return (ITAB_UNKNOWN);
2N/A}
2N/A
2N/A/*
2N/A * get_hw_type(): interpret ",hwtype" in the input string, as part of a DUID.
2N/A * The hwtype string is optional, and must be 0-65535 if
2N/A * present.
2N/A *
2N/A * input: char **: pointer to string pointer
2N/A * int *: error return value
2N/A * output: int: hardware type, or -1 for empty, or -2 for error.
2N/A */
2N/A
2N/Astatic int
2N/Aget_hw_type(char **strp, int *ierrnop)
2N/A{
2N/A char *str = *strp;
2N/A ulong_t hwtype;
2N/A
2N/A if (*str++ != ',') {
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (-2);
2N/A }
2N/A if (*str == ',' || *str == '\0') {
2N/A *strp = str;
2N/A return (-1);
2N/A }
2N/A hwtype = strtoul(str, strp, 0);
2N/A if (errno != 0 || *strp == str || hwtype > 65535) {
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (-2);
2N/A } else {
2N/A return ((int)hwtype);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * get_mac_addr(): interpret ",macaddr" in the input string, as part of a DUID.
2N/A * The 'macaddr' may be a hex string (in any standard format),
2N/A * or the name of a physical interface. If an interface name
2N/A * is given, then the interface type is extracted as well.
2N/A *
2N/A * input: const char *: input string
2N/A * int *: error return value
2N/A * uint16_t *: hardware type output (network byte order)
2N/A * int: hardware type input; -1 for empty
2N/A * uchar_t *: output buffer for MAC address
2N/A * output: int: length of MAC address, or -1 for error
2N/A */
2N/A
2N/Astatic int
2N/Aget_mac_addr(const char *str, int *ierrnop, uint16_t *hwret, int hwtype,
2N/A uchar_t *outbuf)
2N/A{
2N/A int maclen;
2N/A int dig, val;
2N/A dlpi_handle_t dh;
2N/A dlpi_info_t dlinfo;
2N/A char chr;
2N/A
2N/A if (*str != '\0') {
2N/A if (*str++ != ',')
2N/A goto failed;
2N/A if (dlpi_open(str, &dh, 0) != DLPI_SUCCESS) {
2N/A maclen = 0;
2N/A dig = val = 0;
2N/A /*
2N/A * Allow MAC addresses with separators matching regexp
2N/A * (:|-| *).
2N/A */
2N/A while ((chr = *str++) != '\0') {
2N/A if (isdigit(chr)) {
2N/A val = (val << 4) + chr - '0';
2N/A } else if (isxdigit(chr)) {
2N/A val = (val << 4) + chr -
2N/A (isupper(chr) ? 'A' : 'a') + 10;
2N/A } else if (isspace(chr) && dig == 0) {
2N/A continue;
2N/A } else if (chr == ':' || chr == '-' ||
2N/A isspace(chr)) {
2N/A dig = 1;
2N/A } else {
2N/A goto failed;
2N/A }
2N/A if (++dig == 2) {
2N/A *outbuf++ = val;
2N/A maclen++;
2N/A dig = val = 0;
2N/A }
2N/A }
2N/A } else {
2N/A if (dlpi_info(dh, &dlinfo, DLPI_INFO_VERSION) !=
2N/A DLPI_SUCCESS) {
2N/A dlpi_close(dh);
2N/A goto failed;
2N/A }
2N/A maclen = dlinfo.di_physaddrlen;
2N/A (void) memcpy(outbuf, dlinfo.di_physaddr, maclen);
2N/A dlpi_close(dh);
2N/A if (hwtype == -1)
2N/A hwtype = dlpi_arptype(dlinfo.di_mactype);
2N/A }
2N/A }
2N/A if (hwtype == -1)
2N/A goto failed;
2N/A *hwret = htons(hwtype);
2N/A return (maclen);
2N/A
2N/Afailed:
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (-1);
2N/A}
2N/A
2N/A/*
2N/A * inittab_encode_e(): converts a string representation of a given datatype into
2N/A * binary; used for encoding ascii values into a form that
2N/A * can be put in DHCP packets to be sent on the wire.
2N/A *
2N/A * input: const dhcp_symbol_t *: the entry describing the value option
2N/A * const char *: the value to convert
2N/A * uint16_t *: set to the length of the binary data returned
2N/A * boolean_t: if false, return a full DHCP option
2N/A * int *: error return value
2N/A * output: uchar_t *: a dynamically allocated byte array with converted data
2N/A */
2N/A
2N/Auchar_t *
2N/Ainittab_encode_e(const dhcp_symbol_t *ie, const char *value, uint16_t *lengthp,
2N/A boolean_t just_payload, int *ierrnop)
2N/A{
2N/A int hlen = 0;
2N/A uint16_t length;
2N/A uchar_t n_entries = 0;
2N/A const char *valuep;
2N/A char *currp;
2N/A uchar_t *result = NULL;
2N/A uchar_t *optstart;
2N/A unsigned int i;
2N/A uint8_t type_size = inittab_type_to_size(ie);
2N/A boolean_t is_signed;
2N/A uint_t vallen, reslen;
2N/A dhcpv6_option_t *d6o;
2N/A int type;
2N/A char *cp2;
2N/A
2N/A *ierrnop = 0;
2N/A if (type_size == 0) {
2N/A *ierrnop = ITAB_SYNTAX_ERROR;
2N/A return (NULL);
2N/A }
2N/A
2N/A switch (ie->ds_type) {
2N/A case DSYM_ASCII:
2N/A n_entries = strlen(value); /* no NUL */
2N/A break;
2N/A
2N/A case DSYM_OCTET:
2N/A vallen = strlen(value);
2N/A n_entries = vallen / 2;
2N/A n_entries += vallen % 2;
2N/A break;
2N/A
2N/A case DSYM_DOMAIN:
2N/A /*
2N/A * Maximum (worst-case) encoded length is one byte more than
2N/A * the number of characters on input.
2N/A */
2N/A n_entries = strlen(value) + 1;
2N/A break;
2N/A
2N/A case DSYM_DUID:
2N/A /* Worst case is ":::::" */
2N/A n_entries = strlen(value);
2N/A if (n_entries < DLPI_PHYSADDR_MAX)
2N/A n_entries = DLPI_PHYSADDR_MAX;
2N/A n_entries += sizeof (duid_llt_t);
2N/A break;
2N/A
2N/A default:
2N/A /*
2N/A * figure out the number of entries by counting the spaces
2N/A * in the value string
2N/A */
2N/A for (valuep = value; valuep++ != NULL; n_entries++)
2N/A valuep = strchr(valuep, ' ');
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * if we're gonna return a complete option, then include the
2N/A * option length and code in the size of the packet we allocate
2N/A */
2N/A if (!just_payload)
2N/A hlen = ie->ds_dhcpv6 ? sizeof (*d6o) : 2;
2N/A
2N/A length = n_entries * type_size;
2N/A if (hlen + length > 0)
2N/A result = malloc(hlen + length);
2N/A
2N/A if ((optstart = result) != NULL && !just_payload)
2N/A optstart += hlen;
2N/A
2N/A switch (ie->ds_type) {
2N/A
2N/A case DSYM_ASCII:
2N/A
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A (void) memcpy(optstart, value, length);
2N/A break;
2N/A
2N/A case DSYM_DOMAIN:
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Note that this encoder always presents the trailing 0-octet
2N/A * when dealing with a list. This means that you can't have
2N/A * non-fully-qualified members anywhere but at the end of a
2N/A * list (or as the only member of the list).
2N/A */
2N/A valuep = value;
2N/A while (*valuep != '\0') {
2N/A int dig, val, inchr;
2N/A boolean_t escape;
2N/A uchar_t *flen;
2N/A
2N/A /*
2N/A * Skip over whitespace that delimits list members.
2N/A */
2N/A if (isascii(*valuep) && isspace(*valuep)) {
2N/A valuep++;
2N/A continue;
2N/A }
2N/A dig = val = 0;
2N/A escape = B_FALSE;
2N/A flen = optstart++;
2N/A while ((inchr = *valuep) != '\0') {
2N/A valuep++;
2N/A /*
2N/A * Just copy non-ASCII text directly to the
2N/A * output string. This simplifies the use of
2N/A * other ctype macros below, as, unlike the
2N/A * special isascii function, they don't handle
2N/A * non-ASCII.
2N/A */
2N/A if (!isascii(inchr)) {
2N/A escape = B_FALSE;
2N/A *optstart++ = inchr;
2N/A continue;
2N/A }
2N/A if (escape) {
2N/A /*
2N/A * Handle any of \D, \DD, or \DDD for
2N/A * a digit escape.
2N/A */
2N/A if (isdigit(inchr)) {
2N/A val = val * 10 + inchr - '0';
2N/A if (++dig == 3) {
2N/A *optstart++ = val;
2N/A dig = val = 0;
2N/A escape = B_FALSE;
2N/A }
2N/A continue;
2N/A } else if (dig > 0) {
2N/A /*
2N/A * User terminated \D or \DD
2N/A * with non-digit. An error,
2N/A * but we can assume he means
2N/A * to treat as \00D or \0DD.
2N/A */
2N/A *optstart++ = val;
2N/A dig = val = 0;
2N/A }
2N/A /* Fall through and copy character */
2N/A escape = B_FALSE;
2N/A } else if (inchr == '\\') {
2N/A escape = B_TRUE;
2N/A continue;
2N/A } else if (inchr == '.') {
2N/A /*
2N/A * End of component. Write the length
2N/A * prefix. If the component is zero
2N/A * length (i.e., ".."), the just omit
2N/A * it.
2N/A */
2N/A *flen = (optstart - flen) - 1;
2N/A if (*flen > 0)
2N/A flen = optstart++;
2N/A continue;
2N/A } else if (isspace(inchr)) {
2N/A /*
2N/A * Unescaped space; end of domain name
2N/A * in list.
2N/A */
2N/A break;
2N/A }
2N/A *optstart++ = inchr;
2N/A }
2N/A /*
2N/A * Handle trailing escape sequence. If string ends
2N/A * with \, then assume user wants \ at end of encoded
2N/A * string. If it ends with \D or \DD, assume \00D or
2N/A * \0DD.
2N/A */
2N/A if (escape)
2N/A *optstart++ = dig > 0 ? val : '\\';
2N/A *flen = (optstart - flen) - 1;
2N/A /*
2N/A * If user specified FQDN with trailing '.', then above
2N/A * will result in zero for the last component length.
2N/A * We're done, and optstart already points to the start
2N/A * of the next in list. Otherwise, we need to write a
2N/A * single zero byte to end the entry, if there are more
2N/A * entries that will be decoded.
2N/A */
2N/A while (isascii(*valuep) && isspace(*valuep))
2N/A valuep++;
2N/A if (*flen > 0 && *valuep != '\0')
2N/A *optstart++ = '\0';
2N/A }
2N/A length = (optstart - result) - hlen;
2N/A break;
2N/A
2N/A case DSYM_DUID:
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A errno = 0;
2N/A type = strtoul(value, &currp, 0);
2N/A if (errno != 0 || value == currp || type > 65535 ||
2N/A (*currp != ',' && *currp != '\0')) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A switch (type) {
2N/A case DHCPV6_DUID_LLT: {
2N/A duid_llt_t dllt;
2N/A int hwtype;
2N/A ulong_t tstamp;
2N/A int maclen;
2N/A
2N/A if ((hwtype = get_hw_type(&currp, ierrnop)) == -2) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A if (*currp++ != ',') {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A if (*currp == ',' || *currp == '\0') {
2N/A tstamp = time(NULL) - DUID_TIME_BASE;
2N/A } else {
2N/A tstamp = strtoul(currp, &cp2, 0);
2N/A if (errno != 0 || currp == cp2) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A currp = cp2;
2N/A }
2N/A maclen = get_mac_addr(currp, ierrnop,
2N/A &dllt.dllt_hwtype, hwtype,
2N/A optstart + sizeof (dllt));
2N/A if (maclen == -1) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A dllt.dllt_dutype = htons(type);
2N/A dllt.dllt_time = htonl(tstamp);
2N/A (void) memcpy(optstart, &dllt, sizeof (dllt));
2N/A length = maclen + sizeof (dllt);
2N/A break;
2N/A }
2N/A case DHCPV6_DUID_EN: {
2N/A duid_en_t den;
2N/A ulong_t enterp;
2N/A
2N/A if (*currp++ != ',') {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A enterp = strtoul(currp, &cp2, 0);
2N/A DHCPV6_SET_ENTNUM(&den, enterp);
2N/A if (errno != 0 || currp == cp2 ||
2N/A enterp != DHCPV6_GET_ENTNUM(&den) ||
2N/A (*cp2 != ',' && *cp2 != '\0')) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A if (*cp2 == ',')
2N/A cp2++;
2N/A vallen = strlen(cp2);
2N/A reslen = (vallen + 1) / 2;
2N/A if (hexascii_to_octet(cp2, vallen,
2N/A optstart + sizeof (den), &reslen) != 0) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A den.den_dutype = htons(type);
2N/A (void) memcpy(optstart, &den, sizeof (den));
2N/A length = reslen + sizeof (den);
2N/A break;
2N/A }
2N/A case DHCPV6_DUID_LL: {
2N/A duid_ll_t dll;
2N/A int hwtype;
2N/A int maclen;
2N/A
2N/A if ((hwtype = get_hw_type(&currp, ierrnop)) == -2) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A maclen = get_mac_addr(currp, ierrnop, &dll.dll_hwtype,
2N/A hwtype, optstart + sizeof (dll));
2N/A if (maclen == -1) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A dll.dll_dutype = htons(type);
2N/A (void) memcpy(optstart, &dll, sizeof (dll));
2N/A length = maclen + sizeof (dll);
2N/A break;
2N/A }
2N/A default:
2N/A if (*currp == ',')
2N/A currp++;
2N/A vallen = strlen(currp);
2N/A reslen = (vallen + 1) / 2;
2N/A if (hexascii_to_octet(currp, vallen, optstart + 2,
2N/A &reslen) != 0) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A optstart[0] = type >> 8;
2N/A optstart[1] = type;
2N/A length = reslen + 2;
2N/A break;
2N/A }
2N/A break;
2N/A
2N/A case DSYM_OCTET:
2N/A
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_BAD_OCTET;
2N/A return (NULL);
2N/A }
2N/A
2N/A reslen = length;
2N/A /* Call libinetutil function to decode */
2N/A if (hexascii_to_octet(value, vallen, optstart, &reslen) != 0) {
2N/A free(result);
2N/A *ierrnop = ITAB_BAD_OCTET;
2N/A return (NULL);
2N/A }
2N/A break;
2N/A
2N/A case DSYM_IP:
2N/A case DSYM_IPV6:
2N/A
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_BAD_IPADDR;
2N/A return (NULL);
2N/A }
2N/A if (n_entries % ie->ds_gran != 0) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A inittab_msg("inittab_encode: number of entries "
2N/A "not compatible with option granularity");
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A
2N/A for (valuep = value, i = 0; i < n_entries; i++, valuep++) {
2N/A
2N/A currp = strchr(valuep, ' ');
2N/A if (currp != NULL)
2N/A *currp = '\0';
2N/A if (inet_pton(ie->ds_type == DSYM_IP ? AF_INET :
2N/A AF_INET6, valuep, optstart) != 1) {
2N/A *ierrnop = ITAB_BAD_IPADDR;
2N/A inittab_msg("inittab_encode: bogus ip address");
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A
2N/A valuep = currp;
2N/A if (valuep == NULL) {
2N/A if (i < (n_entries - 1)) {
2N/A *ierrnop = ITAB_NOT_ENOUGH_IP;
2N/A inittab_msg("inittab_encode: too few "
2N/A "ip addresses");
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A break;
2N/A }
2N/A optstart += type_size;
2N/A }
2N/A break;
2N/A
2N/A case DSYM_NUMBER: /* FALLTHRU */
2N/A case DSYM_UNUMBER8: /* FALLTHRU */
2N/A case DSYM_SNUMBER8: /* FALLTHRU */
2N/A case DSYM_UNUMBER16: /* FALLTHRU */
2N/A case DSYM_SNUMBER16: /* FALLTHRU */
2N/A case DSYM_UNUMBER24: /* FALLTHRU */
2N/A case DSYM_UNUMBER32: /* FALLTHRU */
2N/A case DSYM_SNUMBER32: /* FALLTHRU */
2N/A case DSYM_UNUMBER64: /* FALLTHRU */
2N/A case DSYM_SNUMBER64:
2N/A
2N/A if (optstart == NULL) {
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A return (NULL);
2N/A }
2N/A
2N/A is_signed = (ie->ds_type == DSYM_SNUMBER64 ||
2N/A ie->ds_type == DSYM_SNUMBER32 ||
2N/A ie->ds_type == DSYM_SNUMBER16 ||
2N/A ie->ds_type == DSYM_SNUMBER8);
2N/A
2N/A if (encode_number(n_entries, type_size, is_signed, 0, value,
2N/A optstart, ierrnop) == B_FALSE) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A break;
2N/A
2N/A default:
2N/A if (ie->ds_type == DSYM_BOOL)
2N/A *ierrnop = ITAB_BAD_BOOLEAN;
2N/A else
2N/A *ierrnop = ITAB_SYNTAX_ERROR;
2N/A
2N/A inittab_msg("inittab_encode: unsupported type `%d'",
2N/A ie->ds_type);
2N/A
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * if just_payload is false, then we need to add the option
2N/A * code and length fields in.
2N/A */
2N/A if (!just_payload) {
2N/A if (ie->ds_dhcpv6) {
2N/A /* LINTED: alignment */
2N/A d6o = (dhcpv6_option_t *)result;
2N/A d6o->d6o_code = htons(ie->ds_code);
2N/A d6o->d6o_len = htons(length);
2N/A } else {
2N/A result[0] = ie->ds_code;
2N/A result[1] = length;
2N/A }
2N/A }
2N/A
2N/A if (lengthp != NULL)
2N/A *lengthp = length + hlen;
2N/A
2N/A return (result);
2N/A}
2N/A
2N/A/*
2N/A * get_dhcpopt_code(): converts a string representation of a given DHCP option
2N/A * into the DHCP option code. Returns -1 if the given DHCP
2N/A * option string wasn't found in dhcp_inittab(5).
2N/A * input: const char *: the DHCP option string to convert
2N/A * output: int: the DHCP option code on success and -1 on failure.
2N/A */
2N/A
2N/Astatic int
2N/Aget_dhcpopt_code(const char *option)
2N/A{
2N/A dhcp_symbol_t *dhcpsym;
2N/A int optcode;
2N/A
2N/A dhcpsym = inittab_getbyname(ITAB_CAT_STANDARD, ITAB_CONS_INFO, option);
2N/A if (dhcpsym == NULL)
2N/A return (-1);
2N/A
2N/A optcode = dhcpsym->ds_code;
2N/A free(dhcpsym);
2N/A return (optcode);
2N/A}
2N/A
2N/A/*
2N/A * validate_servervalue(): validate the DHCP option value received from the
2N/A * DHCP server. Only checks hostname related options.
2N/A * input: ushort_t: the DHCP option code of the value to be checked.
2N/A * const char *: the DHCP option value to check.
2N/A * output: boolean_t: B_TRUE on success and B_FALSE on failure.
2N/A */
2N/A
2N/Astatic boolean_t
2N/Avalidate_servervalue(ushort_t optcode, const char *result)
2N/A{
2N/A boolean_t valid = B_TRUE;
2N/A
2N/A /* Validate any hostname or domain name option value */
2N/A if (optcode == get_dhcpopt_code("Hostname") ||
2N/A optcode == get_dhcpopt_code("DNSdmain") ||
2N/A optcode == get_dhcpopt_code("TFTPsrvN"))
2N/A valid = valid_hostname(result);
2N/A
2N/A return (valid);
2N/A}
2N/A
2N/A/*
2N/A * inittab_decode_e(): converts a binary representation of a given datatype into
2N/A * a string; used for decoding DHCP options in a packet off
2N/A * the wire into ascii
2N/A *
2N/A * input: dhcp_symbol_t *: the entry describing the payload option
2N/A * uchar_t *: the payload to convert
2N/A * uint16_t: the payload length (only used if just_payload is true)
2N/A * boolean_t: if false, payload is assumed to be a DHCP option
2N/A * int *: set to extended error code if error occurs.
2N/A * output: char *: a dynamically allocated string containing the converted data
2N/A */
2N/A
2N/Achar *
2N/Ainittab_decode_e(const dhcp_symbol_t *ie, const uchar_t *payload,
2N/A uint16_t length, boolean_t just_payload, int *ierrnop)
2N/A{
2N/A char *resultp, *result = NULL;
2N/A uint_t n_entries;
2N/A struct in_addr in_addr;
2N/A in6_addr_t in6_addr;
2N/A uint8_t type_size = inittab_type_to_size(ie);
2N/A boolean_t is_signed;
2N/A int type;
2N/A
2N/A *ierrnop = 0;
2N/A if (type_size == 0) {
2N/A *ierrnop = ITAB_SYNTAX_ERROR;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (!just_payload) {
2N/A if (ie->ds_dhcpv6) {
2N/A dhcpv6_option_t d6o;
2N/A
2N/A (void) memcpy(&d6o, payload, sizeof (d6o));
2N/A length = ntohs(d6o.d6o_len);
2N/A payload += sizeof (d6o);
2N/A } else {
2N/A length = payload[1];
2N/A payload += 2;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * figure out the number of elements to convert. note that
2N/A * for ds_type NUMBER, the granularity is really 1 since the
2N/A * value of ds_gran is the number of bytes in the number.
2N/A */
2N/A if (ie->ds_type == DSYM_NUMBER)
2N/A n_entries = MIN(ie->ds_max, length / type_size);
2N/A else
2N/A n_entries = MIN(ie->ds_max * ie->ds_gran, length / type_size);
2N/A
2N/A if (n_entries == 0)
2N/A n_entries = length / type_size;
2N/A
2N/A if ((length % type_size) != 0) {
2N/A inittab_msg("inittab_decode: length of string not compatible "
2N/A "with option type `%i'", ie->ds_type);
2N/A *ierrnop = ITAB_BAD_STRING;
2N/A return (NULL);
2N/A }
2N/A
2N/A switch (ie->ds_type) {
2N/A
2N/A case DSYM_ASCII:
2N/A
2N/A result = malloc(n_entries + 1);
2N/A if (result == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A (void) memcpy(result, payload, n_entries);
2N/A result[n_entries] = '\0';
2N/A
2N/A if (!validate_servervalue(ie->ds_code, result)) {
2N/A *ierrnop = ITAB_BAD_STRING;
2N/A inittab_msg("inittab_decode: invalid option value %s "
2N/A "for code %d from server", result, ie->ds_code);
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A break;
2N/A
2N/A case DSYM_DOMAIN:
2N/A
2N/A /*
2N/A * A valid, decoded RFC 1035 domain string or sequence of
2N/A * strings is always the same size as the encoded form, but we
2N/A * allow for RFC 1035 \DDD and \\ and \. escaping.
2N/A *
2N/A * Decoding stops at the end of the input or the first coding
2N/A * violation. Coding violations result in discarding the
2N/A * offending list entry entirely. Note that we ignore the 255
2N/A * character overall limit on domain names.
2N/A */
2N/A if ((result = malloc(4 * length + 1)) == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A resultp = result;
2N/A while (length > 0) {
2N/A char *dstart;
2N/A int slen;
2N/A
2N/A dstart = resultp;
2N/A while (length > 0) {
2N/A slen = *payload++;
2N/A length--;
2N/A /* Upper two bits of length must be zero */
2N/A if ((slen & 0xc0) != 0 || slen > length) {
2N/A length = 0;
2N/A resultp = dstart;
2N/A break;
2N/A }
2N/A if (resultp != dstart)
2N/A *resultp++ = '.';
2N/A if (slen == 0)
2N/A break;
2N/A length -= slen;
2N/A while (slen > 0) {
2N/A if (!isascii(*payload) ||
2N/A !isgraph(*payload)) {
2N/A (void) snprintf(resultp, 5,
2N/A "\\%03d",
2N/A *(unsigned char *)payload);
2N/A resultp += 4;
2N/A payload++;
2N/A } else {
2N/A if (*payload == '.' ||
2N/A *payload == '\\')
2N/A *resultp++ = '\\';
2N/A *resultp++ = *payload++;
2N/A }
2N/A slen--;
2N/A }
2N/A }
2N/A if (resultp != dstart && length > 0)
2N/A *resultp++ = ' ';
2N/A }
2N/A *resultp = '\0';
2N/A break;
2N/A
2N/A case DSYM_DUID:
2N/A
2N/A /*
2N/A * First, determine the type of DUID. We need at least two
2N/A * octets worth of data to grab the type code. Once we have
2N/A * that, the number of octets required for representation
2N/A * depends on the type.
2N/A */
2N/A
2N/A if (length < 2) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A return (NULL);
2N/A }
2N/A type = (payload[0] << 8) + payload[1];
2N/A switch (type) {
2N/A case DHCPV6_DUID_LLT: {
2N/A duid_llt_t dllt;
2N/A
2N/A if (length < sizeof (dllt)) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A return (NULL);
2N/A }
2N/A (void) memcpy(&dllt, payload, sizeof (dllt));
2N/A payload += sizeof (dllt);
2N/A length -= sizeof (dllt);
2N/A n_entries = sizeof ("1,65535,4294967295,") +
2N/A length * 3;
2N/A if ((result = malloc(n_entries)) == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A (void) snprintf(result, n_entries, "%d,%u,%u,", type,
2N/A ntohs(dllt.dllt_hwtype), ntohl(dllt.dllt_time));
2N/A break;
2N/A }
2N/A case DHCPV6_DUID_EN: {
2N/A duid_en_t den;
2N/A
2N/A if (length < sizeof (den)) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A return (NULL);
2N/A }
2N/A (void) memcpy(&den, payload, sizeof (den));
2N/A payload += sizeof (den);
2N/A length -= sizeof (den);
2N/A n_entries = sizeof ("2,4294967295,") + length * 2;
2N/A if ((result = malloc(n_entries)) == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A (void) snprintf(result, n_entries, "%d,%u,", type,
2N/A DHCPV6_GET_ENTNUM(&den));
2N/A break;
2N/A }
2N/A case DHCPV6_DUID_LL: {
2N/A duid_ll_t dll;
2N/A
2N/A if (length < sizeof (dll)) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A return (NULL);
2N/A }
2N/A (void) memcpy(&dll, payload, sizeof (dll));
2N/A payload += sizeof (dll);
2N/A length -= sizeof (dll);
2N/A n_entries = sizeof ("3,65535,") + length * 3;
2N/A if ((result = malloc(n_entries)) == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A (void) snprintf(result, n_entries, "%d,%u,", type,
2N/A ntohs(dll.dll_hwtype));
2N/A break;
2N/A }
2N/A default:
2N/A n_entries = sizeof ("0,") + length * 2;
2N/A if ((result = malloc(n_entries)) == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A (void) snprintf(result, n_entries, "%d,", type);
2N/A break;
2N/A }
2N/A resultp = result + strlen(result);
2N/A n_entries -= strlen(result);
2N/A if (type == DHCPV6_DUID_LLT || type == DHCPV6_DUID_LL) {
2N/A if (length > 0) {
2N/A resultp += snprintf(resultp, 3, "%02X",
2N/A *payload++);
2N/A length--;
2N/A }
2N/A while (length-- > 0) {
2N/A resultp += snprintf(resultp, 4, ":%02X",
2N/A *payload++);
2N/A }
2N/A } else {
2N/A while (length-- > 0) {
2N/A resultp += snprintf(resultp, 3, "%02X",
2N/A *payload++);
2N/A }
2N/A }
2N/A break;
2N/A
2N/A case DSYM_OCTET:
2N/A
2N/A result = malloc(n_entries * (sizeof ("0xNN") + 1));
2N/A if (result == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A result[0] = '\0';
2N/A resultp = result;
2N/A if (n_entries > 0) {
2N/A resultp += sprintf(resultp, "0x%02X", *payload++);
2N/A n_entries--;
2N/A }
2N/A while (n_entries-- > 0)
2N/A resultp += sprintf(resultp, " 0x%02X", *payload++);
2N/A
2N/A break;
2N/A
2N/A case DSYM_IP:
2N/A case DSYM_IPV6:
2N/A if ((length / type_size) % ie->ds_gran != 0) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A inittab_msg("inittab_decode: number of entries "
2N/A "not compatible with option granularity");
2N/A return (NULL);
2N/A }
2N/A
2N/A result = malloc(n_entries * (ie->ds_type == DSYM_IP ?
2N/A INET_ADDRSTRLEN : INET6_ADDRSTRLEN));
2N/A if (result == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A for (resultp = result; n_entries != 0; n_entries--) {
2N/A if (ie->ds_type == DSYM_IP) {
2N/A (void) memcpy(&in_addr.s_addr, payload,
2N/A sizeof (ipaddr_t));
2N/A (void) strcpy(resultp, inet_ntoa(in_addr));
2N/A } else {
2N/A (void) memcpy(&in6_addr, payload,
2N/A sizeof (in6_addr));
2N/A (void) inet_ntop(AF_INET6, &in6_addr, resultp,
2N/A INET6_ADDRSTRLEN);
2N/A }
2N/A resultp += strlen(resultp);
2N/A if (n_entries > 1)
2N/A *resultp++ = ' ';
2N/A payload += type_size;
2N/A }
2N/A *resultp = '\0';
2N/A break;
2N/A
2N/A case DSYM_NUMBER: /* FALLTHRU */
2N/A case DSYM_UNUMBER8: /* FALLTHRU */
2N/A case DSYM_SNUMBER8: /* FALLTHRU */
2N/A case DSYM_UNUMBER16: /* FALLTHRU */
2N/A case DSYM_SNUMBER16: /* FALLTHRU */
2N/A case DSYM_UNUMBER32: /* FALLTHRU */
2N/A case DSYM_SNUMBER32: /* FALLTHRU */
2N/A case DSYM_UNUMBER64: /* FALLTHRU */
2N/A case DSYM_SNUMBER64:
2N/A
2N/A is_signed = (ie->ds_type == DSYM_SNUMBER64 ||
2N/A ie->ds_type == DSYM_SNUMBER32 ||
2N/A ie->ds_type == DSYM_SNUMBER16 ||
2N/A ie->ds_type == DSYM_SNUMBER8);
2N/A
2N/A result = malloc(n_entries * ITAB_MAX_NUMBER_LEN);
2N/A if (result == NULL) {
2N/A *ierrnop = ITAB_NOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (decode_number(n_entries, type_size, is_signed, ie->ds_gran,
2N/A payload, result, ierrnop) == B_FALSE) {
2N/A free(result);
2N/A return (NULL);
2N/A }
2N/A break;
2N/A
2N/A default:
2N/A inittab_msg("inittab_decode: unsupported type `%d'",
2N/A ie->ds_type);
2N/A break;
2N/A }
2N/A
2N/A return (result);
2N/A}
2N/A
2N/A/*
2N/A * inittab_encode(): converts a string representation of a given datatype into
2N/A * binary; used for encoding ascii values into a form that
2N/A * can be put in DHCP packets to be sent on the wire.
2N/A *
2N/A * input: dhcp_symbol_t *: the entry describing the value option
2N/A * const char *: the value to convert
2N/A * uint16_t *: set to the length of the binary data returned
2N/A * boolean_t: if false, return a full DHCP option
2N/A * output: uchar_t *: a dynamically allocated byte array with converted data
2N/A */
2N/A
2N/Auchar_t *
2N/Ainittab_encode(const dhcp_symbol_t *ie, const char *value, uint16_t *lengthp,
2N/A boolean_t just_payload)
2N/A{
2N/A int ierrno;
2N/A
2N/A return (inittab_encode_e(ie, value, lengthp, just_payload, &ierrno));
2N/A}
2N/A
2N/A/*
2N/A * inittab_decode(): converts a binary representation of a given datatype into
2N/A * a string; used for decoding DHCP options in a packet off
2N/A * the wire into ascii
2N/A *
2N/A * input: dhcp_symbol_t *: the entry describing the payload option
2N/A * uchar_t *: the payload to convert
2N/A * uint16_t: the payload length (only used if just_payload is true)
2N/A * boolean_t: if false, payload is assumed to be a DHCP option
2N/A * output: char *: a dynamically allocated string containing the converted data
2N/A */
2N/A
2N/Achar *
2N/Ainittab_decode(const dhcp_symbol_t *ie, const uchar_t *payload, uint16_t length,
2N/A boolean_t just_payload)
2N/A{
2N/A int ierrno;
2N/A
2N/A return (inittab_decode_e(ie, payload, length, just_payload, &ierrno));
2N/A}
2N/A
2N/A/*
2N/A * inittab_msg(): prints diagnostic messages if INITTAB_DEBUG is set
2N/A *
2N/A * const char *: a printf-like format string
2N/A * ...: arguments to the format string
2N/A * output: void
2N/A */
2N/A
2N/A/*PRINTFLIKE1*/
2N/Astatic void
2N/Ainittab_msg(const char *fmt, ...)
2N/A{
2N/A enum { INITTAB_MSG_CHECK, INITTAB_MSG_RETURN, INITTAB_MSG_OUTPUT };
2N/A
2N/A va_list ap;
2N/A char buf[512];
2N/A static int action = INITTAB_MSG_CHECK;
2N/A
2N/A /*
2N/A * check DHCP_INITTAB_DEBUG the first time in; thereafter, use
2N/A * the the cached result (stored in `action').
2N/A */
2N/A switch (action) {
2N/A
2N/A case INITTAB_MSG_CHECK:
2N/A
2N/A if (getenv("DHCP_INITTAB_DEBUG") == NULL) {
2N/A action = INITTAB_MSG_RETURN;
2N/A return;
2N/A }
2N/A
2N/A action = INITTAB_MSG_OUTPUT;
2N/A
2N/A /* FALLTHRU into INITTAB_MSG_OUTPUT */
2N/A
2N/A case INITTAB_MSG_OUTPUT:
2N/A
2N/A va_start(ap, fmt);
2N/A
2N/A (void) snprintf(buf, sizeof (buf), "inittab: %s\n", fmt);
2N/A (void) vfprintf(stderr, buf, ap);
2N/A
2N/A va_end(ap);
2N/A break;
2N/A
2N/A case INITTAB_MSG_RETURN:
2N/A
2N/A return;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * decode_number(): decodes a sequence of numbers from binary into ascii;
2N/A * binary is coming off of the network, so it is in nbo
2N/A *
2N/A * input: uint8_t: the number of "granularity" numbers to decode
2N/A * uint8_t: the length of each number
2N/A * boolean_t: whether the numbers should be considered signed
2N/A * uint8_t: the number of numbers per granularity
2N/A * const uint8_t *: where to decode the numbers from
2N/A * char *: where to decode the numbers to
2N/A * output: boolean_t: true on successful conversion, false on failure
2N/A */
2N/A
2N/Astatic boolean_t
2N/Adecode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed,
2N/A uint8_t granularity, const uint8_t *from, char *to, int *ierrnop)
2N/A{
2N/A uint16_t uint16;
2N/A uint32_t uint32;
2N/A uint64_t uint64;
2N/A
2N/A if (granularity != 0) {
2N/A if ((granularity % n_entries) != 0) {
2N/A inittab_msg("decode_number: number of entries "
2N/A "not compatible with option granularity");
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A return (B_FALSE);
2N/A }
2N/A }
2N/A
2N/A for (; n_entries != 0; n_entries--, from += size) {
2N/A
2N/A switch (size) {
2N/A
2N/A case 1:
2N/A to += sprintf(to, is_signed ? "%d" : "%u", *from);
2N/A break;
2N/A
2N/A case 2:
2N/A (void) memcpy(&uint16, from, 2);
2N/A to += sprintf(to, is_signed ? "%hd" : "%hu",
2N/A ntohs(uint16));
2N/A break;
2N/A
2N/A case 3:
2N/A uint32 = 0;
2N/A (void) memcpy((uchar_t *)&uint32 + 1, from, 3);
2N/A to += sprintf(to, is_signed ? "%ld" : "%lu",
2N/A ntohl(uint32));
2N/A break;
2N/A
2N/A case 4:
2N/A (void) memcpy(&uint32, from, 4);
2N/A to += sprintf(to, is_signed ? "%ld" : "%lu",
2N/A ntohl(uint32));
2N/A break;
2N/A
2N/A case 8:
2N/A (void) memcpy(&uint64, from, 8);
2N/A to += sprintf(to, is_signed ? "%lld" : "%llu",
2N/A ntohll(uint64));
2N/A break;
2N/A
2N/A default:
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A inittab_msg("decode_number: unknown integer size `%d'",
2N/A size);
2N/A return (B_FALSE);
2N/A }
2N/A if (n_entries > 0)
2N/A *to++ = ' ';
2N/A }
2N/A
2N/A *to = '\0';
2N/A return (B_TRUE);
2N/A}
2N/A
2N/A/*
2N/A * encode_number(): encodes a sequence of numbers from ascii into binary;
2N/A * number will end up on the wire so it needs to be in nbo
2N/A *
2N/A * input: uint8_t: the number of "granularity" numbers to encode
2N/A * uint8_t: the length of each number
2N/A * boolean_t: whether the numbers should be considered signed
2N/A * uint8_t: the number of numbers per granularity
2N/A * const uint8_t *: where to encode the numbers from
2N/A * char *: where to encode the numbers to
2N/A * int *: set to extended error code if error occurs.
2N/A * output: boolean_t: true on successful conversion, false on failure
2N/A */
2N/A
2N/Astatic boolean_t /* ARGSUSED */
2N/Aencode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed,
2N/A uint8_t granularity, const char *from, uint8_t *to, int *ierrnop)
2N/A{
2N/A uint8_t i;
2N/A uint16_t uint16;
2N/A uint32_t uint32;
2N/A uint64_t uint64;
2N/A char *endptr;
2N/A
2N/A if (granularity != 0) {
2N/A if ((granularity % n_entries) != 0) {
2N/A *ierrnop = ITAB_BAD_GRAN;
2N/A inittab_msg("encode_number: number of entries "
2N/A "not compatible with option granularity");
2N/A return (B_FALSE);
2N/A }
2N/A }
2N/A
2N/A for (i = 0; i < n_entries; i++, from++, to += size) {
2N/A
2N/A /*
2N/A * totally obscure c factoid: it is legal to pass a
2N/A * string representing a negative number to strtoul().
2N/A * in this case, strtoul() will return an unsigned
2N/A * long that if cast to a long, would represent the
2N/A * negative number. we take advantage of this to
2N/A * cut down on code here.
2N/A */
2N/A
2N/A errno = 0;
2N/A switch (size) {
2N/A
2N/A case 1:
2N/A *to = strtoul(from, &endptr, 0);
2N/A if (errno != 0 || from == endptr) {
2N/A goto error;
2N/A }
2N/A break;
2N/A
2N/A case 2:
2N/A uint16 = htons(strtoul(from, &endptr, 0));
2N/A if (errno != 0 || from == endptr) {
2N/A goto error;
2N/A }
2N/A (void) memcpy(to, &uint16, 2);
2N/A break;
2N/A
2N/A case 3:
2N/A uint32 = htonl(strtoul(from, &endptr, 0));
2N/A if (errno != 0 || from == endptr) {
2N/A goto error;
2N/A }
2N/A (void) memcpy(to, (uchar_t *)&uint32 + 1, 3);
2N/A break;
2N/A
2N/A case 4:
2N/A uint32 = htonl(strtoul(from, &endptr, 0));
2N/A if (errno != 0 || from == endptr) {
2N/A goto error;
2N/A }
2N/A (void) memcpy(to, &uint32, 4);
2N/A break;
2N/A
2N/A case 8:
2N/A uint64 = htonll(strtoull(from, &endptr, 0));
2N/A if (errno != 0 || from == endptr) {
2N/A goto error;
2N/A }
2N/A (void) memcpy(to, &uint64, 8);
2N/A break;
2N/A
2N/A default:
2N/A inittab_msg("encode_number: unsupported integer "
2N/A "size `%d'", size);
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A from = strchr(from, ' ');
2N/A if (from == NULL)
2N/A break;
2N/A }
2N/A
2N/A return (B_TRUE);
2N/A
2N/Aerror:
2N/A *ierrnop = ITAB_BAD_NUMBER;
2N/A inittab_msg("encode_number: cannot convert to integer");
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * inittab_type_to_size(): given an inittab entry, returns size of one entry of
2N/A * its type
2N/A *
2N/A * input: dhcp_symbol_t *: an entry of the given type
2N/A * output: uint8_t: the size in bytes of an entry of that type
2N/A */
2N/A
2N/Auint8_t
2N/Ainittab_type_to_size(const dhcp_symbol_t *ie)
2N/A{
2N/A switch (ie->ds_type) {
2N/A
2N/A case DSYM_DUID:
2N/A case DSYM_DOMAIN:
2N/A case DSYM_ASCII:
2N/A case DSYM_OCTET:
2N/A case DSYM_SNUMBER8:
2N/A case DSYM_UNUMBER8:
2N/A
2N/A return (1);
2N/A
2N/A case DSYM_SNUMBER16:
2N/A case DSYM_UNUMBER16:
2N/A
2N/A return (2);
2N/A
2N/A case DSYM_UNUMBER24:
2N/A
2N/A return (3);
2N/A
2N/A case DSYM_SNUMBER32:
2N/A case DSYM_UNUMBER32:
2N/A case DSYM_IP:
2N/A
2N/A return (4);
2N/A
2N/A case DSYM_SNUMBER64:
2N/A case DSYM_UNUMBER64:
2N/A
2N/A return (8);
2N/A
2N/A case DSYM_NUMBER:
2N/A
2N/A return (ie->ds_gran);
2N/A
2N/A case DSYM_IPV6:
2N/A
2N/A return (sizeof (in6_addr_t));
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * itabcode_to_dsymcode(): maps an inittab category code to its dsym
2N/A * representation
2N/A *
2N/A * input: uchar_t: the inittab category code
2N/A * output: dsym_category_t: the dsym category code
2N/A */
2N/A
2N/Astatic dsym_category_t
2N/Aitabcode_to_dsymcode(uchar_t itabcode)
2N/A{
2N/A
2N/A unsigned int i;
2N/A
2N/A for (i = 0; i < category_map_nent; i++)
2N/A if (category_map[i].cme_itabcode == itabcode)
2N/A return (category_map[i].cme_dsymcode);
2N/A
2N/A return (DSYM_BAD_CAT);
2N/A}
2N/A
2N/A/*
2N/A * category_to_code(): maps a category name to its numeric representation
2N/A *
2N/A * input: const char *: the category name
2N/A * output: uchar_t: its internal code (numeric representation)
2N/A */
2N/A
2N/Astatic uchar_t
2N/Acategory_to_code(const char *category)
2N/A{
2N/A unsigned int i;
2N/A
2N/A for (i = 0; i < category_map_nent; i++)
2N/A if (strcasecmp(category_map[i].cme_name, category) == 0)
2N/A return (category_map[i].cme_itabcode);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * our internal table of DHCP option values, used by inittab_verify()
2N/A */
2N/Astatic dhcp_symbol_t inittab_table[] =
2N/A{
2N/A{ DSYM_INTERNAL, 1024, "Hostname", DSYM_BOOL, 0, 0 },
2N/A{ DSYM_INTERNAL, 1025, "LeaseNeg", DSYM_BOOL, 0, 0 },
2N/A{ DSYM_INTERNAL, 1026, "EchoVC", DSYM_BOOL, 0, 0 },
2N/A{ DSYM_INTERNAL, 1027, "BootPath", DSYM_ASCII, 1, 128 },
2N/A{ DSYM_FIELD, 0, "Opcode", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_FIELD, 1, "Htype", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_FIELD, 2, "HLen", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_FIELD, 3, "Hops", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_FIELD, 4, "Xid", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_FIELD, 8, "Secs", DSYM_UNUMBER16, 1, 1 },
2N/A{ DSYM_FIELD, 10, "Flags", DSYM_OCTET, 1, 2 },
2N/A{ DSYM_FIELD, 12, "Ciaddr", DSYM_IP, 1, 1 },
2N/A{ DSYM_FIELD, 16, "Yiaddr", DSYM_IP, 1, 1 },
2N/A{ DSYM_FIELD, 20, "BootSrvA", DSYM_IP, 1, 1 },
2N/A{ DSYM_FIELD, 24, "Giaddr", DSYM_IP, 1, 1 },
2N/A{ DSYM_FIELD, 28, "Chaddr", DSYM_OCTET, 1, 16 },
2N/A{ DSYM_FIELD, 44, "BootSrvN", DSYM_ASCII, 1, 64 },
2N/A{ DSYM_FIELD, 108, "BootFile", DSYM_ASCII, 1, 128 },
2N/A{ DSYM_FIELD, 236, "Magic", DSYM_OCTET, 1, 4 },
2N/A{ DSYM_FIELD, 240, "Options", DSYM_OCTET, 1, 60 },
2N/A{ DSYM_STANDARD, 1, "Subnet", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 2, "UTCoffst", DSYM_SNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 3, "Router", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 4, "Timeserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 5, "IEN116ns", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 6, "DNSserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 7, "Logserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 8, "Cookie", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 9, "Lprserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 10, "Impress", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 11, "Resource", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 12, "Hostname", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 13, "Bootsize", DSYM_UNUMBER16, 1, 1 },
2N/A{ DSYM_STANDARD, 14, "Dumpfile", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 15, "DNSdmain", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 16, "Swapserv", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 17, "Rootpath", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 18, "ExtendP", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 19, "IpFwdF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 20, "NLrouteF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 21, "PFilter", DSYM_IP, 2, 0 },
2N/A{ DSYM_STANDARD, 22, "MaxIpSiz", DSYM_UNUMBER16, 1, 1 },
2N/A{ DSYM_STANDARD, 23, "IpTTL", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 24, "PathTO", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 25, "PathTbl", DSYM_UNUMBER16, 1, 0 },
2N/A{ DSYM_STANDARD, 26, "MTU", DSYM_UNUMBER16, 1, 1 },
2N/A{ DSYM_STANDARD, 27, "SameMtuF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 28, "Broadcst", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 29, "MaskDscF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 30, "MaskSupF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 31, "RDiscvyF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 32, "RSolictS", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 33, "StaticRt", DSYM_IP, 2, 0 },
2N/A{ DSYM_STANDARD, 34, "TrailerF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 35, "ArpTimeO", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 36, "EthEncap", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 37, "TcpTTL", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 38, "TcpKaInt", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 39, "TcpKaGbF", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 40, "NISdmain", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 41, "NISservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 42, "NTPservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 43, "Vendor", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 44, "NetBNms", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 45, "NetBDsts", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 46, "NetBNdT", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 47, "NetBScop", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 48, "XFontSrv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 49, "XDispMgr", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 50, "ReqIP", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 51, "LeaseTim", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 52, "OptOvrld", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 53, "DHCPType", DSYM_UNUMBER8, 1, 1 },
2N/A{ DSYM_STANDARD, 54, "ServerID", DSYM_IP, 1, 1 },
2N/A{ DSYM_STANDARD, 55, "ReqList", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 56, "Message", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 57, "DHCP_MTU", DSYM_UNUMBER16, 1, 1 },
2N/A{ DSYM_STANDARD, 58, "T1Time", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 59, "T2Time", DSYM_UNUMBER32, 1, 1 },
2N/A{ DSYM_STANDARD, 60, "ClassID", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 61, "ClientID", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 62, "NW_dmain", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 63, "NWIPOpts", DSYM_OCTET, 1, 128 },
2N/A{ DSYM_STANDARD, 64, "NIS+dom", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 65, "NIS+serv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 66, "TFTPsrvN", DSYM_ASCII, 1, 64 },
2N/A{ DSYM_STANDARD, 67, "OptBootF", DSYM_ASCII, 1, 128 },
2N/A{ DSYM_STANDARD, 68, "MblIPAgt", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 69, "SMTPserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 70, "POP3serv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 71, "NNTPserv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 72, "WWWservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 73, "Fingersv", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 74, "IRCservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 75, "STservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 76, "STDAservs", DSYM_IP, 1, 0 },
2N/A{ DSYM_STANDARD, 77, "UserClas", DSYM_ASCII, 1, 0 },
2N/A{ DSYM_STANDARD, 78, "SLP_DA", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 79, "SLP_SS", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 82, "AgentOpt", DSYM_OCTET, 1, 0 },
2N/A{ DSYM_STANDARD, 89, "FQDN", DSYM_OCTET, 1, 0 },
2N/A{ 0, 0, "", 0, 0, 0 }
2N/A};