1002 lines
25 KiB
C
1002 lines
25 KiB
C
/* $OpenBSD: options.c,v 1.123 2020/07/07 19:48:31 krw Exp $ */
|
|
|
|
/* DHCP options parsing and reassembly. */
|
|
|
|
/*
|
|
* Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of The Internet Software Consortium nor the names
|
|
* of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
|
|
* CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* This software has been written for the Internet Software Consortium
|
|
* by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
|
|
* Enterprises. To learn more about the Internet Software Consortium,
|
|
* see ``http://www.vix.com/isc''. To learn more about Vixie
|
|
* Enterprises, see ``http://www.vix.com''.
|
|
*/
|
|
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <net/if.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <ctype.h>
|
|
#include <resolv.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <vis.h>
|
|
|
|
#include "dhcp.h"
|
|
#include "dhcpd.h"
|
|
#include "log.h"
|
|
|
|
int parse_option_buffer(struct option_data *, unsigned char *, int);
|
|
void pretty_print_classless_routes(unsigned char *, size_t, unsigned char *,
|
|
size_t);
|
|
void pretty_print_domain_list(unsigned char *, size_t, unsigned char *,
|
|
size_t);
|
|
|
|
/*
|
|
* DHCP Option names, formats and codes, from RFC1533.
|
|
*
|
|
* Format codes:
|
|
*
|
|
* e - end of data
|
|
* I - IP address
|
|
* l - 32-bit signed integer
|
|
* L - 32-bit unsigned integer
|
|
* S - 16-bit unsigned integer
|
|
* B - 8-bit unsigned integer
|
|
* t - ASCII text
|
|
* f - flag (true or false)
|
|
* A - array of whatever precedes (e.g., IA means array of IP addresses)
|
|
* C - CIDR description
|
|
* X - hex octets
|
|
* D - domain name list, comma separated list of domain names.
|
|
*/
|
|
|
|
static const struct {
|
|
char *name;
|
|
char *format;
|
|
} dhcp_options[DHO_COUNT] = {
|
|
/* 0 */ { "pad", "" },
|
|
/* 1 */ { "subnet-mask", "I" },
|
|
/* 2 */ { "time-offset", "l" },
|
|
/* 3 */ { "routers", "IA" },
|
|
/* 4 */ { "time-servers", "IA" },
|
|
/* 5 */ { "ien116-name-servers", "IA" },
|
|
/* 6 */ { "domain-name-servers", "IA" },
|
|
/* 7 */ { "log-servers", "IA" },
|
|
/* 8 */ { "cookie-servers", "IA" },
|
|
/* 9 */ { "lpr-servers", "IA" },
|
|
/* 10 */ { "impress-servers", "IA" },
|
|
/* 11 */ { "resource-location-servers", "IA" },
|
|
/* 12 */ { "host-name", "t" },
|
|
/* 13 */ { "boot-size", "S" },
|
|
/* 14 */ { "merit-dump", "t" },
|
|
/* 15 */ { "domain-name", "t" },
|
|
/* 16 */ { "swap-server", "I" },
|
|
/* 17 */ { "root-path", "t" },
|
|
/* 18 */ { "extensions-path", "t" },
|
|
/* 19 */ { "ip-forwarding", "f" },
|
|
/* 20 */ { "non-local-source-routing", "f" },
|
|
/* 21 */ { "policy-filter", "IIA" },
|
|
/* 22 */ { "max-dgram-reassembly", "S" },
|
|
/* 23 */ { "default-ip-ttl", "B" },
|
|
/* 24 */ { "path-mtu-aging-timeout", "L" },
|
|
/* 25 */ { "path-mtu-plateau-table", "SA" },
|
|
/* 26 */ { "interface-mtu", "S" },
|
|
/* 27 */ { "all-subnets-local", "f" },
|
|
/* 28 */ { "broadcast-address", "I" },
|
|
/* 29 */ { "perform-mask-discovery", "f" },
|
|
/* 30 */ { "mask-supplier", "f" },
|
|
/* 31 */ { "router-discovery", "f" },
|
|
/* 32 */ { "router-solicitation-address", "I" },
|
|
/* 33 */ { "static-routes", "IIA" },
|
|
/* 34 */ { "trailer-encapsulation", "f" },
|
|
/* 35 */ { "arp-cache-timeout", "L" },
|
|
/* 36 */ { "ieee802-3-encapsulation", "f" },
|
|
/* 37 */ { "default-tcp-ttl", "B" },
|
|
/* 38 */ { "tcp-keepalive-interval", "L" },
|
|
/* 39 */ { "tcp-keepalive-garbage", "f" },
|
|
/* 40 */ { "nis-domain", "t" },
|
|
/* 41 */ { "nis-servers", "IA" },
|
|
/* 42 */ { "ntp-servers", "IA" },
|
|
/* 43 */ { "vendor-encapsulated-options", "X" },
|
|
/* 44 */ { "netbios-name-servers", "IA" },
|
|
/* 45 */ { "netbios-dd-server", "IA" },
|
|
/* 46 */ { "netbios-node-type", "B" },
|
|
/* 47 */ { "netbios-scope", "t" },
|
|
/* 48 */ { "font-servers", "IA" },
|
|
/* 49 */ { "x-display-manager", "IA" },
|
|
/* 50 */ { "dhcp-requested-address", "I" },
|
|
/* 51 */ { "dhcp-lease-time", "L" },
|
|
/* 52 */ { "dhcp-option-overload", "B" },
|
|
/* 53 */ { "dhcp-message-type", "B" },
|
|
/* 54 */ { "dhcp-server-identifier", "I" },
|
|
/* 55 */ { "dhcp-parameter-request-list", "BA" },
|
|
/* 56 */ { "dhcp-message", "t" },
|
|
/* 57 */ { "dhcp-max-message-size", "S" },
|
|
/* 58 */ { "dhcp-renewal-time", "L" },
|
|
/* 59 */ { "dhcp-rebinding-time", "L" },
|
|
/* 60 */ { "dhcp-class-identifier", "t" },
|
|
/* 61 */ { "dhcp-client-identifier", "X" },
|
|
/* 62 */ { NULL, NULL },
|
|
/* 63 */ { NULL, NULL },
|
|
/* 64 */ { "nisplus-domain", "t" },
|
|
/* 65 */ { "nisplus-servers", "IA" },
|
|
/* 66 */ { "tftp-server-name", "t" },
|
|
/* 67 */ { "bootfile-name", "t" },
|
|
/* 68 */ { "mobile-ip-home-agent", "IA" },
|
|
/* 69 */ { "smtp-server", "IA" },
|
|
/* 70 */ { "pop-server", "IA" },
|
|
/* 71 */ { "nntp-server", "IA" },
|
|
/* 72 */ { "www-server", "IA" },
|
|
/* 73 */ { "finger-server", "IA" },
|
|
/* 74 */ { "irc-server", "IA" },
|
|
/* 75 */ { "streettalk-server", "IA" },
|
|
/* 76 */ { "streettalk-directory-assistance-server", "IA" },
|
|
/* 77 */ { "user-class", "t" },
|
|
/* 78 */ { NULL, NULL },
|
|
/* 79 */ { NULL, NULL },
|
|
/* 80 */ { NULL, NULL },
|
|
/* 81 */ { NULL, NULL },
|
|
/* 82 */ { "relay-agent-information", "X" },
|
|
/* 83 */ { NULL, NULL },
|
|
/* 84 */ { NULL, NULL },
|
|
/* 85 */ { "nds-servers", "IA" },
|
|
/* 86 */ { "nds-tree-name", "X" },
|
|
/* 87 */ { "nds-context", "X" },
|
|
/* 88 */ { NULL, NULL },
|
|
/* 89 */ { NULL, NULL },
|
|
/* 90 */ { NULL, NULL },
|
|
/* 91 */ { NULL, NULL },
|
|
/* 92 */ { NULL, NULL },
|
|
/* 93 */ { NULL, NULL },
|
|
/* 94 */ { NULL, NULL },
|
|
/* 95 */ { NULL, NULL },
|
|
/* 96 */ { NULL, NULL },
|
|
/* 97 */ { NULL, NULL },
|
|
/* 98 */ { NULL, NULL },
|
|
/* 99 */ { NULL, NULL },
|
|
/* 100 */ { NULL, NULL },
|
|
/* 101 */ { NULL, NULL },
|
|
/* 102 */ { NULL, NULL },
|
|
/* 103 */ { NULL, NULL },
|
|
/* 104 */ { NULL, NULL },
|
|
/* 105 */ { NULL, NULL },
|
|
/* 106 */ { NULL, NULL },
|
|
/* 107 */ { NULL, NULL },
|
|
/* 108 */ { NULL, NULL },
|
|
/* 109 */ { NULL, NULL },
|
|
/* 110 */ { NULL, NULL },
|
|
/* 111 */ { NULL, NULL },
|
|
/* 112 */ { NULL, NULL },
|
|
/* 113 */ { NULL, NULL },
|
|
/* 114 */ { NULL, NULL },
|
|
/* 115 */ { NULL, NULL },
|
|
/* 116 */ { NULL, NULL },
|
|
/* 117 */ { NULL, NULL },
|
|
/* 118 */ { NULL, NULL },
|
|
/* 119 */ { "domain-search", "D" },
|
|
/* 120 */ { NULL, NULL },
|
|
/* 121 */ { "classless-static-routes", "CIA" },
|
|
/* 122 */ { NULL, NULL },
|
|
/* 123 */ { NULL, NULL },
|
|
/* 124 */ { NULL, NULL },
|
|
/* 125 */ { NULL, NULL },
|
|
/* 126 */ { NULL, NULL },
|
|
/* 127 */ { NULL, NULL },
|
|
/* 128 */ { NULL, NULL },
|
|
/* 129 */ { NULL, NULL },
|
|
/* 130 */ { NULL, NULL },
|
|
/* 131 */ { NULL, NULL },
|
|
/* 132 */ { NULL, NULL },
|
|
/* 133 */ { NULL, NULL },
|
|
/* 134 */ { NULL, NULL },
|
|
/* 135 */ { NULL, NULL },
|
|
/* 136 */ { NULL, NULL },
|
|
/* 137 */ { NULL, NULL },
|
|
/* 138 */ { NULL, NULL },
|
|
/* 139 */ { NULL, NULL },
|
|
/* 140 */ { NULL, NULL },
|
|
/* 141 */ { NULL, NULL },
|
|
/* 142 */ { NULL, NULL },
|
|
/* 143 */ { NULL, NULL },
|
|
/* 144 */ { "tftp-config-file", "t" },
|
|
/* 145 */ { NULL, NULL },
|
|
/* 146 */ { NULL, NULL },
|
|
/* 147 */ { NULL, NULL },
|
|
/* 148 */ { NULL, NULL },
|
|
/* 149 */ { NULL, NULL },
|
|
/* 150 */ { "voip-configuration-server", "IA" },
|
|
/* 151 */ { NULL, NULL },
|
|
/* 152 */ { NULL, NULL },
|
|
/* 153 */ { NULL, NULL },
|
|
/* 154 */ { NULL, NULL },
|
|
/* 155 */ { NULL, NULL },
|
|
/* 156 */ { NULL, NULL },
|
|
/* 157 */ { NULL, NULL },
|
|
/* 158 */ { NULL, NULL },
|
|
/* 159 */ { NULL, NULL },
|
|
/* 160 */ { NULL, NULL },
|
|
/* 161 */ { NULL, NULL },
|
|
/* 162 */ { NULL, NULL },
|
|
/* 163 */ { NULL, NULL },
|
|
/* 164 */ { NULL, NULL },
|
|
/* 165 */ { NULL, NULL },
|
|
/* 166 */ { NULL, NULL },
|
|
/* 167 */ { NULL, NULL },
|
|
/* 168 */ { NULL, NULL },
|
|
/* 169 */ { NULL, NULL },
|
|
/* 170 */ { NULL, NULL },
|
|
/* 171 */ { NULL, NULL },
|
|
/* 172 */ { NULL, NULL },
|
|
/* 173 */ { NULL, NULL },
|
|
/* 174 */ { NULL, NULL },
|
|
/* 175 */ { NULL, NULL },
|
|
/* 176 */ { NULL, NULL },
|
|
/* 177 */ { NULL, NULL },
|
|
/* 178 */ { NULL, NULL },
|
|
/* 179 */ { NULL, NULL },
|
|
/* 180 */ { NULL, NULL },
|
|
/* 181 */ { NULL, NULL },
|
|
/* 182 */ { NULL, NULL },
|
|
/* 183 */ { NULL, NULL },
|
|
/* 184 */ { NULL, NULL },
|
|
/* 185 */ { NULL, NULL },
|
|
/* 186 */ { NULL, NULL },
|
|
/* 187 */ { NULL, NULL },
|
|
/* 188 */ { NULL, NULL },
|
|
/* 189 */ { NULL, NULL },
|
|
/* 190 */ { NULL, NULL },
|
|
/* 191 */ { NULL, NULL },
|
|
/* 192 */ { NULL, NULL },
|
|
/* 193 */ { NULL, NULL },
|
|
/* 194 */ { NULL, NULL },
|
|
/* 195 */ { NULL, NULL },
|
|
/* 196 */ { NULL, NULL },
|
|
/* 197 */ { NULL, NULL },
|
|
/* 198 */ { NULL, NULL },
|
|
/* 199 */ { NULL, NULL },
|
|
/* 200 */ { NULL, NULL },
|
|
/* 201 */ { NULL, NULL },
|
|
/* 202 */ { NULL, NULL },
|
|
/* 203 */ { NULL, NULL },
|
|
/* 204 */ { NULL, NULL },
|
|
/* 205 */ { NULL, NULL },
|
|
/* 206 */ { NULL, NULL },
|
|
/* 207 */ { NULL, NULL },
|
|
/* 208 */ { NULL, NULL },
|
|
/* 209 */ { NULL, NULL },
|
|
/* 210 */ { NULL, NULL },
|
|
/* 211 */ { NULL, NULL },
|
|
/* 212 */ { NULL, NULL },
|
|
/* 213 */ { NULL, NULL },
|
|
/* 214 */ { NULL, NULL },
|
|
/* 215 */ { NULL, NULL },
|
|
/* 216 */ { NULL, NULL },
|
|
/* 217 */ { NULL, NULL },
|
|
/* 218 */ { NULL, NULL },
|
|
/* 219 */ { NULL, NULL },
|
|
/* 220 */ { NULL, NULL },
|
|
/* 221 */ { NULL, NULL },
|
|
/* 222 */ { NULL, NULL },
|
|
/* 223 */ { NULL, NULL },
|
|
/* 224 */ { NULL, NULL },
|
|
/* 225 */ { NULL, NULL },
|
|
/* 226 */ { NULL, NULL },
|
|
/* 227 */ { NULL, NULL },
|
|
/* 228 */ { NULL, NULL },
|
|
/* 229 */ { NULL, NULL },
|
|
/* 230 */ { NULL, NULL },
|
|
/* 231 */ { NULL, NULL },
|
|
/* 232 */ { NULL, NULL },
|
|
/* 233 */ { NULL, NULL },
|
|
/* 234 */ { NULL, NULL },
|
|
/* 235 */ { NULL, NULL },
|
|
/* 236 */ { NULL, NULL },
|
|
/* 237 */ { NULL, NULL },
|
|
/* 238 */ { NULL, NULL },
|
|
/* 239 */ { NULL, NULL },
|
|
/* 240 */ { NULL, NULL },
|
|
/* 241 */ { NULL, NULL },
|
|
/* 242 */ { NULL, NULL },
|
|
/* 243 */ { NULL, NULL },
|
|
/* 244 */ { NULL, NULL },
|
|
/* 245 */ { NULL, NULL },
|
|
/* 246 */ { NULL, NULL },
|
|
/* 247 */ { NULL, NULL },
|
|
/* 248 */ { NULL, NULL },
|
|
/* 249 */ { "classless-ms-static-routes", "CIA" },
|
|
/* 250 */ { NULL, NULL },
|
|
/* 251 */ { NULL, NULL },
|
|
/* 252 */ { "autoproxy-script", "t" },
|
|
/* 253 */ { NULL, NULL },
|
|
/* 254 */ { NULL, NULL },
|
|
/* 255 */ { "option-end", "e" },
|
|
};
|
|
|
|
char *
|
|
code_to_name(int code)
|
|
{
|
|
static char unknown[11]; /* "option-NNN" */
|
|
int ret;
|
|
|
|
if (code < 0 || code >= DHO_COUNT)
|
|
return "";
|
|
|
|
if (dhcp_options[code].name != NULL)
|
|
return dhcp_options[code].name;
|
|
|
|
ret = snprintf(unknown, sizeof(unknown), "option-%d", code);
|
|
if (ret < 0 || ret >= (int)sizeof(unknown))
|
|
return "";
|
|
|
|
return unknown;
|
|
}
|
|
|
|
int
|
|
name_to_code(char *name)
|
|
{
|
|
char unknown[11]; /* "option-NNN" */
|
|
int code, ret;
|
|
|
|
for (code = 1; code < DHO_END; code++) {
|
|
if (dhcp_options[code].name == NULL) {
|
|
ret = snprintf(unknown, sizeof(unknown), "option-%d",
|
|
code);
|
|
if (ret < 0 || ret >= (int)sizeof(unknown))
|
|
return DHO_END;
|
|
if (strcasecmp(unknown, name) == 0)
|
|
return code;
|
|
} else if (strcasecmp(dhcp_options[code].name, name) == 0) {
|
|
return code;
|
|
}
|
|
}
|
|
|
|
return DHO_END;
|
|
}
|
|
|
|
char *
|
|
code_to_format(int code)
|
|
{
|
|
if (code < 0 || code >= DHO_COUNT)
|
|
return "";
|
|
|
|
if (dhcp_options[code].format == NULL)
|
|
return "X";
|
|
|
|
return dhcp_options[code].format;
|
|
}
|
|
|
|
/*
|
|
* Some option data types cannot be appended or prepended to. For
|
|
* such options change ACTION_PREPEND to ACTION_SUPERSEDE and
|
|
* ACTION_APPEND to ACTION_DEFAULT.
|
|
*/
|
|
int
|
|
code_to_action(int code, int action)
|
|
{
|
|
char *fmt;
|
|
|
|
fmt = code_to_format(code);
|
|
if (fmt == NULL || strpbrk(fmt, "ADtX") != NULL)
|
|
return action;
|
|
|
|
/*
|
|
* For our protection all formats which have been excluded shall be
|
|
* deemed included.
|
|
*/
|
|
switch (action) {
|
|
case ACTION_APPEND:
|
|
action = ACTION_DEFAULT;
|
|
break;
|
|
case ACTION_PREPEND:
|
|
action = ACTION_SUPERSEDE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
/*
|
|
* Parse options out of the specified buffer, storing addresses of
|
|
* option values in options. Return 0 if errors, 1 if not.
|
|
*/
|
|
int
|
|
parse_option_buffer(struct option_data *options, unsigned char *buffer,
|
|
int length)
|
|
{
|
|
unsigned char *s, *t, *end;
|
|
char *name, *fmt;
|
|
int code, len, newlen;
|
|
|
|
s = buffer;
|
|
end = s + length;
|
|
while (s < end) {
|
|
code = s[0];
|
|
|
|
/* End options terminate processing. */
|
|
if (code == DHO_END)
|
|
break;
|
|
|
|
/* Pad options don't have a length - just skip them. */
|
|
if (code == DHO_PAD) {
|
|
s++;
|
|
continue;
|
|
}
|
|
|
|
name = code_to_name(code);
|
|
fmt = code_to_format(code);
|
|
|
|
/*
|
|
* All options other than DHO_PAD and DHO_END have a one-byte
|
|
* length field. It could be 0! Make sure that the length byte
|
|
* is present, and all the data is available.
|
|
*/
|
|
if (s + 1 < end) {
|
|
len = s[1];
|
|
if (s + 1 + len < end) {
|
|
; /* option data is all there. */
|
|
} else {
|
|
log_warnx("%s: option %s (%d) larger than "
|
|
"buffer", log_procname, name, len);
|
|
return 0;
|
|
}
|
|
} else {
|
|
log_warnx("%s: option %s has no length field",
|
|
log_procname, name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Strip trailing NULs from ascii ('t') options. RFC 2132
|
|
* says "Options containing NVT ASCII data SHOULD NOT include
|
|
* a trailing NULL; however, the receiver of such options
|
|
* MUST be prepared to delete trailing nulls if they exist."
|
|
*/
|
|
if (fmt[0] == 't') {
|
|
while (len > 0 && s[len + 1] == '\0')
|
|
len--;
|
|
}
|
|
|
|
/*
|
|
* Concatenate new data + NUL to existing option data.
|
|
*
|
|
* Note that the NUL is *not* counted in the len field!
|
|
*/
|
|
newlen = options[code].len + len;
|
|
if ((t = realloc(options[code].data, newlen + 1)) == NULL)
|
|
fatal("option %s", name);
|
|
|
|
memcpy(t + options[code].len, &s[2], len);
|
|
t[newlen] = 0;
|
|
|
|
options[code].len = newlen;
|
|
options[code].data = t;
|
|
|
|
s += s[1] + 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Pack as many options as fit in buflen bytes of buf. Return the
|
|
* offset of the start of the last option copied. A caller can check
|
|
* to see if it's DHO_END to decide if all the options were copied.
|
|
*/
|
|
int
|
|
pack_options(unsigned char *buf, int buflen, struct option_data *options)
|
|
{
|
|
int ix, incr, length, bufix, code, lastopt = -1;
|
|
|
|
memset(buf, 0, buflen);
|
|
|
|
memcpy(buf, DHCP_OPTIONS_COOKIE, 4);
|
|
if (options[DHO_DHCP_MESSAGE_TYPE].data != NULL) {
|
|
memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3);
|
|
buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0];
|
|
bufix = 7;
|
|
} else
|
|
bufix = 4;
|
|
|
|
for (code = DHO_SUBNET_MASK; code < DHO_END; code++) {
|
|
if (options[code].data == NULL ||
|
|
code == DHO_DHCP_MESSAGE_TYPE)
|
|
continue;
|
|
|
|
length = options[code].len;
|
|
if (bufix + length + 2*((length+254)/255) >= buflen)
|
|
return lastopt;
|
|
|
|
lastopt = bufix;
|
|
ix = 0;
|
|
|
|
while (length) {
|
|
incr = length > 255 ? 255 : length;
|
|
|
|
buf[bufix++] = code;
|
|
buf[bufix++] = incr;
|
|
memcpy(buf + bufix, options[code].data + ix, incr);
|
|
|
|
length -= incr;
|
|
ix += incr;
|
|
bufix += incr;
|
|
}
|
|
}
|
|
|
|
if (bufix < buflen) {
|
|
buf[bufix] = DHO_END;
|
|
lastopt = bufix;
|
|
}
|
|
|
|
return lastopt;
|
|
}
|
|
|
|
/*
|
|
* Use vis() to encode characters of src and append encoded characters onto
|
|
* dst. Also encode ", ', $, ` and \, to ensure resulting strings can be
|
|
* represented as '"' delimited strings and safely passed to scripts. Surround
|
|
* result with double quotes if emit_punct is true.
|
|
*/
|
|
char *
|
|
pretty_print_string(unsigned char *src, size_t srclen, int emit_punct)
|
|
{
|
|
static char string[8196];
|
|
char visbuf[5];
|
|
unsigned char *origsrc = src;
|
|
size_t rslt = 0;
|
|
|
|
memset(string, 0, sizeof(string));
|
|
|
|
if (emit_punct != 0)
|
|
rslt = strlcat(string, "\"", sizeof(string));
|
|
|
|
for (; src < origsrc + srclen; src++) {
|
|
if (*src && strchr("\"'$`\\", *src))
|
|
vis(visbuf, *src, VIS_ALL | VIS_OCTAL, *src+1);
|
|
else
|
|
vis(visbuf, *src, VIS_OCTAL, *src+1);
|
|
rslt = strlcat(string, visbuf, sizeof(string));
|
|
}
|
|
|
|
if (emit_punct != 0)
|
|
rslt = strlcat(string, "\"", sizeof(string));
|
|
|
|
if (rslt >= sizeof(string))
|
|
return NULL;
|
|
|
|
return string;
|
|
}
|
|
|
|
/*
|
|
* Must special case *_CLASSLESS_* route options due to the variable size
|
|
* of the CIDR element in its CIA format.
|
|
*/
|
|
void
|
|
pretty_print_classless_routes(unsigned char *src, size_t srclen,
|
|
unsigned char *buf, size_t buflen)
|
|
{
|
|
char bitsbuf[5]; /* to hold "/nn " */
|
|
struct in_addr dest, netmask, gateway;
|
|
unsigned int bits, i, len;
|
|
uint32_t m;
|
|
int rslt;
|
|
|
|
i = 0;
|
|
while (i < srclen) {
|
|
len = extract_route(&src[i], srclen - i, &dest.s_addr,
|
|
&netmask.s_addr, &gateway.s_addr);
|
|
if (len == 0)
|
|
goto bad;
|
|
i += len;
|
|
|
|
m = ntohl(netmask.s_addr);
|
|
bits = 32;
|
|
while ((bits > 0) && ((m & 1) == 0)) {
|
|
m >>= 1;
|
|
bits--;
|
|
}
|
|
|
|
rslt = snprintf(bitsbuf, sizeof(bitsbuf), "/%d ", bits);
|
|
if (rslt < 0 || (unsigned int)rslt >= sizeof(bitsbuf))
|
|
goto bad;
|
|
|
|
if (strlen(buf) > 0)
|
|
strlcat(buf, ", ", buflen);
|
|
strlcat(buf, inet_ntoa(dest), buflen);
|
|
strlcat(buf, bitsbuf, buflen);
|
|
if (strlcat(buf, inet_ntoa(gateway), buflen) >= buflen)
|
|
goto bad;
|
|
}
|
|
|
|
return;
|
|
|
|
bad:
|
|
memset(buf, 0, buflen);
|
|
}
|
|
|
|
/*
|
|
* Print string containing blank separated list of domain names
|
|
* as a comma separated list of double-quote delimited strings.
|
|
*
|
|
* e.g. "eng.apple.com. marketing.apple.com."
|
|
*
|
|
* will be translated to
|
|
*
|
|
* "eng.apple.com.", "marketing.apple.com."
|
|
*/
|
|
void
|
|
pretty_print_domain_list(unsigned char *src, size_t srclen,
|
|
unsigned char *buf, size_t buflen)
|
|
{
|
|
char *dupnames, *hn, *inputstring;
|
|
int count;
|
|
|
|
memset(buf, 0, buflen);
|
|
|
|
/*
|
|
* N.B.: option data is *NOT* guaranteed to be NUL
|
|
* terminated. Avoid strlen(), strdup(), etc.!
|
|
*/
|
|
if (srclen >= DHCP_DOMAIN_SEARCH_LEN || src[0] == '\0')
|
|
return;
|
|
|
|
inputstring = malloc(srclen + 1);
|
|
if (inputstring == NULL)
|
|
fatal("domain name list");
|
|
memcpy(inputstring, src, srclen);
|
|
inputstring[srclen] = '\0';
|
|
dupnames = inputstring;
|
|
|
|
count = 0;
|
|
while ((hn = strsep(&inputstring, " \t")) != NULL) {
|
|
if (strlen(hn) == 0)
|
|
continue;
|
|
if (res_hnok(hn) == 0)
|
|
goto bad;
|
|
if (count > 0)
|
|
strlcat(buf, ", ", buflen);
|
|
strlcat(buf, "\"", buflen);
|
|
strlcat(buf, hn, buflen);
|
|
if (strlcat(buf, "\"", buflen) >= buflen)
|
|
goto bad;
|
|
count++;
|
|
if (count > DHCP_DOMAIN_SEARCH_CNT)
|
|
goto bad;
|
|
}
|
|
|
|
free(dupnames);
|
|
return;
|
|
|
|
bad:
|
|
free(dupnames);
|
|
memset(buf, 0, buflen);
|
|
}
|
|
|
|
/*
|
|
* Format the specified option so that a human can easily read it.
|
|
*/
|
|
char *
|
|
pretty_print_option(unsigned int code, struct option_data *option,
|
|
int emit_punct)
|
|
{
|
|
static char optbuf[8192]; /* XXX */
|
|
char fmtbuf[32];
|
|
struct in_addr foo;
|
|
unsigned char *data = option->data;
|
|
unsigned char *dp = data;
|
|
char *op = optbuf, *buf, *name, *fmt;
|
|
int hunksize = 0, numhunk = -1, numelem = 0;
|
|
int i, j, k, opleft = sizeof(optbuf);
|
|
int len = option->len;
|
|
int opcount = 0;
|
|
int32_t int32val;
|
|
uint32_t uint32val;
|
|
uint16_t uint16val;
|
|
char comma;
|
|
|
|
memset(optbuf, 0, sizeof(optbuf));
|
|
|
|
/* Code should be between 0 and 255. */
|
|
if (code > 255) {
|
|
log_warnx("%s: pretty_print_option: bad code %d", log_procname,
|
|
code);
|
|
goto done;
|
|
}
|
|
|
|
if (emit_punct != 0)
|
|
comma = ',';
|
|
else
|
|
comma = ' ';
|
|
|
|
/* Handle the princess class options with weirdo formats. */
|
|
switch (code) {
|
|
case DHO_CLASSLESS_STATIC_ROUTES:
|
|
case DHO_CLASSLESS_MS_STATIC_ROUTES:
|
|
pretty_print_classless_routes(dp, len, optbuf, sizeof(optbuf));
|
|
goto done;
|
|
case DHO_DOMAIN_SEARCH:
|
|
pretty_print_domain_list(dp, len, optbuf, sizeof(optbuf));
|
|
goto done;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
name = code_to_name(code);
|
|
fmt = code_to_format(code);
|
|
|
|
/* Figure out the size of the data. */
|
|
for (i = 0; fmt[i]; i++) {
|
|
if (numhunk == 0) {
|
|
log_warnx("%s: %s: excess information in format "
|
|
"string: %s", log_procname, name, &fmt[i]);
|
|
goto done;
|
|
}
|
|
numelem++;
|
|
fmtbuf[i] = fmt[i];
|
|
switch (fmt[i]) {
|
|
case 'A':
|
|
--numelem;
|
|
fmtbuf[i] = 0;
|
|
numhunk = 0;
|
|
if (hunksize == 0) {
|
|
log_warnx("%s: %s: no size indicator before A"
|
|
" in format string: %s", log_procname,
|
|
name, fmt);
|
|
goto done;
|
|
}
|
|
break;
|
|
case 'X':
|
|
for (k = 0; k < len; k++)
|
|
if (isascii(data[k]) == 0 ||
|
|
isprint(data[k]) == 0)
|
|
break;
|
|
if (k == len) {
|
|
fmtbuf[i] = 't';
|
|
numhunk = -2;
|
|
} else {
|
|
hunksize++;
|
|
comma = ':';
|
|
numhunk = 0;
|
|
}
|
|
fmtbuf[i + 1] = 0;
|
|
break;
|
|
case 't':
|
|
fmtbuf[i + 1] = 0;
|
|
numhunk = -2;
|
|
break;
|
|
case 'I':
|
|
case 'l':
|
|
case 'L':
|
|
hunksize += 4;
|
|
break;
|
|
case 'S':
|
|
hunksize += 2;
|
|
break;
|
|
case 'B':
|
|
case 'f':
|
|
hunksize++;
|
|
break;
|
|
case 'e':
|
|
break;
|
|
default:
|
|
log_warnx("%s: %s: garbage in format string: %s",
|
|
log_procname, name, &fmt[i]);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* Check for too few bytes. */
|
|
if (hunksize > len) {
|
|
log_warnx("%s: %s: expecting at least %d bytes; got %d",
|
|
log_procname, name, hunksize, len);
|
|
goto done;
|
|
}
|
|
/* Check for too many bytes. */
|
|
if (numhunk == -1 && hunksize < len) {
|
|
log_warnx("%s: %s: expecting only %d bytes: got %d",
|
|
log_procname, name, hunksize, len);
|
|
goto done;
|
|
}
|
|
|
|
/* If this is an array, compute its size. */
|
|
if (numhunk == 0)
|
|
numhunk = len / hunksize;
|
|
/* See if we got an exact number of hunks. */
|
|
if (numhunk > 0 && numhunk * hunksize != len) {
|
|
log_warnx("%s: %s: expecting %d bytes: got %d", log_procname,
|
|
name, numhunk * hunksize, len);
|
|
goto done;
|
|
}
|
|
|
|
/* A one-hunk array prints the same as a single hunk. */
|
|
if (numhunk < 0)
|
|
numhunk = 1;
|
|
|
|
/* Cycle through the array (or hunk) printing the data. */
|
|
for (i = 0; i < numhunk; i++) {
|
|
for (j = 0; j < numelem; j++) {
|
|
switch (fmtbuf[j]) {
|
|
case 't':
|
|
buf = pretty_print_string(dp, len, emit_punct);
|
|
if (buf == NULL)
|
|
opcount = -1;
|
|
else
|
|
opcount = strlcat(op, buf, opleft);
|
|
break;
|
|
case 'I':
|
|
memcpy(&foo.s_addr, dp, sizeof(foo.s_addr));
|
|
opcount = snprintf(op, opleft, "%s",
|
|
inet_ntoa(foo));
|
|
dp += sizeof(foo.s_addr);
|
|
break;
|
|
case 'l':
|
|
memcpy(&int32val, dp, sizeof(int32val));
|
|
opcount = snprintf(op, opleft, "%d",
|
|
ntohl(int32val));
|
|
dp += sizeof(int32val);
|
|
break;
|
|
case 'L':
|
|
memcpy(&uint32val, dp, sizeof(uint32val));
|
|
opcount = snprintf(op, opleft, "%u",
|
|
ntohl(uint32val));
|
|
dp += sizeof(uint32val);
|
|
break;
|
|
case 'S':
|
|
memcpy(&uint16val, dp, sizeof(uint16val));
|
|
opcount = snprintf(op, opleft, "%hu",
|
|
ntohs(uint16val));
|
|
dp += sizeof(uint16val);
|
|
break;
|
|
case 'B':
|
|
opcount = snprintf(op, opleft, "%u", *dp);
|
|
dp++;
|
|
break;
|
|
case 'X':
|
|
opcount = snprintf(op, opleft, "%x", *dp);
|
|
dp++;
|
|
break;
|
|
case 'f':
|
|
opcount = snprintf(op, opleft, "%s",
|
|
*dp ? "true" : "false");
|
|
dp++;
|
|
break;
|
|
default:
|
|
log_warnx("%s: unexpected format code %c",
|
|
log_procname, fmtbuf[j]);
|
|
goto toobig;
|
|
}
|
|
if (opcount < 0 || opcount >= opleft)
|
|
goto toobig;
|
|
opleft -= opcount;
|
|
op += opcount;
|
|
if (j + 1 < numelem && comma != ':') {
|
|
opcount = snprintf(op, opleft, " ");
|
|
if (opcount < 0 || opcount >= opleft)
|
|
goto toobig;
|
|
opleft -= opcount;
|
|
op += opcount;
|
|
}
|
|
}
|
|
if (i + 1 < numhunk) {
|
|
opcount = snprintf(op, opleft, "%c", comma);
|
|
if (opcount < 0 || opcount >= opleft)
|
|
goto toobig;
|
|
opleft -= opcount;
|
|
op += opcount;
|
|
}
|
|
}
|
|
|
|
done:
|
|
return optbuf;
|
|
|
|
toobig:
|
|
memset(optbuf, 0, sizeof(optbuf));
|
|
return optbuf;
|
|
}
|
|
|
|
struct option_data *
|
|
unpack_options(struct dhcp_packet *packet)
|
|
{
|
|
static struct option_data options[DHO_COUNT];
|
|
int i;
|
|
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
free(options[i].data);
|
|
options[i].data = NULL;
|
|
options[i].len = 0;
|
|
}
|
|
|
|
if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) {
|
|
/* Parse the BOOTP/DHCP options field. */
|
|
parse_option_buffer(options, &packet->options[4],
|
|
sizeof(packet->options) - 4);
|
|
|
|
/* DHCP packets can also use overload areas for options. */
|
|
if (options[DHO_DHCP_MESSAGE_TYPE].data != NULL &&
|
|
options[DHO_DHCP_OPTION_OVERLOAD].data != NULL) {
|
|
if ((options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) !=
|
|
0)
|
|
parse_option_buffer(options,
|
|
(unsigned char *)packet->file,
|
|
sizeof(packet->file));
|
|
if ((options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) !=
|
|
0)
|
|
parse_option_buffer(options,
|
|
(unsigned char *)packet->sname,
|
|
sizeof(packet->sname));
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
void
|
|
merge_option_data(char *fmt, struct option_data *first,
|
|
struct option_data *second, struct option_data *dest)
|
|
{
|
|
int space = 0;
|
|
|
|
free(dest->data);
|
|
dest->data = NULL;
|
|
dest->len = first->len + second->len;
|
|
if (dest->len == 0)
|
|
return;
|
|
|
|
/*
|
|
* N.B.: option data is *NOT* guaranteed to be NUL
|
|
* terminated. Avoid strlen(), strdup(), etc.!
|
|
*/
|
|
if (fmt[0] == 'D') {
|
|
if (first->len > 0 && second->len > 0)
|
|
space = 1;
|
|
}
|
|
|
|
dest->len += space;
|
|
dest->data = malloc(dest->len);
|
|
if (dest->data == NULL)
|
|
fatal("merged option data");
|
|
|
|
memcpy(dest->data, first->data, first->len);
|
|
if (space == 1)
|
|
dest->data[first->len] = ' ';
|
|
memcpy(dest->data + first->len + space, second->data, second->len);
|
|
}
|