/* $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 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 #include #include #include #include #include #include #include #include #include #include #include #include #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); }