1147072Sbrooks/* $OpenBSD: options.c,v 1.15 2004/12/26 03:17:07 deraadt Exp $ */ 2147072Sbrooks 3147072Sbrooks/* DHCP options parsing and reassembly. */ 4147072Sbrooks 5147072Sbrooks/* 6147072Sbrooks * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. 7147072Sbrooks * All rights reserved. 8147072Sbrooks * 9147072Sbrooks * Redistribution and use in source and binary forms, with or without 10147072Sbrooks * modification, are permitted provided that the following conditions 11147072Sbrooks * are met: 12147072Sbrooks * 13147072Sbrooks * 1. Redistributions of source code must retain the above copyright 14147072Sbrooks * notice, this list of conditions and the following disclaimer. 15147072Sbrooks * 2. Redistributions in binary form must reproduce the above copyright 16147072Sbrooks * notice, this list of conditions and the following disclaimer in the 17147072Sbrooks * documentation and/or other materials provided with the distribution. 18147072Sbrooks * 3. Neither the name of The Internet Software Consortium nor the names 19147072Sbrooks * of its contributors may be used to endorse or promote products derived 20147072Sbrooks * from this software without specific prior written permission. 21147072Sbrooks * 22147072Sbrooks * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 23147072Sbrooks * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24147072Sbrooks * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25147072Sbrooks * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26147072Sbrooks * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 27147072Sbrooks * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28147072Sbrooks * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29147072Sbrooks * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 30147072Sbrooks * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31147072Sbrooks * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32147072Sbrooks * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 33147072Sbrooks * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34147072Sbrooks * SUCH DAMAGE. 35147072Sbrooks * 36147072Sbrooks * This software has been written for the Internet Software Consortium 37147072Sbrooks * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 38147072Sbrooks * Enterprises. To learn more about the Internet Software Consortium, 39147072Sbrooks * see ``http://www.vix.com/isc''. To learn more about Vixie 40147072Sbrooks * Enterprises, see ``http://www.vix.com''. 41147072Sbrooks */ 42147072Sbrooks 43149399Sbrooks#include <sys/cdefs.h> 44149399Sbrooks__FBSDID("$FreeBSD: releng/10.3/sbin/dhclient/options.c 229778 2012-01-07 16:09:33Z uqs $"); 45149399Sbrooks 46147072Sbrooks#include <ctype.h> 47147072Sbrooks 48147072Sbrooks#define DHCP_OPTION_DATA 49147072Sbrooks#include "dhcpd.h" 50147072Sbrooks 51147072Sbrooksint bad_options = 0; 52147072Sbrooksint bad_options_max = 5; 53147072Sbrooks 54147072Sbrooksvoid parse_options(struct packet *); 55147072Sbrooksvoid parse_option_buffer(struct packet *, unsigned char *, int); 56147072Sbrooksint store_options(unsigned char *, int, struct tree_cache **, 57147072Sbrooks unsigned char *, int, int, int, int); 58228259Sdumbbellvoid expand_domain_search(struct packet *packet); 59228259Sdumbbellint find_search_domain_name_len(struct option_data *option, int *offset); 60228259Sdumbbellvoid expand_search_domain_name(struct option_data *option, int *offset, 61228259Sdumbbell unsigned char **domain_search); 62147072Sbrooks 63147072Sbrooks 64147072Sbrooks/* 65147072Sbrooks * Parse all available options out of the specified packet. 66147072Sbrooks */ 67147072Sbrooksvoid 68147072Sbrooksparse_options(struct packet *packet) 69147072Sbrooks{ 70147072Sbrooks /* Initially, zero all option pointers. */ 71147072Sbrooks memset(packet->options, 0, sizeof(packet->options)); 72147072Sbrooks 73147072Sbrooks /* If we don't see the magic cookie, there's nothing to parse. */ 74147072Sbrooks if (memcmp(packet->raw->options, DHCP_OPTIONS_COOKIE, 4)) { 75147072Sbrooks packet->options_valid = 0; 76147072Sbrooks return; 77147072Sbrooks } 78147072Sbrooks 79147072Sbrooks /* 80147072Sbrooks * Go through the options field, up to the end of the packet or 81147072Sbrooks * the End field. 82147072Sbrooks */ 83147072Sbrooks parse_option_buffer(packet, &packet->raw->options[4], 84147072Sbrooks packet->packet_length - DHCP_FIXED_NON_UDP - 4); 85147072Sbrooks 86147072Sbrooks /* 87147072Sbrooks * If we parsed a DHCP Option Overload option, parse more 88147072Sbrooks * options out of the buffer(s) containing them. 89147072Sbrooks */ 90147072Sbrooks if (packet->options_valid && 91147072Sbrooks packet->options[DHO_DHCP_OPTION_OVERLOAD].data) { 92147072Sbrooks if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) 93147072Sbrooks parse_option_buffer(packet, 94147072Sbrooks (unsigned char *)packet->raw->file, 95147072Sbrooks sizeof(packet->raw->file)); 96147072Sbrooks if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) 97147072Sbrooks parse_option_buffer(packet, 98147072Sbrooks (unsigned char *)packet->raw->sname, 99147072Sbrooks sizeof(packet->raw->sname)); 100147072Sbrooks } 101228259Sdumbbell 102228259Sdumbbell /* Expand DHCP Domain Search option. */ 103228259Sdumbbell if (packet->options_valid) { 104228259Sdumbbell expand_domain_search(packet); 105228259Sdumbbell } 106147072Sbrooks} 107147072Sbrooks 108147072Sbrooks/* 109147072Sbrooks * Parse options out of the specified buffer, storing addresses of 110147072Sbrooks * option values in packet->options and setting packet->options_valid if 111147072Sbrooks * no errors are encountered. 112147072Sbrooks */ 113147072Sbrooksvoid 114147072Sbrooksparse_option_buffer(struct packet *packet, 115147072Sbrooks unsigned char *buffer, int length) 116147072Sbrooks{ 117147072Sbrooks unsigned char *s, *t, *end = buffer + length; 118147072Sbrooks int len, code; 119147072Sbrooks 120147072Sbrooks for (s = buffer; *s != DHO_END && s < end; ) { 121147072Sbrooks code = s[0]; 122147072Sbrooks 123147072Sbrooks /* Pad options don't have a length - just skip them. */ 124147072Sbrooks if (code == DHO_PAD) { 125147072Sbrooks s++; 126147072Sbrooks continue; 127147072Sbrooks } 128147072Sbrooks if (s + 2 > end) { 129147072Sbrooks len = 65536; 130147072Sbrooks goto bogus; 131147072Sbrooks } 132147072Sbrooks 133147072Sbrooks /* 134147072Sbrooks * All other fields (except end, see above) have a 135147072Sbrooks * one-byte length. 136147072Sbrooks */ 137147072Sbrooks len = s[1]; 138147072Sbrooks 139147072Sbrooks /* 140147072Sbrooks * If the length is outrageous, silently skip the rest, 141147072Sbrooks * and mark the packet bad. Unfortunately some crappy 142147072Sbrooks * dhcp servers always seem to give us garbage on the 143147072Sbrooks * end of a packet. so rather than keep refusing, give 144147072Sbrooks * up and try to take one after seeing a few without 145147072Sbrooks * anything good. 146147072Sbrooks */ 147147072Sbrooks if (s + len + 2 > end) { 148147072Sbrooks bogus: 149147072Sbrooks bad_options++; 150147072Sbrooks warning("option %s (%d) %s.", 151147072Sbrooks dhcp_options[code].name, len, 152147072Sbrooks "larger than buffer"); 153147072Sbrooks if (bad_options == bad_options_max) { 154147072Sbrooks packet->options_valid = 1; 155147072Sbrooks bad_options = 0; 156147072Sbrooks warning("Many bogus options seen in offers. " 157147072Sbrooks "Taking this offer in spite of bogus " 158147072Sbrooks "options - hope for the best!"); 159147072Sbrooks } else { 160147072Sbrooks warning("rejecting bogus offer."); 161147072Sbrooks packet->options_valid = 0; 162147072Sbrooks } 163147072Sbrooks return; 164147072Sbrooks } 165147072Sbrooks /* 166147072Sbrooks * If we haven't seen this option before, just make 167147072Sbrooks * space for it and copy it there. 168147072Sbrooks */ 169147072Sbrooks if (!packet->options[code].data) { 170147072Sbrooks if (!(t = calloc(1, len + 1))) 171147072Sbrooks error("Can't allocate storage for option %s.", 172147072Sbrooks dhcp_options[code].name); 173147072Sbrooks /* 174147072Sbrooks * Copy and NUL-terminate the option (in case 175147072Sbrooks * it's an ASCII string. 176147072Sbrooks */ 177147072Sbrooks memcpy(t, &s[2], len); 178147072Sbrooks t[len] = 0; 179147072Sbrooks packet->options[code].len = len; 180147072Sbrooks packet->options[code].data = t; 181147072Sbrooks } else { 182147072Sbrooks /* 183147072Sbrooks * If it's a repeat, concatenate it to whatever 184147072Sbrooks * we last saw. This is really only required 185147072Sbrooks * for clients, but what the heck... 186147072Sbrooks */ 187147072Sbrooks t = calloc(1, len + packet->options[code].len + 1); 188147072Sbrooks if (!t) 189147072Sbrooks error("Can't expand storage for option %s.", 190147072Sbrooks dhcp_options[code].name); 191147072Sbrooks memcpy(t, packet->options[code].data, 192147072Sbrooks packet->options[code].len); 193147072Sbrooks memcpy(t + packet->options[code].len, 194147072Sbrooks &s[2], len); 195147072Sbrooks packet->options[code].len += len; 196147072Sbrooks t[packet->options[code].len] = 0; 197147072Sbrooks free(packet->options[code].data); 198147072Sbrooks packet->options[code].data = t; 199147072Sbrooks } 200147072Sbrooks s += len + 2; 201147072Sbrooks } 202147072Sbrooks packet->options_valid = 1; 203147072Sbrooks} 204147072Sbrooks 205147072Sbrooks/* 206228259Sdumbbell * Expand DHCP Domain Search option. The value of this option is 207228259Sdumbbell * encoded like DNS' list of labels. See: 208228259Sdumbbell * RFC 3397 209228259Sdumbbell * RFC 1035 210228259Sdumbbell */ 211228259Sdumbbellvoid 212228259Sdumbbellexpand_domain_search(struct packet *packet) 213228259Sdumbbell{ 214229000Sdumbbell int offset, expanded_len, next_domain_len; 215228259Sdumbbell struct option_data *option; 216228259Sdumbbell unsigned char *domain_search, *cursor; 217228259Sdumbbell 218228259Sdumbbell if (packet->options[DHO_DOMAIN_SEARCH].data == NULL) 219228259Sdumbbell return; 220228259Sdumbbell 221228259Sdumbbell option = &packet->options[DHO_DOMAIN_SEARCH]; 222228259Sdumbbell 223228259Sdumbbell /* Compute final expanded length. */ 224228259Sdumbbell expanded_len = 0; 225228259Sdumbbell offset = 0; 226228259Sdumbbell while (offset < option->len) { 227229000Sdumbbell next_domain_len = find_search_domain_name_len(option, &offset); 228229000Sdumbbell if (next_domain_len < 0) 229229000Sdumbbell /* The Domain Search option value is invalid. */ 230229000Sdumbbell return; 231229000Sdumbbell 232228259Sdumbbell /* We add 1 for the space between domain names. */ 233229000Sdumbbell expanded_len += next_domain_len + 1; 234228259Sdumbbell } 235228259Sdumbbell if (expanded_len > 0) 236228259Sdumbbell /* Remove 1 for the superfluous trailing space. */ 237228259Sdumbbell --expanded_len; 238228259Sdumbbell 239228259Sdumbbell domain_search = malloc(expanded_len + 1); 240228259Sdumbbell if (domain_search == NULL) 241228259Sdumbbell error("Can't allocate storage for expanded domain-search\n"); 242228259Sdumbbell 243228259Sdumbbell offset = 0; 244228259Sdumbbell cursor = domain_search; 245228259Sdumbbell while (offset < option->len) { 246228259Sdumbbell expand_search_domain_name(option, &offset, &cursor); 247228259Sdumbbell cursor[0] = ' '; 248228259Sdumbbell cursor++; 249228259Sdumbbell } 250228259Sdumbbell domain_search[expanded_len] = '\0'; 251228259Sdumbbell 252228259Sdumbbell free(option->data); 253228259Sdumbbell option->len = expanded_len; 254228259Sdumbbell option->data = domain_search; 255228259Sdumbbell} 256228259Sdumbbell 257228259Sdumbbellint 258228259Sdumbbellfind_search_domain_name_len(struct option_data *option, int *offset) 259228259Sdumbbell{ 260228259Sdumbbell int domain_name_len, i, label_len, pointer, pointed_len; 261228259Sdumbbell 262228259Sdumbbell domain_name_len = 0; 263228259Sdumbbell 264228259Sdumbbell i = *offset; 265228259Sdumbbell while (i < option->len) { 266228259Sdumbbell label_len = option->data[i]; 267228259Sdumbbell if (label_len == 0) { 268228259Sdumbbell /* 269228259Sdumbbell * A zero-length label marks the end of this 270228259Sdumbbell * domain name. 271228259Sdumbbell */ 272228259Sdumbbell *offset = i + 1; 273228259Sdumbbell return (domain_name_len); 274228259Sdumbbell } else if (label_len & 0xC0) { 275228259Sdumbbell /* This is a pointer to another list of labels. */ 276228259Sdumbbell if (i + 1 >= option->len) { 277228259Sdumbbell /* The pointer is truncated. */ 278229000Sdumbbell warning("Truncated pointer in DHCP Domain " 279228259Sdumbbell "Search option."); 280229000Sdumbbell return (-1); 281228259Sdumbbell } 282228259Sdumbbell 283228259Sdumbbell pointer = ((label_len & ~(0xC0)) << 8) + 284228259Sdumbbell option->data[i + 1]; 285228259Sdumbbell if (pointer >= *offset) { 286228259Sdumbbell /* 287229778Suqs * The pointer must indicate a prior 288229778Suqs * occurrence. 289228259Sdumbbell */ 290229000Sdumbbell warning("Invalid forward pointer in DHCP " 291229000Sdumbbell "Domain Search option compression."); 292229000Sdumbbell return (-1); 293228259Sdumbbell } 294228259Sdumbbell 295228259Sdumbbell pointed_len = find_search_domain_name_len(option, 296228259Sdumbbell &pointer); 297228259Sdumbbell domain_name_len += pointed_len; 298228259Sdumbbell 299228259Sdumbbell *offset = i + 2; 300228259Sdumbbell return (domain_name_len); 301228259Sdumbbell } 302228259Sdumbbell 303228259Sdumbbell if (i + label_len >= option->len) { 304229000Sdumbbell warning("Truncated label in DHCP Domain Search " 305229000Sdumbbell "option."); 306229000Sdumbbell return (-1); 307228259Sdumbbell } 308228259Sdumbbell 309228259Sdumbbell /* 310228259Sdumbbell * Update the domain name length with the length of the 311228259Sdumbbell * current label, plus a trailing dot ('.'). 312228259Sdumbbell */ 313228259Sdumbbell domain_name_len += label_len + 1; 314228259Sdumbbell 315228259Sdumbbell /* Move cursor. */ 316228259Sdumbbell i += label_len + 1; 317228259Sdumbbell } 318228259Sdumbbell 319229000Sdumbbell warning("Truncated DHCP Domain Search option."); 320228259Sdumbbell 321229000Sdumbbell return (-1); 322228259Sdumbbell} 323228259Sdumbbell 324228259Sdumbbellvoid 325228259Sdumbbellexpand_search_domain_name(struct option_data *option, int *offset, 326228259Sdumbbell unsigned char **domain_search) 327228259Sdumbbell{ 328228259Sdumbbell int i, label_len, pointer; 329228259Sdumbbell unsigned char *cursor; 330228259Sdumbbell 331228259Sdumbbell /* 332228259Sdumbbell * This is the same loop than the function above 333228259Sdumbbell * (find_search_domain_name_len). Therefore, we remove checks, 334228259Sdumbbell * they're already done. Here, we just make the copy. 335228259Sdumbbell */ 336228259Sdumbbell i = *offset; 337228259Sdumbbell cursor = *domain_search; 338228259Sdumbbell while (i < option->len) { 339228259Sdumbbell label_len = option->data[i]; 340228259Sdumbbell if (label_len == 0) { 341228259Sdumbbell /* 342228259Sdumbbell * A zero-length label marks the end of this 343228259Sdumbbell * domain name. 344228259Sdumbbell */ 345228259Sdumbbell *offset = i + 1; 346228259Sdumbbell *domain_search = cursor; 347228259Sdumbbell return; 348228259Sdumbbell } else if (label_len & 0xC0) { 349228259Sdumbbell /* This is a pointer to another list of labels. */ 350228259Sdumbbell pointer = ((label_len & ~(0xC0)) << 8) + 351228259Sdumbbell option->data[i + 1]; 352228259Sdumbbell 353228259Sdumbbell expand_search_domain_name(option, &pointer, &cursor); 354228259Sdumbbell 355228259Sdumbbell *offset = i + 2; 356228259Sdumbbell *domain_search = cursor; 357228259Sdumbbell return; 358228259Sdumbbell } 359228259Sdumbbell 360228259Sdumbbell /* Copy the label found. */ 361228259Sdumbbell memcpy(cursor, option->data + i + 1, label_len); 362228259Sdumbbell cursor[label_len] = '.'; 363228259Sdumbbell 364228259Sdumbbell /* Move cursor. */ 365228259Sdumbbell i += label_len + 1; 366228259Sdumbbell cursor += label_len + 1; 367228259Sdumbbell } 368228259Sdumbbell} 369228259Sdumbbell 370228259Sdumbbell/* 371147072Sbrooks * cons options into a big buffer, and then split them out into the 372147072Sbrooks * three separate buffers if needed. This allows us to cons up a set of 373147072Sbrooks * vendor options using the same routine. 374147072Sbrooks */ 375147072Sbrooksint 376147072Sbrookscons_options(struct packet *inpacket, struct dhcp_packet *outpacket, 377147072Sbrooks int mms, struct tree_cache **options, 378147072Sbrooks int overload, /* Overload flags that may be set. */ 379147072Sbrooks int terminate, int bootpp, u_int8_t *prl, int prl_len) 380147072Sbrooks{ 381147072Sbrooks unsigned char priority_list[300], buffer[4096]; 382147072Sbrooks int priority_len, main_buffer_size, mainbufix, bufix; 383147072Sbrooks int option_size, length; 384147072Sbrooks 385147072Sbrooks /* 386147072Sbrooks * If the client has provided a maximum DHCP message size, use 387147072Sbrooks * that; otherwise, if it's BOOTP, only 64 bytes; otherwise use 388147072Sbrooks * up to the minimum IP MTU size (576 bytes). 389147072Sbrooks * 390147072Sbrooks * XXX if a BOOTP client specifies a max message size, we will 391147072Sbrooks * honor it. 392147072Sbrooks */ 393147072Sbrooks if (!mms && 394147072Sbrooks inpacket && 395147072Sbrooks inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data && 396147072Sbrooks (inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].len >= 397147072Sbrooks sizeof(u_int16_t))) 398147072Sbrooks mms = getUShort( 399147072Sbrooks inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data); 400147072Sbrooks 401147072Sbrooks if (mms) 402147072Sbrooks main_buffer_size = mms - DHCP_FIXED_LEN; 403147072Sbrooks else if (bootpp) 404147072Sbrooks main_buffer_size = 64; 405147072Sbrooks else 406147072Sbrooks main_buffer_size = 576 - DHCP_FIXED_LEN; 407147072Sbrooks 408147072Sbrooks if (main_buffer_size > sizeof(buffer)) 409147072Sbrooks main_buffer_size = sizeof(buffer); 410147072Sbrooks 411147072Sbrooks /* Preload the option priority list with mandatory options. */ 412147072Sbrooks priority_len = 0; 413147072Sbrooks priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE; 414147072Sbrooks priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER; 415147072Sbrooks priority_list[priority_len++] = DHO_DHCP_LEASE_TIME; 416147072Sbrooks priority_list[priority_len++] = DHO_DHCP_MESSAGE; 417147072Sbrooks 418147072Sbrooks /* 419147072Sbrooks * If the client has provided a list of options that it wishes 420147072Sbrooks * returned, use it to prioritize. Otherwise, prioritize based 421147072Sbrooks * on the default priority list. 422147072Sbrooks */ 423147072Sbrooks if (inpacket && 424147072Sbrooks inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data) { 425147072Sbrooks int prlen = 426147072Sbrooks inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].len; 427147072Sbrooks if (prlen + priority_len > sizeof(priority_list)) 428147072Sbrooks prlen = sizeof(priority_list) - priority_len; 429147072Sbrooks 430147072Sbrooks memcpy(&priority_list[priority_len], 431147072Sbrooks inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data, 432147072Sbrooks prlen); 433147072Sbrooks priority_len += prlen; 434147072Sbrooks prl = priority_list; 435147072Sbrooks } else if (prl) { 436147072Sbrooks if (prl_len + priority_len > sizeof(priority_list)) 437147072Sbrooks prl_len = sizeof(priority_list) - priority_len; 438147072Sbrooks 439147072Sbrooks memcpy(&priority_list[priority_len], prl, prl_len); 440147072Sbrooks priority_len += prl_len; 441147072Sbrooks prl = priority_list; 442147072Sbrooks } else { 443147072Sbrooks memcpy(&priority_list[priority_len], 444147072Sbrooks dhcp_option_default_priority_list, 445147072Sbrooks sizeof_dhcp_option_default_priority_list); 446147072Sbrooks priority_len += sizeof_dhcp_option_default_priority_list; 447147072Sbrooks } 448147072Sbrooks 449147072Sbrooks /* Copy the options into the big buffer... */ 450147072Sbrooks option_size = store_options( 451147072Sbrooks buffer, 452147072Sbrooks (main_buffer_size - 7 + ((overload & 1) ? DHCP_FILE_LEN : 0) + 453147072Sbrooks ((overload & 2) ? DHCP_SNAME_LEN : 0)), 454147072Sbrooks options, priority_list, priority_len, main_buffer_size, 455147072Sbrooks (main_buffer_size + ((overload & 1) ? DHCP_FILE_LEN : 0)), 456147072Sbrooks terminate); 457147072Sbrooks 458147072Sbrooks /* Put the cookie up front... */ 459147072Sbrooks memcpy(outpacket->options, DHCP_OPTIONS_COOKIE, 4); 460147072Sbrooks mainbufix = 4; 461147072Sbrooks 462147072Sbrooks /* 463147072Sbrooks * If we're going to have to overload, store the overload option 464147072Sbrooks * at the beginning. If we can, though, just store the whole 465147072Sbrooks * thing in the packet's option buffer and leave it at that. 466147072Sbrooks */ 467147072Sbrooks if (option_size <= main_buffer_size - mainbufix) { 468147072Sbrooks memcpy(&outpacket->options[mainbufix], 469147072Sbrooks buffer, option_size); 470147072Sbrooks mainbufix += option_size; 471147072Sbrooks if (mainbufix < main_buffer_size) 472147072Sbrooks outpacket->options[mainbufix++] = DHO_END; 473147072Sbrooks length = DHCP_FIXED_NON_UDP + mainbufix; 474147072Sbrooks } else { 475147072Sbrooks outpacket->options[mainbufix++] = DHO_DHCP_OPTION_OVERLOAD; 476147072Sbrooks outpacket->options[mainbufix++] = 1; 477147072Sbrooks if (option_size > 478147072Sbrooks main_buffer_size - mainbufix + DHCP_FILE_LEN) 479147072Sbrooks outpacket->options[mainbufix++] = 3; 480147072Sbrooks else 481147072Sbrooks outpacket->options[mainbufix++] = 1; 482147072Sbrooks 483147072Sbrooks memcpy(&outpacket->options[mainbufix], 484147072Sbrooks buffer, main_buffer_size - mainbufix); 485147072Sbrooks bufix = main_buffer_size - mainbufix; 486147072Sbrooks length = DHCP_FIXED_NON_UDP + mainbufix; 487147072Sbrooks if (overload & 1) { 488147072Sbrooks if (option_size - bufix <= DHCP_FILE_LEN) { 489147072Sbrooks memcpy(outpacket->file, 490147072Sbrooks &buffer[bufix], option_size - bufix); 491147072Sbrooks mainbufix = option_size - bufix; 492147072Sbrooks if (mainbufix < DHCP_FILE_LEN) 493147072Sbrooks outpacket->file[mainbufix++] = (char)DHO_END; 494147072Sbrooks while (mainbufix < DHCP_FILE_LEN) 495147072Sbrooks outpacket->file[mainbufix++] = (char)DHO_PAD; 496147072Sbrooks } else { 497147072Sbrooks memcpy(outpacket->file, 498147072Sbrooks &buffer[bufix], DHCP_FILE_LEN); 499147072Sbrooks bufix += DHCP_FILE_LEN; 500147072Sbrooks } 501147072Sbrooks } 502147072Sbrooks if ((overload & 2) && option_size < bufix) { 503147072Sbrooks memcpy(outpacket->sname, 504147072Sbrooks &buffer[bufix], option_size - bufix); 505147072Sbrooks 506147072Sbrooks mainbufix = option_size - bufix; 507147072Sbrooks if (mainbufix < DHCP_SNAME_LEN) 508147072Sbrooks outpacket->file[mainbufix++] = (char)DHO_END; 509147072Sbrooks while (mainbufix < DHCP_SNAME_LEN) 510147072Sbrooks outpacket->file[mainbufix++] = (char)DHO_PAD; 511147072Sbrooks } 512147072Sbrooks } 513147072Sbrooks return (length); 514147072Sbrooks} 515147072Sbrooks 516147072Sbrooks/* 517147072Sbrooks * Store all the requested options into the requested buffer. 518147072Sbrooks */ 519147072Sbrooksint 520147072Sbrooksstore_options(unsigned char *buffer, int buflen, struct tree_cache **options, 521147072Sbrooks unsigned char *priority_list, int priority_len, int first_cutoff, 522147072Sbrooks int second_cutoff, int terminate) 523147072Sbrooks{ 524147072Sbrooks int bufix = 0, option_stored[256], i, ix, tto; 525147072Sbrooks 526147072Sbrooks /* Zero out the stored-lengths array. */ 527147072Sbrooks memset(option_stored, 0, sizeof(option_stored)); 528147072Sbrooks 529147072Sbrooks /* 530147072Sbrooks * Copy out the options in the order that they appear in the 531147072Sbrooks * priority list... 532147072Sbrooks */ 533147072Sbrooks for (i = 0; i < priority_len; i++) { 534147072Sbrooks /* Code for next option to try to store. */ 535147072Sbrooks int code = priority_list[i]; 536147072Sbrooks int optstart; 537147072Sbrooks 538147072Sbrooks /* 539147072Sbrooks * Number of bytes left to store (some may already have 540147072Sbrooks * been stored by a previous pass). 541147072Sbrooks */ 542147072Sbrooks int length; 543147072Sbrooks 544147072Sbrooks /* If no data is available for this option, skip it. */ 545147072Sbrooks if (!options[code]) { 546147072Sbrooks continue; 547147072Sbrooks } 548147072Sbrooks 549147072Sbrooks /* 550147072Sbrooks * The client could ask for things that are mandatory, 551147072Sbrooks * in which case we should avoid storing them twice... 552147072Sbrooks */ 553147072Sbrooks if (option_stored[code]) 554147072Sbrooks continue; 555147072Sbrooks option_stored[code] = 1; 556147072Sbrooks 557147072Sbrooks /* We should now have a constant length for the option. */ 558147072Sbrooks length = options[code]->len; 559147072Sbrooks 560147072Sbrooks /* Do we add a NUL? */ 561147072Sbrooks if (terminate && dhcp_options[code].format[0] == 't') { 562147072Sbrooks length++; 563147072Sbrooks tto = 1; 564147072Sbrooks } else 565147072Sbrooks tto = 0; 566147072Sbrooks 567147072Sbrooks /* Try to store the option. */ 568147072Sbrooks 569147072Sbrooks /* 570147072Sbrooks * If the option's length is more than 255, we must 571147072Sbrooks * store it in multiple hunks. Store 255-byte hunks 572147072Sbrooks * first. However, in any case, if the option data will 573147072Sbrooks * cross a buffer boundary, split it across that 574147072Sbrooks * boundary. 575147072Sbrooks */ 576147072Sbrooks ix = 0; 577147072Sbrooks 578147072Sbrooks optstart = bufix; 579147072Sbrooks while (length) { 580147072Sbrooks unsigned char incr = length > 255 ? 255 : length; 581147072Sbrooks 582147072Sbrooks /* 583147072Sbrooks * If this hunk of the buffer will cross a 584147072Sbrooks * boundary, only go up to the boundary in this 585147072Sbrooks * pass. 586147072Sbrooks */ 587147072Sbrooks if (bufix < first_cutoff && 588147072Sbrooks bufix + incr > first_cutoff) 589147072Sbrooks incr = first_cutoff - bufix; 590147072Sbrooks else if (bufix < second_cutoff && 591147072Sbrooks bufix + incr > second_cutoff) 592147072Sbrooks incr = second_cutoff - bufix; 593147072Sbrooks 594147072Sbrooks /* 595147072Sbrooks * If this option is going to overflow the 596147072Sbrooks * buffer, skip it. 597147072Sbrooks */ 598147072Sbrooks if (bufix + 2 + incr > buflen) { 599147072Sbrooks bufix = optstart; 600147072Sbrooks break; 601147072Sbrooks } 602147072Sbrooks 603147072Sbrooks /* Everything looks good - copy it in! */ 604147072Sbrooks buffer[bufix] = code; 605147072Sbrooks buffer[bufix + 1] = incr; 606147072Sbrooks if (tto && incr == length) { 607147072Sbrooks memcpy(buffer + bufix + 2, 608147072Sbrooks options[code]->value + ix, incr - 1); 609147072Sbrooks buffer[bufix + 2 + incr - 1] = 0; 610147072Sbrooks } else 611147072Sbrooks memcpy(buffer + bufix + 2, 612147072Sbrooks options[code]->value + ix, incr); 613147072Sbrooks length -= incr; 614147072Sbrooks ix += incr; 615147072Sbrooks bufix += 2 + incr; 616147072Sbrooks } 617147072Sbrooks } 618147072Sbrooks return (bufix); 619147072Sbrooks} 620147072Sbrooks 621147072Sbrooks/* 622147072Sbrooks * Format the specified option so that a human can easily read it. 623147072Sbrooks */ 624147072Sbrookschar * 625147072Sbrookspretty_print_option(unsigned int code, unsigned char *data, int len, 626147072Sbrooks int emit_commas, int emit_quotes) 627147072Sbrooks{ 628147072Sbrooks static char optbuf[32768]; /* XXX */ 629147072Sbrooks int hunksize = 0, numhunk = -1, numelem = 0; 630147072Sbrooks char fmtbuf[32], *op = optbuf; 631147072Sbrooks int i, j, k, opleft = sizeof(optbuf); 632147072Sbrooks unsigned char *dp = data; 633147072Sbrooks struct in_addr foo; 634147072Sbrooks char comma; 635147072Sbrooks 636147072Sbrooks /* Code should be between 0 and 255. */ 637147072Sbrooks if (code > 255) 638147072Sbrooks error("pretty_print_option: bad code %d", code); 639147072Sbrooks 640147072Sbrooks if (emit_commas) 641147072Sbrooks comma = ','; 642147072Sbrooks else 643147072Sbrooks comma = ' '; 644147072Sbrooks 645147072Sbrooks /* Figure out the size of the data. */ 646147072Sbrooks for (i = 0; dhcp_options[code].format[i]; i++) { 647147072Sbrooks if (!numhunk) { 648147072Sbrooks warning("%s: Excess information in format string: %s", 649147072Sbrooks dhcp_options[code].name, 650147072Sbrooks &(dhcp_options[code].format[i])); 651147072Sbrooks break; 652147072Sbrooks } 653147072Sbrooks numelem++; 654147072Sbrooks fmtbuf[i] = dhcp_options[code].format[i]; 655147072Sbrooks switch (dhcp_options[code].format[i]) { 656147072Sbrooks case 'A': 657147072Sbrooks --numelem; 658147072Sbrooks fmtbuf[i] = 0; 659147072Sbrooks numhunk = 0; 660147072Sbrooks break; 661147072Sbrooks case 'X': 662147072Sbrooks for (k = 0; k < len; k++) 663147072Sbrooks if (!isascii(data[k]) || 664147072Sbrooks !isprint(data[k])) 665147072Sbrooks break; 666147072Sbrooks if (k == len) { 667147072Sbrooks fmtbuf[i] = 't'; 668147072Sbrooks numhunk = -2; 669147072Sbrooks } else { 670147072Sbrooks fmtbuf[i] = 'x'; 671147072Sbrooks hunksize++; 672147072Sbrooks comma = ':'; 673147072Sbrooks numhunk = 0; 674147072Sbrooks } 675147072Sbrooks fmtbuf[i + 1] = 0; 676147072Sbrooks break; 677147072Sbrooks case 't': 678147072Sbrooks fmtbuf[i] = 't'; 679147072Sbrooks fmtbuf[i + 1] = 0; 680147072Sbrooks numhunk = -2; 681147072Sbrooks break; 682147072Sbrooks case 'I': 683147072Sbrooks case 'l': 684147072Sbrooks case 'L': 685147072Sbrooks hunksize += 4; 686147072Sbrooks break; 687147072Sbrooks case 's': 688147072Sbrooks case 'S': 689147072Sbrooks hunksize += 2; 690147072Sbrooks break; 691147072Sbrooks case 'b': 692147072Sbrooks case 'B': 693147072Sbrooks case 'f': 694147072Sbrooks hunksize++; 695147072Sbrooks break; 696147072Sbrooks case 'e': 697147072Sbrooks break; 698147072Sbrooks default: 699147072Sbrooks warning("%s: garbage in format string: %s", 700147072Sbrooks dhcp_options[code].name, 701147072Sbrooks &(dhcp_options[code].format[i])); 702147072Sbrooks break; 703147072Sbrooks } 704147072Sbrooks } 705147072Sbrooks 706147072Sbrooks /* Check for too few bytes... */ 707147072Sbrooks if (hunksize > len) { 708147072Sbrooks warning("%s: expecting at least %d bytes; got %d", 709147072Sbrooks dhcp_options[code].name, hunksize, len); 710147072Sbrooks return ("<error>"); 711147072Sbrooks } 712147072Sbrooks /* Check for too many bytes... */ 713147072Sbrooks if (numhunk == -1 && hunksize < len) 714147072Sbrooks warning("%s: %d extra bytes", 715147072Sbrooks dhcp_options[code].name, len - hunksize); 716147072Sbrooks 717147072Sbrooks /* If this is an array, compute its size. */ 718147072Sbrooks if (!numhunk) 719147072Sbrooks numhunk = len / hunksize; 720147072Sbrooks /* See if we got an exact number of hunks. */ 721147072Sbrooks if (numhunk > 0 && numhunk * hunksize < len) 722147072Sbrooks warning("%s: %d extra bytes at end of array", 723147072Sbrooks dhcp_options[code].name, len - numhunk * hunksize); 724147072Sbrooks 725147072Sbrooks /* A one-hunk array prints the same as a single hunk. */ 726147072Sbrooks if (numhunk < 0) 727147072Sbrooks numhunk = 1; 728147072Sbrooks 729147072Sbrooks /* Cycle through the array (or hunk) printing the data. */ 730147072Sbrooks for (i = 0; i < numhunk; i++) { 731147072Sbrooks for (j = 0; j < numelem; j++) { 732147072Sbrooks int opcount; 733147072Sbrooks switch (fmtbuf[j]) { 734147072Sbrooks case 't': 735147072Sbrooks if (emit_quotes) { 736147072Sbrooks *op++ = '"'; 737147072Sbrooks opleft--; 738147072Sbrooks } 739147072Sbrooks for (; dp < data + len; dp++) { 740147072Sbrooks if (!isascii(*dp) || 741147072Sbrooks !isprint(*dp)) { 742147072Sbrooks if (dp + 1 != data + len || 743147072Sbrooks *dp != 0) { 744147072Sbrooks snprintf(op, opleft, 745147072Sbrooks "\\%03o", *dp); 746147072Sbrooks op += 4; 747147072Sbrooks opleft -= 4; 748147072Sbrooks } 749147072Sbrooks } else if (*dp == '"' || 750147072Sbrooks *dp == '\'' || 751147072Sbrooks *dp == '$' || 752147072Sbrooks *dp == '`' || 753147072Sbrooks *dp == '\\') { 754147072Sbrooks *op++ = '\\'; 755147072Sbrooks *op++ = *dp; 756147072Sbrooks opleft -= 2; 757147072Sbrooks } else { 758147072Sbrooks *op++ = *dp; 759147072Sbrooks opleft--; 760147072Sbrooks } 761147072Sbrooks } 762147072Sbrooks if (emit_quotes) { 763147072Sbrooks *op++ = '"'; 764147072Sbrooks opleft--; 765147072Sbrooks } 766147072Sbrooks 767147072Sbrooks *op = 0; 768147072Sbrooks break; 769147072Sbrooks case 'I': 770147072Sbrooks foo.s_addr = htonl(getULong(dp)); 771147072Sbrooks opcount = strlcpy(op, inet_ntoa(foo), opleft); 772147072Sbrooks if (opcount >= opleft) 773147072Sbrooks goto toobig; 774147072Sbrooks opleft -= opcount; 775147072Sbrooks dp += 4; 776147072Sbrooks break; 777147072Sbrooks case 'l': 778147072Sbrooks opcount = snprintf(op, opleft, "%ld", 779147072Sbrooks (long)getLong(dp)); 780147072Sbrooks if (opcount >= opleft || opcount == -1) 781147072Sbrooks goto toobig; 782147072Sbrooks opleft -= opcount; 783147072Sbrooks dp += 4; 784147072Sbrooks break; 785147072Sbrooks case 'L': 786147072Sbrooks opcount = snprintf(op, opleft, "%ld", 787147072Sbrooks (unsigned long)getULong(dp)); 788147072Sbrooks if (opcount >= opleft || opcount == -1) 789147072Sbrooks goto toobig; 790147072Sbrooks opleft -= opcount; 791147072Sbrooks dp += 4; 792147072Sbrooks break; 793147072Sbrooks case 's': 794147072Sbrooks opcount = snprintf(op, opleft, "%d", 795147072Sbrooks getShort(dp)); 796147072Sbrooks if (opcount >= opleft || opcount == -1) 797147072Sbrooks goto toobig; 798147072Sbrooks opleft -= opcount; 799147072Sbrooks dp += 2; 800147072Sbrooks break; 801147072Sbrooks case 'S': 802147072Sbrooks opcount = snprintf(op, opleft, "%d", 803147072Sbrooks getUShort(dp)); 804147072Sbrooks if (opcount >= opleft || opcount == -1) 805147072Sbrooks goto toobig; 806147072Sbrooks opleft -= opcount; 807147072Sbrooks dp += 2; 808147072Sbrooks break; 809147072Sbrooks case 'b': 810147072Sbrooks opcount = snprintf(op, opleft, "%d", 811147072Sbrooks *(char *)dp++); 812147072Sbrooks if (opcount >= opleft || opcount == -1) 813147072Sbrooks goto toobig; 814147072Sbrooks opleft -= opcount; 815147072Sbrooks break; 816147072Sbrooks case 'B': 817147072Sbrooks opcount = snprintf(op, opleft, "%d", *dp++); 818147072Sbrooks if (opcount >= opleft || opcount == -1) 819147072Sbrooks goto toobig; 820147072Sbrooks opleft -= opcount; 821147072Sbrooks break; 822147072Sbrooks case 'x': 823147072Sbrooks opcount = snprintf(op, opleft, "%x", *dp++); 824147072Sbrooks if (opcount >= opleft || opcount == -1) 825147072Sbrooks goto toobig; 826147072Sbrooks opleft -= opcount; 827147072Sbrooks break; 828147072Sbrooks case 'f': 829147072Sbrooks opcount = strlcpy(op, 830147072Sbrooks *dp++ ? "true" : "false", opleft); 831147072Sbrooks if (opcount >= opleft) 832147072Sbrooks goto toobig; 833147072Sbrooks opleft -= opcount; 834147072Sbrooks break; 835147072Sbrooks default: 836147072Sbrooks warning("Unexpected format code %c", fmtbuf[j]); 837147072Sbrooks } 838147072Sbrooks op += strlen(op); 839147072Sbrooks opleft -= strlen(op); 840147072Sbrooks if (opleft < 1) 841147072Sbrooks goto toobig; 842147072Sbrooks if (j + 1 < numelem && comma != ':') { 843147072Sbrooks *op++ = ' '; 844147072Sbrooks opleft--; 845147072Sbrooks } 846147072Sbrooks } 847147072Sbrooks if (i + 1 < numhunk) { 848147072Sbrooks *op++ = comma; 849147072Sbrooks opleft--; 850147072Sbrooks } 851147072Sbrooks if (opleft < 1) 852147072Sbrooks goto toobig; 853147072Sbrooks 854147072Sbrooks } 855147072Sbrooks return (optbuf); 856147072Sbrooks toobig: 857147072Sbrooks warning("dhcp option too large"); 858147072Sbrooks return ("<error>"); 859147072Sbrooks} 860147072Sbrooks 861147072Sbrooksvoid 862147072Sbrooksdo_packet(struct interface_info *interface, struct dhcp_packet *packet, 863147072Sbrooks int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom) 864147072Sbrooks{ 865147072Sbrooks struct packet tp; 866147072Sbrooks int i; 867147072Sbrooks 868147072Sbrooks if (packet->hlen > sizeof(packet->chaddr)) { 869147072Sbrooks note("Discarding packet with invalid hlen."); 870147072Sbrooks return; 871147072Sbrooks } 872147072Sbrooks 873147072Sbrooks memset(&tp, 0, sizeof(tp)); 874147072Sbrooks tp.raw = packet; 875147072Sbrooks tp.packet_length = len; 876147072Sbrooks tp.client_port = from_port; 877147072Sbrooks tp.client_addr = from; 878147072Sbrooks tp.interface = interface; 879147072Sbrooks tp.haddr = hfrom; 880147072Sbrooks 881147072Sbrooks parse_options(&tp); 882147072Sbrooks if (tp.options_valid && 883147072Sbrooks tp.options[DHO_DHCP_MESSAGE_TYPE].data) 884147072Sbrooks tp.packet_type = tp.options[DHO_DHCP_MESSAGE_TYPE].data[0]; 885147072Sbrooks if (tp.packet_type) 886147072Sbrooks dhcp(&tp); 887147072Sbrooks else 888147072Sbrooks bootp(&tp); 889147072Sbrooks 890147072Sbrooks /* Free the data associated with the options. */ 891147072Sbrooks for (i = 0; i < 256; i++) 892147072Sbrooks if (tp.options[i].len && tp.options[i].data) 893147072Sbrooks free(tp.options[i].data); 894147072Sbrooks} 895