1*642cc348Skrw /* $OpenBSD: options.c,v 1.83 2017/03/26 21:33:36 krw Exp $ */ 29a2590e5Sderaadt 3e7eb2effShenning /* DHCP options parsing and reassembly. */ 49a2590e5Sderaadt 59a2590e5Sderaadt /* 69a2590e5Sderaadt * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. 79a2590e5Sderaadt * All rights reserved. 89a2590e5Sderaadt * 99a2590e5Sderaadt * Redistribution and use in source and binary forms, with or without 109a2590e5Sderaadt * modification, are permitted provided that the following conditions 119a2590e5Sderaadt * are met: 129a2590e5Sderaadt * 139a2590e5Sderaadt * 1. Redistributions of source code must retain the above copyright 149a2590e5Sderaadt * notice, this list of conditions and the following disclaimer. 159a2590e5Sderaadt * 2. Redistributions in binary form must reproduce the above copyright 169a2590e5Sderaadt * notice, this list of conditions and the following disclaimer in the 179a2590e5Sderaadt * documentation and/or other materials provided with the distribution. 189a2590e5Sderaadt * 3. Neither the name of The Internet Software Consortium nor the names 199a2590e5Sderaadt * of its contributors may be used to endorse or promote products derived 209a2590e5Sderaadt * from this software without specific prior written permission. 219a2590e5Sderaadt * 229a2590e5Sderaadt * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 239a2590e5Sderaadt * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 249a2590e5Sderaadt * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 259a2590e5Sderaadt * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 269a2590e5Sderaadt * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 279a2590e5Sderaadt * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 289a2590e5Sderaadt * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 299a2590e5Sderaadt * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 309a2590e5Sderaadt * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 319a2590e5Sderaadt * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 329a2590e5Sderaadt * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 339a2590e5Sderaadt * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 349a2590e5Sderaadt * SUCH DAMAGE. 359a2590e5Sderaadt * 369a2590e5Sderaadt * This software has been written for the Internet Software Consortium 379a2590e5Sderaadt * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 389a2590e5Sderaadt * Enterprises. To learn more about the Internet Software Consortium, 399a2590e5Sderaadt * see ``http://www.vix.com/isc''. To learn more about Vixie 409a2590e5Sderaadt * Enterprises, see ``http://www.vix.com''. 419a2590e5Sderaadt */ 429a2590e5Sderaadt 43711cae1eSkrw #include <sys/queue.h> 44711cae1eSkrw #include <sys/socket.h> 459a2590e5Sderaadt 46711cae1eSkrw #include <arpa/inet.h> 47711cae1eSkrw 48711cae1eSkrw #include <net/if.h> 49711cae1eSkrw 50711cae1eSkrw #include <netinet/in.h> 51711cae1eSkrw #include <netinet/if_ether.h> 52711cae1eSkrw 53711cae1eSkrw #include <ctype.h> 54711cae1eSkrw #include <signal.h> 55711cae1eSkrw #include <stdio.h> 56711cae1eSkrw #include <stdlib.h> 57711cae1eSkrw #include <string.h> 588d2bd14bSkrw #include <vis.h> 598d2bd14bSkrw 60711cae1eSkrw #include "dhcp.h" 61711cae1eSkrw #include "dhcpd.h" 62385a6373Skrw #include "log.h" 63711cae1eSkrw 6402e02bd5Skrw int parse_option_buffer(struct option_data *, unsigned char *, int); 65968fe952Skrw int expand_search_domain_name(unsigned char *, size_t, int *, unsigned char *); 669a2590e5Sderaadt 67c714dadcShenning /* 68c714dadcShenning * Parse options out of the specified buffer, storing addresses of 6992018899Skrw * option values in options. Return 0 if errors, 1 if not. 70c714dadcShenning */ 7102e02bd5Skrw int 724f062ee3Skrw parse_option_buffer(struct option_data *options, unsigned char *buffer, 734f062ee3Skrw int length) 749a2590e5Sderaadt { 75285f06efSderaadt unsigned char *s, *t, *end = buffer + length; 76285f06efSderaadt int len, code; 779a2590e5Sderaadt 789a2590e5Sderaadt for (s = buffer; *s != DHO_END && s < end; ) { 799a2590e5Sderaadt code = s[0]; 809a2590e5Sderaadt 819a2590e5Sderaadt /* Pad options don't have a length - just skip them. */ 829a2590e5Sderaadt if (code == DHO_PAD) { 83f1e89499Shenning s++; 849a2590e5Sderaadt continue; 859a2590e5Sderaadt } 869a2590e5Sderaadt 87c714dadcShenning /* 8899c003b1Skrw * All options other than DHO_PAD and DHO_END have a one-byte 8999c003b1Skrw * length field. It could be 0! Make sure that the length byte 9099c003b1Skrw * is present, and all the data is available. 91c714dadcShenning */ 9299c003b1Skrw if (s + 1 < end) { 939a2590e5Sderaadt len = s[1]; 9499c003b1Skrw if (s + 1 + len < end) { 9599c003b1Skrw ; /* option data is all there. */ 9699c003b1Skrw } else { 97385a6373Skrw log_warnx("option %s (%d) larger than buffer.", 98b6fc88b9Skrw dhcp_options[code].name, len); 9902e02bd5Skrw return (0); 1009a2590e5Sderaadt } 10199c003b1Skrw } else { 102385a6373Skrw log_warnx("option %s has no length field.", 10399c003b1Skrw dhcp_options[code].name); 10499c003b1Skrw return (0); 10599c003b1Skrw } 106df453039Skrw 107df453039Skrw /* 108df453039Skrw * Strip trailing NULs from ascii ('t') options. They 109df453039Skrw * will be treated as DHO_PAD options. i.e. ignored. RFC 2132 110df453039Skrw * says "Options containing NVT ASCII data SHOULD NOT include 111df453039Skrw * a trailing NULL; however, the receiver of such options 112df453039Skrw * MUST be prepared to delete trailing nulls if they exist." 113df453039Skrw */ 114df453039Skrw if (dhcp_options[code].format[0] == 't') { 11599c003b1Skrw while (len > 0 && s[len + 1] == '\0') 11699c003b1Skrw len--; 117df453039Skrw } 118df453039Skrw 119c714dadcShenning /* 120c714dadcShenning * If we haven't seen this option before, just make 121c714dadcShenning * space for it and copy it there. 122c714dadcShenning */ 1234f062ee3Skrw if (!options[code].data) { 1248e916ab9Shenning if (!(t = calloc(1, len + 1))) 125385a6373Skrw fatalx("Can't allocate storage for option %s.", 1269a2590e5Sderaadt dhcp_options[code].name); 127c714dadcShenning /* 128c714dadcShenning * Copy and NUL-terminate the option (in case 129cff08477Sstevesk * it's an ASCII string). 130c714dadcShenning */ 1319a2590e5Sderaadt memcpy(t, &s[2], len); 1329a2590e5Sderaadt t[len] = 0; 1334f062ee3Skrw options[code].len = len; 1344f062ee3Skrw options[code].data = t; 1359a2590e5Sderaadt } else { 136c714dadcShenning /* 137c714dadcShenning * If it's a repeat, concatenate it to whatever 13892018899Skrw * we last saw. 139c714dadcShenning */ 1404f062ee3Skrw t = calloc(1, len + options[code].len + 1); 1419a2590e5Sderaadt if (!t) 142385a6373Skrw fatalx("Can't expand storage for option %s.", 1439a2590e5Sderaadt dhcp_options[code].name); 1444f062ee3Skrw memcpy(t, options[code].data, options[code].len); 1454f062ee3Skrw memcpy(t + options[code].len, &s[2], len); 1464f062ee3Skrw options[code].len += len; 1474f062ee3Skrw t[options[code].len] = 0; 1484f062ee3Skrw free(options[code].data); 1494f062ee3Skrw options[code].data = t; 1509a2590e5Sderaadt } 1519a2590e5Sderaadt s += len + 2; 1529a2590e5Sderaadt } 15302e02bd5Skrw 15402e02bd5Skrw return (1); 1559a2590e5Sderaadt } 1569a2590e5Sderaadt 157c714dadcShenning /* 15896978980Skrw * Copy as many options as fit in buflen bytes of buf. Return the 15996978980Skrw * offset of the start of the last option copied. A caller can check 16096978980Skrw * to see if it's DHO_END to decide if all the options were copied. 161c714dadcShenning */ 162c714dadcShenning int 1636a2ee11aSmpi cons_options(struct interface_info *ifi, struct option_data *options) 1649a2590e5Sderaadt { 1656a2ee11aSmpi struct client_state *client = ifi->client; 166e7cf2d10Skrw unsigned char *buf = client->bootrequest_packet.options; 167d6a67f0fSkrw int buflen = 576 - DHCP_FIXED_LEN; 16896978980Skrw int ix, incr, length, bufix, code, lastopt = -1; 1699a2590e5Sderaadt 170736b0ed2Skrw memset(buf, 0, buflen); 1719a2590e5Sderaadt 17296978980Skrw memcpy(buf, DHCP_OPTIONS_COOKIE, 4); 173d6a67f0fSkrw if (options[DHO_DHCP_MESSAGE_TYPE].data) { 174d6a67f0fSkrw memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3); 175d6a67f0fSkrw buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0]; 176d6a67f0fSkrw bufix = 7; 177d6a67f0fSkrw } else 17896978980Skrw bufix = 4; 1799a2590e5Sderaadt 18096978980Skrw for (code = DHO_SUBNET_MASK; code < DHO_END; code++) { 181d6a67f0fSkrw if (!options[code].data || code == DHO_DHCP_MESSAGE_TYPE) 1829a2590e5Sderaadt continue; 1839a2590e5Sderaadt 184d7d9bbf5Skrw length = options[code].len; 18596978980Skrw if (bufix + length + 2*((length+254)/255) >= buflen) 18696978980Skrw return (lastopt); 1879a2590e5Sderaadt 18896978980Skrw lastopt = bufix; 1899a2590e5Sderaadt ix = 0; 1909a2590e5Sderaadt 1919a2590e5Sderaadt while (length) { 19296978980Skrw incr = length > 255 ? 255 : length; 1939a2590e5Sderaadt 19496978980Skrw buf[bufix++] = code; 19596978980Skrw buf[bufix++] = incr; 19696978980Skrw memcpy(buf + bufix, options[code].data + ix, incr); 1979a2590e5Sderaadt 1989a2590e5Sderaadt length -= incr; 1999a2590e5Sderaadt ix += incr; 2006fc9f4f6Skrw bufix += incr; 2019a2590e5Sderaadt } 2029a2590e5Sderaadt } 20396978980Skrw 20496978980Skrw if (bufix < buflen) { 20596978980Skrw buf[bufix] = DHO_END; 20696978980Skrw lastopt = bufix; 20796978980Skrw } 20896978980Skrw 20996978980Skrw return (lastopt); 2109a2590e5Sderaadt } 2119a2590e5Sderaadt 212c714dadcShenning /* 213482123e8Skrw * Use vis() to encode characters of src and append encoded characters onto 214482123e8Skrw * dst. Also encode ", ', $, ` and \, to ensure resulting strings can be 215482123e8Skrw * represented as '"' delimited strings and safely passed to scripts. Surround 216482123e8Skrw * result with double quotes if emit_punct is true. 217482123e8Skrw */ 218482123e8Skrw int 219482123e8Skrw pretty_print_string(unsigned char *dst, size_t dstlen, unsigned char *src, 220482123e8Skrw size_t srclen, int emit_punct) 221482123e8Skrw { 222482123e8Skrw char visbuf[5]; 223482123e8Skrw unsigned char *origsrc = src; 224482123e8Skrw int opcount = 0, total = 0; 225482123e8Skrw 226482123e8Skrw if (emit_punct) { 227482123e8Skrw opcount = snprintf(dst, dstlen, "\""); 228482123e8Skrw if (opcount == -1) 229482123e8Skrw return (-1); 230482123e8Skrw total += opcount; 231482123e8Skrw if (opcount >= dstlen) 232482123e8Skrw goto done; 233482123e8Skrw dstlen -= opcount; 234482123e8Skrw dst += opcount; 235482123e8Skrw } 236482123e8Skrw 237482123e8Skrw for (; src < origsrc + srclen; src++) { 238482123e8Skrw if (*src && strchr("\"'$`\\", *src)) 239*642cc348Skrw vis(visbuf, *src, VIS_ALL | VIS_OCTAL, *src+1); 240*642cc348Skrw else 241482123e8Skrw vis(visbuf, *src, VIS_OCTAL, *src+1); 242482123e8Skrw opcount = snprintf(dst, dstlen, "%s", visbuf); 243482123e8Skrw if (opcount == -1) 244482123e8Skrw return (-1); 245482123e8Skrw total += opcount; 246482123e8Skrw if (opcount >= dstlen) 247482123e8Skrw goto done; 248482123e8Skrw dstlen -= opcount; 249482123e8Skrw dst += opcount; 250482123e8Skrw } 251482123e8Skrw 252482123e8Skrw if (emit_punct) { 253482123e8Skrw opcount = snprintf(dst, dstlen, "\""); 254482123e8Skrw if (opcount == -1) 255482123e8Skrw return (-1); 256482123e8Skrw total += opcount; 257482123e8Skrw if (opcount >= dstlen) 258482123e8Skrw goto done; 259482123e8Skrw dstlen -= opcount; 260482123e8Skrw dst += opcount; 261482123e8Skrw } 262482123e8Skrw done: 263482123e8Skrw return (total); 264482123e8Skrw } 265482123e8Skrw 266482123e8Skrw /* 2675714f486Skrw * Must special case *_CLASSLESS_* route options due to the variable size 2685714f486Skrw * of the CIDR element in its CIA format. 2695714f486Skrw */ 2705714f486Skrw int 2715714f486Skrw pretty_print_classless_routes(unsigned char *dst, size_t dstlen, 2725714f486Skrw unsigned char *src, size_t srclen) 2735714f486Skrw { 2745714f486Skrw struct in_addr mask, gateway; 2755714f486Skrw int opcount = 0, total = 0, bits, bytes; 2765714f486Skrw char ntoabuf[INET_ADDRSTRLEN]; 2775714f486Skrw 2785714f486Skrw while (srclen && dstlen) { 2795714f486Skrw bits = *src; 2805714f486Skrw src++; 2815714f486Skrw srclen--; 2825714f486Skrw bytes = (bits + 7) / 8; 2835714f486Skrw if (srclen < bytes || bytes > sizeof(mask.s_addr)) 2845714f486Skrw break; 2855714f486Skrw memset(&mask, 0, sizeof(mask)); 2865714f486Skrw memcpy(&mask.s_addr, src, bytes); 2875714f486Skrw src += bytes; 2885714f486Skrw srclen -= bytes; 2895714f486Skrw strlcpy(ntoabuf, inet_ntoa(mask), sizeof(ntoabuf)); 2905714f486Skrw if (srclen < sizeof(gateway.s_addr)) 2915714f486Skrw break; 2925714f486Skrw memcpy(&gateway.s_addr, src, sizeof(gateway.s_addr)); 2935714f486Skrw src += sizeof(gateway.s_addr); 2945714f486Skrw srclen -= sizeof(gateway.s_addr); 2955714f486Skrw opcount = snprintf(dst, dstlen, "%s%s/%u %s", 2965714f486Skrw total ? ", " : "", ntoabuf, bits, 2975714f486Skrw inet_ntoa(gateway)); 2985714f486Skrw if (opcount == -1) 2995714f486Skrw return (-1); 3005714f486Skrw total += opcount; 3015714f486Skrw if (opcount >= dstlen) 3025714f486Skrw break; 3035714f486Skrw dst += opcount; 3045714f486Skrw dstlen -= opcount; 3055714f486Skrw } 3065714f486Skrw 3075714f486Skrw return (total); 3085714f486Skrw } 3095714f486Skrw 310968fe952Skrw int 311968fe952Skrw expand_search_domain_name(unsigned char *src, size_t srclen, int *offset, 312968fe952Skrw unsigned char *domain_search) 313968fe952Skrw { 314968fe952Skrw int domain_name_len, i, label_len, pointer, pointed_len; 315968fe952Skrw char *cursor; 316968fe952Skrw 317968fe952Skrw cursor = domain_search + strlen(domain_search); 318968fe952Skrw domain_name_len = 0; 319968fe952Skrw 320968fe952Skrw i = *offset; 321968fe952Skrw while (i <= srclen) { 322968fe952Skrw label_len = src[i]; 323968fe952Skrw if (label_len == 0) { 324968fe952Skrw /* 325968fe952Skrw * A zero-length label marks the end of this 326968fe952Skrw * domain name. 327968fe952Skrw */ 328968fe952Skrw *offset = i + 1; 329968fe952Skrw return (domain_name_len); 330968fe952Skrw } else if (label_len & 0xC0) { 331968fe952Skrw /* This is a pointer to another list of labels. */ 332968fe952Skrw if (i + 1 >= srclen) { 333968fe952Skrw /* The pointer is truncated. */ 334385a6373Skrw log_warnx("Truncated pointer in DHCP Domain " 335968fe952Skrw "Search option."); 336968fe952Skrw return (-1); 337968fe952Skrw } 338968fe952Skrw 339968fe952Skrw pointer = ((label_len & ~(0xC0)) << 8) + src[i + 1]; 340968fe952Skrw if (pointer >= *offset) { 341968fe952Skrw /* 342968fe952Skrw * The pointer must indicates a prior 343968fe952Skrw * occurance. 344968fe952Skrw */ 345385a6373Skrw log_warnx("Invalid forward pointer in DHCP " 346968fe952Skrw "Domain Search option compression."); 347968fe952Skrw return (-1); 348968fe952Skrw } 349968fe952Skrw 350968fe952Skrw pointed_len = expand_search_domain_name(src, srclen, 351968fe952Skrw &pointer, domain_search); 352968fe952Skrw domain_name_len += pointed_len; 353968fe952Skrw 354968fe952Skrw *offset = i + 2; 355968fe952Skrw return (domain_name_len); 356968fe952Skrw } 357968fe952Skrw if (i + label_len + 1 > srclen) { 358385a6373Skrw log_warnx("Truncated label in DHCP Domain Search " 359968fe952Skrw "option."); 360968fe952Skrw return (-1); 361968fe952Skrw } 362968fe952Skrw /* 363968fe952Skrw * Update the domain name length with the length of the 364968fe952Skrw * current label, plus a trailing dot ('.'). 365968fe952Skrw */ 366968fe952Skrw domain_name_len += label_len + 1; 367968fe952Skrw 368968fe952Skrw if (strlen(domain_search) + domain_name_len >= 369968fe952Skrw DHCP_DOMAIN_SEARCH_LEN) { 370385a6373Skrw log_warnx("Domain search list too long."); 371968fe952Skrw return (-1); 372968fe952Skrw } 373968fe952Skrw 374968fe952Skrw /* Copy the label found. */ 375968fe952Skrw memcpy(cursor, src + i + 1, label_len); 376968fe952Skrw cursor[label_len] = '.'; 377968fe952Skrw 378968fe952Skrw /* Move cursor. */ 379968fe952Skrw i += label_len + 1; 380968fe952Skrw cursor += label_len + 1; 381968fe952Skrw } 382968fe952Skrw 383385a6373Skrw log_warnx("Truncated DHCP Domain Search option."); 384968fe952Skrw 385968fe952Skrw return (-1); 386968fe952Skrw } 387968fe952Skrw 388968fe952Skrw /* 389968fe952Skrw * Must special case DHO_DOMAIN_SEARCH because it is encoded as described 390968fe952Skrw * in RFC 1035 section 4.1.4. 391968fe952Skrw */ 392968fe952Skrw int 393968fe952Skrw pretty_print_domain_search(unsigned char *dst, size_t dstlen, 394968fe952Skrw unsigned char *src, size_t srclen) 395968fe952Skrw { 396968fe952Skrw int offset, len, expanded_len, domains; 397968fe952Skrw unsigned char *domain_search, *cursor; 398968fe952Skrw 399968fe952Skrw domain_search = calloc(1, DHCP_DOMAIN_SEARCH_LEN); 400968fe952Skrw if (domain_search == NULL) 401385a6373Skrw fatalx("Can't allocate storage for expanded domain-search\n"); 402968fe952Skrw 403968fe952Skrw /* Compute expanded length. */ 404968fe952Skrw expanded_len = len = 0; 405968fe952Skrw domains = 0; 406968fe952Skrw offset = 0; 407968fe952Skrw while (offset < srclen) { 408968fe952Skrw cursor = domain_search + strlen(domain_search); 409968fe952Skrw if (domain_search[0]) { 410968fe952Skrw *cursor = ' '; 411968fe952Skrw expanded_len++; 412968fe952Skrw } 413968fe952Skrw len = expand_search_domain_name(src, srclen, &offset, 414968fe952Skrw domain_search); 415968fe952Skrw if (len == -1) { 416968fe952Skrw free(domain_search); 417968fe952Skrw return (-1); 418968fe952Skrw } 419968fe952Skrw domains++; 420968fe952Skrw expanded_len += len; 421968fe952Skrw if (domains > DHCP_DOMAIN_SEARCH_CNT) { 422968fe952Skrw free(domain_search); 423968fe952Skrw return (-1); 424968fe952Skrw } 425968fe952Skrw } 426968fe952Skrw 427968fe952Skrw strlcat(dst, domain_search, dstlen); 428968fe952Skrw free(domain_search); 429968fe952Skrw 430968fe952Skrw return (0); 431968fe952Skrw } 432968fe952Skrw 4335714f486Skrw /* 434c714dadcShenning * Format the specified option so that a human can easily read it. 435c714dadcShenning */ 436c714dadcShenning char * 437acf4c28bSkrw pretty_print_option(unsigned int code, struct option_data *option, 438acf4c28bSkrw int emit_punct) 4399a2590e5Sderaadt { 4409a2590e5Sderaadt static char optbuf[32768]; /* XXX */ 441285f06efSderaadt int hunksize = 0, numhunk = -1, numelem = 0; 442482123e8Skrw char fmtbuf[32], *op = optbuf; 443285f06efSderaadt int i, j, k, opleft = sizeof(optbuf); 444acf4c28bSkrw unsigned char *data = option->data; 4459a2590e5Sderaadt unsigned char *dp = data; 446acf4c28bSkrw int len = option->len; 447f3a8c5fdSkrw int opcount = 0; 4489a2590e5Sderaadt struct in_addr foo; 4499a2590e5Sderaadt char comma; 450bce09e58Skrw int32_t int32val; 451bce09e58Skrw u_int32_t uint32val; 452bce09e58Skrw u_int16_t uint16val; 4539a2590e5Sderaadt 4542f18daabSkrw memset(optbuf, 0, sizeof(optbuf)); 4552f18daabSkrw 4569a2590e5Sderaadt /* Code should be between 0 and 255. */ 4572f18daabSkrw if (code > 255) { 458385a6373Skrw log_warnx("pretty_print_option: bad code %d", code); 4592f18daabSkrw goto done; 4602f18daabSkrw } 4619a2590e5Sderaadt 462acf4c28bSkrw if (emit_punct) 4639a2590e5Sderaadt comma = ','; 4649a2590e5Sderaadt else 4659a2590e5Sderaadt comma = ' '; 4669a2590e5Sderaadt 4675714f486Skrw /* Handle the princess class options with weirdo formats. */ 4685714f486Skrw switch (code) { 4695714f486Skrw case DHO_CLASSLESS_STATIC_ROUTES: 4705714f486Skrw case DHO_CLASSLESS_MS_STATIC_ROUTES: 4715714f486Skrw opcount = pretty_print_classless_routes(op, opleft, dp, len); 4725714f486Skrw if (opcount >= opleft || opcount == -1) 4735714f486Skrw goto toobig; 4745714f486Skrw goto done; 4755714f486Skrw default: 4765714f486Skrw break; 4775714f486Skrw } 4785714f486Skrw 4799a2590e5Sderaadt /* Figure out the size of the data. */ 4809a2590e5Sderaadt for (i = 0; dhcp_options[code].format[i]; i++) { 4819a2590e5Sderaadt if (!numhunk) { 482833082e5Skrw log_warnx("%s: Excess information in format string: " 483833082e5Skrw "%s", dhcp_options[code].name, 4849a2590e5Sderaadt &(dhcp_options[code].format[i])); 4852f18daabSkrw goto done; 4869a2590e5Sderaadt } 4879a2590e5Sderaadt numelem++; 4889a2590e5Sderaadt fmtbuf[i] = dhcp_options[code].format[i]; 4899a2590e5Sderaadt switch (dhcp_options[code].format[i]) { 4909a2590e5Sderaadt case 'A': 4919a2590e5Sderaadt --numelem; 4929a2590e5Sderaadt fmtbuf[i] = 0; 4939a2590e5Sderaadt numhunk = 0; 49429432cd9Sphessler if (hunksize == 0) { 495385a6373Skrw log_warnx("%s: no size indicator before A" 49629432cd9Sphessler " in format string: %s", 49729432cd9Sphessler dhcp_options[code].name, 49829432cd9Sphessler dhcp_options[code].format); 4992f18daabSkrw goto done; 50029432cd9Sphessler } 5019a2590e5Sderaadt break; 5029a2590e5Sderaadt case 'X': 503c714dadcShenning for (k = 0; k < len; k++) 5049a2590e5Sderaadt if (!isascii(data[k]) || 5059a2590e5Sderaadt !isprint(data[k])) 5069a2590e5Sderaadt break; 507b54c879eShenning if (k == len) { 5089a2590e5Sderaadt fmtbuf[i] = 't'; 5099a2590e5Sderaadt numhunk = -2; 5109a2590e5Sderaadt } else { 5119a2590e5Sderaadt hunksize++; 5129a2590e5Sderaadt comma = ':'; 5139a2590e5Sderaadt numhunk = 0; 5149a2590e5Sderaadt } 5159a2590e5Sderaadt fmtbuf[i + 1] = 0; 5169a2590e5Sderaadt break; 5179a2590e5Sderaadt case 't': 5189a2590e5Sderaadt fmtbuf[i + 1] = 0; 5199a2590e5Sderaadt numhunk = -2; 5209a2590e5Sderaadt break; 5219a2590e5Sderaadt case 'I': 5229a2590e5Sderaadt case 'l': 5239a2590e5Sderaadt case 'L': 5249a2590e5Sderaadt hunksize += 4; 5259a2590e5Sderaadt break; 5269a2590e5Sderaadt case 'S': 5279a2590e5Sderaadt hunksize += 2; 5289a2590e5Sderaadt break; 5299a2590e5Sderaadt case 'B': 5309a2590e5Sderaadt case 'f': 5319a2590e5Sderaadt hunksize++; 5329a2590e5Sderaadt break; 5339a2590e5Sderaadt case 'e': 5349a2590e5Sderaadt break; 5359a2590e5Sderaadt default: 536385a6373Skrw log_warnx("%s: garbage in format string: %s", 5379a2590e5Sderaadt dhcp_options[code].name, 5389a2590e5Sderaadt &(dhcp_options[code].format[i])); 5392f18daabSkrw goto done; 5409a2590e5Sderaadt } 5419a2590e5Sderaadt } 5429a2590e5Sderaadt 543d22f105fSkrw /* Check for too few bytes. */ 5449a2590e5Sderaadt if (hunksize > len) { 545385a6373Skrw log_warnx("%s: expecting at least %d bytes; got %d", 546c714dadcShenning dhcp_options[code].name, hunksize, len); 5472f18daabSkrw goto done; 5489a2590e5Sderaadt } 549d22f105fSkrw /* Check for too many bytes. */ 5502f18daabSkrw if (numhunk == -1 && hunksize < len) { 551385a6373Skrw log_warnx("%s: expecting only %d bytes: got %d", 55228f2359aSkrw dhcp_options[code].name, hunksize, len); 5532f18daabSkrw goto done; 5542f18daabSkrw } 5559a2590e5Sderaadt 5569a2590e5Sderaadt /* If this is an array, compute its size. */ 5579a2590e5Sderaadt if (!numhunk) 5589a2590e5Sderaadt numhunk = len / hunksize; 5599a2590e5Sderaadt /* See if we got an exact number of hunks. */ 5602f18daabSkrw if (numhunk > 0 && numhunk * hunksize != len) { 561385a6373Skrw log_warnx("%s: expecting %d bytes: got %d", 5622f18daabSkrw dhcp_options[code].name, numhunk * hunksize, len); 5632f18daabSkrw goto done; 5642f18daabSkrw } 5659a2590e5Sderaadt 5669a2590e5Sderaadt /* A one-hunk array prints the same as a single hunk. */ 5679a2590e5Sderaadt if (numhunk < 0) 5689a2590e5Sderaadt numhunk = 1; 5699a2590e5Sderaadt 5709a2590e5Sderaadt /* Cycle through the array (or hunk) printing the data. */ 5719a2590e5Sderaadt for (i = 0; i < numhunk; i++) { 5729a2590e5Sderaadt for (j = 0; j < numelem; j++) { 5739a2590e5Sderaadt switch (fmtbuf[j]) { 5749a2590e5Sderaadt case 't': 575482123e8Skrw opcount = pretty_print_string(op, opleft, 576482123e8Skrw dp, len, emit_punct); 5779a2590e5Sderaadt break; 5789a2590e5Sderaadt case 'I': 579e95625edSkrw memcpy(&foo.s_addr, dp, sizeof(foo.s_addr)); 580f3a8c5fdSkrw opcount = snprintf(op, opleft, "%s", 581f3a8c5fdSkrw inet_ntoa(foo)); 582e95625edSkrw dp += sizeof(foo.s_addr); 5839a2590e5Sderaadt break; 5849a2590e5Sderaadt case 'l': 585bce09e58Skrw memcpy(&int32val, dp, sizeof(int32val)); 586bce09e58Skrw opcount = snprintf(op, opleft, "%d", 587bce09e58Skrw ntohl(int32val)); 588bce09e58Skrw dp += sizeof(int32val); 5899a2590e5Sderaadt break; 5909a2590e5Sderaadt case 'L': 591bce09e58Skrw memcpy(&uint32val, dp, sizeof(uint32val)); 592bce09e58Skrw opcount = snprintf(op, opleft, "%u", 593bce09e58Skrw ntohl(uint32val)); 594bce09e58Skrw dp += sizeof(uint32val); 5959a2590e5Sderaadt break; 5969a2590e5Sderaadt case 'S': 597bce09e58Skrw memcpy(&uint16val, dp, sizeof(uint16val)); 598bce09e58Skrw opcount = snprintf(op, opleft, "%hu", 599bce09e58Skrw ntohs(uint16val)); 600bce09e58Skrw dp += sizeof(uint16val); 6019a2590e5Sderaadt break; 6029a2590e5Sderaadt case 'B': 603221bd6c0Skrw opcount = snprintf(op, opleft, "%u", *dp); 604de3ca9dbSkrw dp++; 6059a2590e5Sderaadt break; 606920d03efSkrw case 'X': 607de3ca9dbSkrw opcount = snprintf(op, opleft, "%x", *dp); 608de3ca9dbSkrw dp++; 6099a2590e5Sderaadt break; 6109a2590e5Sderaadt case 'f': 611f3a8c5fdSkrw opcount = snprintf(op, opleft, "%s", 612f3a8c5fdSkrw *dp ? "true" : "false"); 613de3ca9dbSkrw dp++; 6149a2590e5Sderaadt break; 6159a2590e5Sderaadt default: 616833082e5Skrw log_warnx("Unexpected format code %c", 617833082e5Skrw fmtbuf[j]); 6189a2590e5Sderaadt goto toobig; 619f3a8c5fdSkrw } 620f3a8c5fdSkrw if (opcount >= opleft || opcount == -1) 621f3a8c5fdSkrw goto toobig; 622f3a8c5fdSkrw opleft -= opcount; 623f3a8c5fdSkrw op += opcount; 6249a2590e5Sderaadt if (j + 1 < numelem && comma != ':') { 625f3a8c5fdSkrw opcount = snprintf(op, opleft, " "); 626f3a8c5fdSkrw if (opcount >= opleft || opcount == -1) 627f3a8c5fdSkrw goto toobig; 628f3a8c5fdSkrw opleft -= opcount; 629f3a8c5fdSkrw op += opcount; 6309a2590e5Sderaadt } 6319a2590e5Sderaadt } 6329a2590e5Sderaadt if (i + 1 < numhunk) { 633f3a8c5fdSkrw opcount = snprintf(op, opleft, "%c", comma); 634f3a8c5fdSkrw if (opcount >= opleft || opcount == -1) 6359a2590e5Sderaadt goto toobig; 636f3a8c5fdSkrw opleft -= opcount; 637f3a8c5fdSkrw op += opcount; 638f3a8c5fdSkrw } 6399a2590e5Sderaadt } 6402f18daabSkrw 6412f18daabSkrw done: 642c714dadcShenning return (optbuf); 6432f18daabSkrw 6449a2590e5Sderaadt toobig: 6452f18daabSkrw memset(optbuf, 0, sizeof(optbuf)); 6462f18daabSkrw return (optbuf); 6479a2590e5Sderaadt } 6489a2590e5Sderaadt 649c714dadcShenning void 650916c3997Smpi do_packet(struct interface_info *ifi, unsigned int from_port, 651916c3997Smpi struct in_addr from, struct ether_addr *hfrom) 6529a2590e5Sderaadt { 6536a2ee11aSmpi struct client_state *client = ifi->client; 65402e02bd5Skrw struct dhcp_packet *packet = &client->packet; 6554f062ee3Skrw struct option_data options[256]; 656b21b72f8Skrw struct reject_elem *ap; 65733b81fd8Smpi void (*handler)(struct interface_info *, struct in_addr, 65833b81fd8Smpi struct option_data *, char *); 6596896c986Skrw char *type, *info; 6606896c986Skrw int i, rslt, options_valid = 1; 6619a2590e5Sderaadt 662393831bbSkrw if (packet->hlen != ETHER_ADDR_LEN) { 663aff84b99Skrw #ifdef DEBUG 664385a6373Skrw log_debug("Discarding packet with hlen != %s (%u)", 665aff84b99Skrw ifi->name, packet->hlen); 66668c1ec45Skrw #endif /* DEBUG */ 667aff84b99Skrw return; 668393831bbSkrw } else if (memcmp(&ifi->hw_address, packet->chaddr, 669393831bbSkrw sizeof(ifi->hw_address))) { 670aff84b99Skrw #ifdef DEBUG 671833082e5Skrw log_debug("Discarding packet with chaddr != %s (%s)", 672833082e5Skrw ifi->name, 673aff84b99Skrw ether_ntoa((struct ether_addr *)packet->chaddr)); 67468c1ec45Skrw #endif /* DEBUG */ 6759a2590e5Sderaadt return; 6769a2590e5Sderaadt } 6779a2590e5Sderaadt 678aff84b99Skrw if (client->xid != client->packet.xid) { 679aff84b99Skrw #ifdef DEBUG 680385a6373Skrw log_debug("Discarding packet with XID != %u (%u)", client->xid, 681aff84b99Skrw client->packet.xid); 68268c1ec45Skrw #endif /* DEBUG */ 68302e02bd5Skrw return; 684aff84b99Skrw } 685aff84b99Skrw 686649a5e03Skrw TAILQ_FOREACH(ap, &config->reject_list, next) 687aff84b99Skrw if (from.s_addr == ap->addr.s_addr) { 688aff84b99Skrw #ifdef DEBUG 689833082e5Skrw log_debug("Discarding packet from address on reject " 690833082e5Skrw "list (%s)", inet_ntoa(from)); 69168c1ec45Skrw #endif /* DEBUG */ 692aff84b99Skrw return; 693aff84b99Skrw } 6949a2590e5Sderaadt 69502e02bd5Skrw memset(options, 0, sizeof(options)); 69602e02bd5Skrw 69702e02bd5Skrw if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) { 69802e02bd5Skrw /* Parse the BOOTP/DHCP options field. */ 69902e02bd5Skrw options_valid = parse_option_buffer(options, 70002e02bd5Skrw &packet->options[4], sizeof(packet->options) - 4); 70102e02bd5Skrw 70202e02bd5Skrw /* Only DHCP packets have overload areas for options. */ 70302e02bd5Skrw if (options_valid && 70402e02bd5Skrw options[DHO_DHCP_MESSAGE_TYPE].data && 70502e02bd5Skrw options[DHO_DHCP_OPTION_OVERLOAD].data) { 70602e02bd5Skrw if (options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) 70702e02bd5Skrw options_valid = parse_option_buffer(options, 70802e02bd5Skrw (unsigned char *)packet->file, 70902e02bd5Skrw sizeof(packet->file)); 71002e02bd5Skrw if (options_valid && 71102e02bd5Skrw options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) 71202e02bd5Skrw options_valid = parse_option_buffer(options, 71302e02bd5Skrw (unsigned char *)packet->sname, 71402e02bd5Skrw sizeof(packet->sname)); 71502e02bd5Skrw } 71618d08eb0Skrw 71718d08eb0Skrw /* 71818d08eb0Skrw * RFC 6842 says if the server sends a client identifier 71918d08eb0Skrw * that doesn't match then the packet must be dropped. 72018d08eb0Skrw */ 72118d08eb0Skrw i = DHO_DHCP_CLIENT_IDENTIFIER; 72218d08eb0Skrw if ((options[i].len != 0) && 72318d08eb0Skrw ((options[i].len != config->send_options[i].len) || 72418d08eb0Skrw memcmp(options[i].data, config->send_options[i].data, 72518d08eb0Skrw options[i].len) != 0)) { 72618d08eb0Skrw #ifdef DEBUG 727833082e5Skrw log_debug("Discarding packet with client-identifier " 728833082e5Skrw "'%s'", pretty_print_option(i, &options[i], 0)); 72968c1ec45Skrw #endif /* DEBUG */ 73018d08eb0Skrw goto done; 73118d08eb0Skrw } 73202e02bd5Skrw } 73302e02bd5Skrw 7346896c986Skrw type = "<unknown>"; 73502e02bd5Skrw handler = NULL; 73602e02bd5Skrw 7374f062ee3Skrw if (options[DHO_DHCP_MESSAGE_TYPE].data) { 73802e02bd5Skrw /* Always try a DHCP packet, even if a bad option was seen. */ 73902e02bd5Skrw switch (options[DHO_DHCP_MESSAGE_TYPE].data[0]) { 74002e02bd5Skrw case DHCPOFFER: 74102e02bd5Skrw handler = dhcpoffer; 74202e02bd5Skrw type = "DHCPOFFER"; 74302e02bd5Skrw break; 74402e02bd5Skrw case DHCPNAK: 74502e02bd5Skrw handler = dhcpnak; 74602e02bd5Skrw type = "DHCPNACK"; 74702e02bd5Skrw break; 74802e02bd5Skrw case DHCPACK: 74902e02bd5Skrw handler = dhcpack; 75002e02bd5Skrw type = "DHCPACK"; 75102e02bd5Skrw break; 75202e02bd5Skrw default: 753aff84b99Skrw #ifdef DEBUG 754833082e5Skrw log_debug("Discarding DHCP packet of unknown type " 755833082e5Skrw "(%d)", options[DHO_DHCP_MESSAGE_TYPE].data[0]); 75668c1ec45Skrw #endif /* DEBUG */ 75702e02bd5Skrw break; 75802e02bd5Skrw } 75902e02bd5Skrw } else if (options_valid && packet->op == BOOTREPLY) { 76002e02bd5Skrw handler = dhcpoffer; 76102e02bd5Skrw type = "BOOTREPLY"; 762aff84b99Skrw } else { 763aff84b99Skrw #ifdef DEBUG 764385a6373Skrw log_debug("Discarding packet which is neither DHCP nor BOOTP"); 76568c1ec45Skrw #endif /* DEBUG */ 76602e02bd5Skrw } 7679a2590e5Sderaadt 7686896c986Skrw rslt = asprintf(&info, "%s from %s (%s)", type, inet_ntoa(from), 769393831bbSkrw ether_ntoa(hfrom)); 7706896c986Skrw if (rslt == -1) 771385a6373Skrw fatalx("no memory for info string"); 7726896c986Skrw 77302e02bd5Skrw if (handler) 77433b81fd8Smpi (*handler)(ifi, from, options, info); 7756896c986Skrw 7766896c986Skrw free(info); 77702e02bd5Skrw 77818d08eb0Skrw done: 779c714dadcShenning for (i = 0; i < 256; i++) 7804f062ee3Skrw free(options[i].data); 7819a2590e5Sderaadt } 782