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 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 #include <sys/types.h> 32 #include "dhcp_impl.h" 33 #if defined(_KERNEL) && !defined(_BOOT) 34 #include <sys/sunddi.h> 35 #else 36 #include <strings.h> 37 #endif /* _KERNEL && !_BOOT */ 38 39 static uint8_t bootmagic[] = BOOTMAGIC; 40 41 /* 42 * Scan field for options. 43 */ 44 static void 45 field_scan(uint8_t *start, uint8_t *end, DHCP_OPT **options, 46 uint8_t last_option) 47 { 48 uint8_t *current; 49 50 while (start < end) { 51 if (*start == CD_PAD) { 52 start++; 53 continue; 54 } 55 if (*start == CD_END) 56 break; /* done */ 57 if (*start > last_option) { 58 if (++start < end) 59 start += *start + 1; 60 continue; /* unrecognized option */ 61 } 62 63 current = start; 64 if (++start < end) 65 start += *start + 1; /* advance to next option */ 66 67 /* all options besides CD_END and CD_PAD should have a len */ 68 if ((current + 1) >= end) 69 continue; 70 71 /* Ignores duplicate options. */ 72 if (options[*current] == NULL) { 73 74 options[*current] = (DHCP_OPT *)current; 75 76 /* verify that len won't go beyond end */ 77 if ((current + options[*current]->len + 1) >= end) { 78 options[*current] = NULL; 79 continue; 80 } 81 } 82 } 83 } 84 85 /* 86 * Scan Vendor field for options. 87 */ 88 static void 89 vendor_scan(PKT_LIST *pl) 90 { 91 uint8_t *start, *end, len; 92 93 if (pl->opts[CD_VENDOR_SPEC] == NULL) 94 return; 95 len = pl->opts[CD_VENDOR_SPEC]->len; 96 start = pl->opts[CD_VENDOR_SPEC]->value; 97 98 /* verify that len won't go beyond the end of the packet */ 99 if (((start - (uint8_t *)pl->pkt) + len) > pl->len) 100 return; 101 102 end = start + len; 103 field_scan(start, end, pl->vs, VS_OPTION_END); 104 } 105 106 /* 107 * Load opts table in PKT_LIST entry with PKT's options. 108 * Returns 0 if no fatal errors occur, otherwise... 109 */ 110 int 111 dhcp_options_scan(PKT_LIST *pl, boolean_t scan_vendor) 112 { 113 PKT *pkt = pl->pkt; 114 uint_t opt_size = pl->len - BASE_PKT_SIZE; 115 116 /* 117 * bcmp() is used here instead of memcmp() since kernel/standalone 118 * doesn't have a memcmp(). 119 */ 120 if (pl->len < BASE_PKT_SIZE || 121 bcmp(pl->pkt->cookie, bootmagic, sizeof (pl->pkt->cookie)) != 0) { 122 pl->rfc1048 = 0; 123 return (0); 124 } 125 126 pl->rfc1048 = 1; 127 128 /* check the options field */ 129 field_scan(pkt->options, &pkt->options[opt_size], pl->opts, 130 DHCP_LAST_OPT); 131 132 /* 133 * process vendor specific options. We look at the vendor options 134 * here, simply because a BOOTP server could fake DHCP vendor 135 * options. This increases our interoperability with BOOTP. 136 */ 137 if (scan_vendor && (pl->opts[CD_VENDOR_SPEC] != NULL)) 138 vendor_scan(pl); 139 140 if (pl->opts[CD_DHCP_TYPE] == NULL) 141 return (0); 142 143 if (pl->opts[CD_DHCP_TYPE]->len != 1) 144 return (DHCP_GARBLED_MSG_TYPE); 145 146 if (*pl->opts[CD_DHCP_TYPE]->value < DISCOVER || 147 *pl->opts[CD_DHCP_TYPE]->value > INFORM) 148 return (DHCP_WRONG_MSG_TYPE); 149 150 if (pl->opts[CD_OPTION_OVERLOAD]) { 151 if (pl->opts[CD_OPTION_OVERLOAD]->len != 1) { 152 pl->opts[CD_OPTION_OVERLOAD] = NULL; 153 return (DHCP_BAD_OPT_OVLD); 154 } 155 switch (*pl->opts[CD_OPTION_OVERLOAD]->value) { 156 case 1: 157 field_scan(pkt->file, &pkt->cookie[0], pl->opts, 158 DHCP_LAST_OPT); 159 break; 160 case 2: 161 field_scan(pkt->sname, &pkt->file[0], pl->opts, 162 DHCP_LAST_OPT); 163 break; 164 case 3: 165 field_scan(pkt->file, &pkt->cookie[0], pl->opts, 166 DHCP_LAST_OPT); 167 field_scan(pkt->sname, &pkt->file[0], pl->opts, 168 DHCP_LAST_OPT); 169 break; 170 default: 171 pl->opts[CD_OPTION_OVERLOAD] = NULL; 172 return (DHCP_BAD_OPT_OVLD); 173 } 174 } 175 return (0); 176 } 177 178 /* 179 * Locate a DHCPv6 option or suboption within a buffer. DHCPv6 uses nested 180 * options within options, and this function is designed to work with both 181 * primary options and the suboptions contained within. 182 * 183 * The 'oldopt' is a previous option pointer, and is typically used to iterate 184 * over options of the same code number. The 'codenum' is in host byte order 185 * for simplicity. 'retlenp' may be NULL, and if present gets the _entire_ 186 * option length (including header). 187 * 188 * Warning: the returned pointer has no particular alignment because DHCPv6 189 * defines options without alignment. The caller must deal with unaligned 190 * pointers carefully. 191 */ 192 dhcpv6_option_t * 193 dhcpv6_find_option(const void *buffer, size_t buflen, 194 const dhcpv6_option_t *oldopt, uint16_t codenum, uint_t *retlenp) 195 { 196 const uchar_t *bp; 197 dhcpv6_option_t d6o; 198 uint_t olen; 199 200 codenum = htons(codenum); 201 bp = buffer; 202 while (buflen >= sizeof (dhcpv6_option_t)) { 203 (void) memcpy(&d6o, bp, sizeof (d6o)); 204 olen = ntohs(d6o.d6o_len) + sizeof (d6o); 205 if (olen > buflen) 206 break; 207 if (d6o.d6o_code != codenum || 208 (oldopt != NULL && bp <= (const uchar_t *)oldopt)) { 209 bp += olen; 210 buflen -= olen; 211 continue; 212 } 213 if (retlenp != NULL) 214 *retlenp = olen; 215 /* LINTED: alignment */ 216 return ((dhcpv6_option_t *)bp); 217 } 218 return (NULL); 219 } 220 221 /* 222 * Locate a DHCPv6 option within the top level of a PKT_LIST entry. DHCPv6 223 * uses nested options within options, and this function returns only the 224 * primary options. Use dhcpv6_find_option to traverse suboptions. 225 * 226 * See dhcpv6_find_option for usage details and warnings. 227 */ 228 dhcpv6_option_t * 229 dhcpv6_pkt_option(const PKT_LIST *plp, const dhcpv6_option_t *oldopt, 230 uint16_t codenum, uint_t *retlenp) 231 { 232 const dhcpv6_message_t *d6m; 233 234 if (plp == NULL || plp->pkt == NULL || plp->len < sizeof (*d6m)) 235 return (NULL); 236 d6m = (const dhcpv6_message_t *)plp->pkt; 237 return (dhcpv6_find_option(d6m + 1, plp->len - sizeof (*d6m), oldopt, 238 codenum, retlenp)); 239 } 240