1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Routines used to extract/insert DHCP options. Must be kept MT SAFE, 26 * as they are called from different threads. 27 */ 28 29 #include <sys/types.h> 30 #include "dhcp_impl.h" 31 #if defined(_KERNEL) && !defined(_BOOT) 32 #include <sys/sunddi.h> 33 #else 34 #include <strings.h> 35 #endif /* _KERNEL && !_BOOT */ 36 37 static uint8_t bootmagic[] = BOOTMAGIC; 38 39 /* 40 * Scan field for options. 41 */ 42 static void 43 field_scan(uint8_t *start, uint8_t *end, DHCP_OPT **options, 44 uint8_t last_option) 45 { 46 uint8_t *current; 47 48 while (start < end) { 49 if (*start == CD_PAD) { 50 start++; 51 continue; 52 } 53 if (*start == CD_END) 54 break; /* done */ 55 if (*start > last_option) { 56 if (++start < end) 57 start += *start + 1; 58 continue; /* unrecognized option */ 59 } 60 61 current = start; 62 if (++start < end) 63 start += *start + 1; /* advance to next option */ 64 65 /* all options besides CD_END and CD_PAD should have a len */ 66 if ((current + 1) >= end) 67 continue; 68 69 /* Ignores duplicate options. */ 70 if (options[*current] == NULL) { 71 72 options[*current] = (DHCP_OPT *)current; 73 74 /* verify that len won't go beyond end */ 75 if ((current + options[*current]->len + 1) >= end) { 76 options[*current] = NULL; 77 continue; 78 } 79 } 80 } 81 } 82 83 /* 84 * Scan Vendor field for options. 85 */ 86 static void 87 vendor_scan(PKT_LIST *pl) 88 { 89 uint8_t *start, *end, len; 90 91 if (pl->opts[CD_VENDOR_SPEC] == NULL) 92 return; 93 len = pl->opts[CD_VENDOR_SPEC]->len; 94 start = pl->opts[CD_VENDOR_SPEC]->value; 95 96 /* verify that len won't go beyond the end of the packet */ 97 if (((start - (uint8_t *)pl->pkt) + len) > pl->len) 98 return; 99 100 end = start + len; 101 field_scan(start, end, pl->vs, VS_OPTION_END); 102 } 103 104 /* 105 * Load opts table in PKT_LIST entry with PKT's options. 106 * Returns 0 if no fatal errors occur, otherwise... 107 */ 108 int 109 dhcp_options_scan(PKT_LIST *pl, boolean_t scan_vendor) 110 { 111 PKT *pkt = pl->pkt; 112 uint_t opt_size = pl->len - BASE_PKT_SIZE; 113 114 /* 115 * bcmp() is used here instead of memcmp() since kernel/standalone 116 * doesn't have a memcmp(). 117 */ 118 if (pl->len < BASE_PKT_SIZE || 119 bcmp(pl->pkt->cookie, bootmagic, sizeof (pl->pkt->cookie)) != 0) { 120 pl->rfc1048 = 0; 121 return (0); 122 } 123 124 pl->rfc1048 = 1; 125 126 /* check the options field */ 127 field_scan(pkt->options, &pkt->options[opt_size], pl->opts, 128 DHCP_LAST_OPT); 129 130 /* 131 * process vendor specific options. We look at the vendor options 132 * here, simply because a BOOTP server could fake DHCP vendor 133 * options. This increases our interoperability with BOOTP. 134 */ 135 if (scan_vendor && (pl->opts[CD_VENDOR_SPEC] != NULL)) 136 vendor_scan(pl); 137 138 if (pl->opts[CD_DHCP_TYPE] == NULL) 139 return (0); 140 141 if (pl->opts[CD_DHCP_TYPE]->len != 1) 142 return (DHCP_GARBLED_MSG_TYPE); 143 144 if (*pl->opts[CD_DHCP_TYPE]->value < DISCOVER || 145 *pl->opts[CD_DHCP_TYPE]->value > INFORM) 146 return (DHCP_WRONG_MSG_TYPE); 147 148 if (pl->opts[CD_OPTION_OVERLOAD]) { 149 if (pl->opts[CD_OPTION_OVERLOAD]->len != 1) { 150 pl->opts[CD_OPTION_OVERLOAD] = NULL; 151 return (DHCP_BAD_OPT_OVLD); 152 } 153 switch (*pl->opts[CD_OPTION_OVERLOAD]->value) { 154 case 1: 155 field_scan(pkt->file, &pkt->cookie[0], pl->opts, 156 DHCP_LAST_OPT); 157 break; 158 case 2: 159 field_scan(pkt->sname, &pkt->file[0], pl->opts, 160 DHCP_LAST_OPT); 161 break; 162 case 3: 163 field_scan(pkt->file, &pkt->cookie[0], pl->opts, 164 DHCP_LAST_OPT); 165 field_scan(pkt->sname, &pkt->file[0], pl->opts, 166 DHCP_LAST_OPT); 167 break; 168 default: 169 pl->opts[CD_OPTION_OVERLOAD] = NULL; 170 return (DHCP_BAD_OPT_OVLD); 171 } 172 } 173 return (0); 174 } 175 176 /* 177 * Locate a DHCPv6 option or suboption within a buffer. DHCPv6 uses nested 178 * options within options, and this function is designed to work with both 179 * primary options and the suboptions contained within. 180 * 181 * The 'oldopt' is a previous option pointer, and is typically used to iterate 182 * over options of the same code number. The 'codenum' is in host byte order 183 * for simplicity. 'retlenp' may be NULL, and if present gets the _entire_ 184 * option length (including header). 185 * 186 * Warning: the returned pointer has no particular alignment because DHCPv6 187 * defines options without alignment. The caller must deal with unaligned 188 * pointers carefully. 189 */ 190 dhcpv6_option_t * 191 dhcpv6_find_option(const void *buffer, size_t buflen, 192 const dhcpv6_option_t *oldopt, uint16_t codenum, uint_t *retlenp) 193 { 194 const uchar_t *bp; 195 dhcpv6_option_t d6o; 196 uint_t olen; 197 198 codenum = htons(codenum); 199 bp = buffer; 200 while (buflen >= sizeof (dhcpv6_option_t)) { 201 (void) memcpy(&d6o, bp, sizeof (d6o)); 202 olen = ntohs(d6o.d6o_len) + sizeof (d6o); 203 if (olen > buflen) 204 break; 205 if (d6o.d6o_code != codenum || 206 (oldopt != NULL && bp <= (const uchar_t *)oldopt)) { 207 bp += olen; 208 buflen -= olen; 209 continue; 210 } 211 if (retlenp != NULL) 212 *retlenp = olen; 213 /* LINTED: alignment */ 214 return ((dhcpv6_option_t *)bp); 215 } 216 return (NULL); 217 } 218 219 /* 220 * Locate a DHCPv6 option within the top level of a PKT_LIST entry. DHCPv6 221 * uses nested options within options, and this function returns only the 222 * primary options. Use dhcpv6_find_option to traverse suboptions. 223 * 224 * See dhcpv6_find_option for usage details and warnings. 225 */ 226 dhcpv6_option_t * 227 dhcpv6_pkt_option(const PKT_LIST *plp, const dhcpv6_option_t *oldopt, 228 uint16_t codenum, uint_t *retlenp) 229 { 230 const dhcpv6_message_t *d6m; 231 232 if (plp == NULL || plp->pkt == NULL || plp->len < sizeof (*d6m)) 233 return (NULL); 234 d6m = (const dhcpv6_message_t *)plp->pkt; 235 return (dhcpv6_find_option(d6m + 1, plp->len - sizeof (*d6m), oldopt, 236 codenum, retlenp)); 237 } 238