2011-03-31 01:27:01 +05:30
|
|
|
/* options.c - DHCP options handling
|
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* Copyright (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
|
|
|
|
* All rights reserved.
|
2011-03-31 01:27:01 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
2011-03-31 01:27:01 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
2011-03-31 01:27:01 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - 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.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
|
2010-11-12 14:32:18 +05:30
|
|
|
*/
|
2010-11-13 01:03:17 +05:30
|
|
|
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "options.h"
|
2011-03-30 16:35:00 +05:30
|
|
|
#include "log.h"
|
2011-07-25 11:31:38 +05:30
|
|
|
#include "ifch_proto.h"
|
2011-03-30 16:35:00 +05:30
|
|
|
|
2011-03-30 21:06:27 +05:30
|
|
|
struct dhcp_option {
|
2011-03-31 02:05:23 +05:30
|
|
|
uint8_t code;
|
2011-07-25 12:58:04 +05:30
|
|
|
uint8_t type;
|
2011-07-25 13:09:17 +05:30
|
|
|
char name[6];
|
2011-03-30 21:06:27 +05:30
|
|
|
};
|
|
|
|
|
2011-03-31 02:04:09 +05:30
|
|
|
// Marks an option that will be sent on the parameter request list to the
|
|
|
|
// remote DHCP server.
|
|
|
|
#define OPTION_REQ 16
|
|
|
|
// Marks an option that can be sent as a list of multiple items.
|
|
|
|
#define OPTION_LIST 32
|
2011-07-25 11:31:38 +05:30
|
|
|
|
2011-07-25 12:58:04 +05:30
|
|
|
// This structure is mostly used here and for finding the correct strings
|
|
|
|
// to describe option commands when sending to ifchd. The type is the more
|
|
|
|
// useful part and helps for safety checks and determining what options to
|
|
|
|
// send in the initial DHCP option request packet.
|
2011-07-11 15:05:40 +05:30
|
|
|
static const struct dhcp_option options[] = {
|
2011-07-25 12:58:04 +05:30
|
|
|
{DCODE_SUBNET , OPTION_IP | OPTION_LIST | OPTION_REQ, CMD_SUBNET },
|
|
|
|
{DCODE_TIMEZONE , OPTION_S32, CMD_TIMEZONE },
|
|
|
|
{DCODE_ROUTER , OPTION_IP | OPTION_REQ, CMD_ROUTER },
|
|
|
|
{DCODE_DNS , OPTION_IP | OPTION_LIST | OPTION_REQ, CMD_DNS },
|
|
|
|
{DCODE_LPRSVR , OPTION_IP | OPTION_LIST, CMD_LPRSVR },
|
|
|
|
{DCODE_HOSTNAME , OPTION_STRING | OPTION_REQ, CMD_HOSTNAME },
|
|
|
|
{DCODE_DOMAIN , OPTION_STRING | OPTION_REQ, CMD_DOMAIN },
|
|
|
|
{DCODE_IPTTL , OPTION_U8, CMD_IPTTL },
|
|
|
|
{DCODE_MTU , OPTION_U16, CMD_MTU },
|
|
|
|
{DCODE_BROADCAST, OPTION_IP | OPTION_REQ, CMD_BROADCAST},
|
|
|
|
{DCODE_NTPSVR , OPTION_IP | OPTION_LIST, CMD_NTPSVR },
|
|
|
|
{DCODE_WINS , OPTION_IP | OPTION_LIST, CMD_WINS },
|
2011-07-25 13:56:34 +05:30
|
|
|
{0x00 , OPTION_NONE, CMD_NULL }
|
2010-11-12 14:32:18 +05:30
|
|
|
};
|
|
|
|
|
2011-03-30 21:06:27 +05:30
|
|
|
enum option_type option_type(uint8_t code)
|
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
for (int i = 0; options[i].code; ++i)
|
2011-03-30 21:06:27 +05:30
|
|
|
if (options[i].code == code)
|
2011-03-31 02:04:09 +05:30
|
|
|
return options[i].type & 0xf;
|
|
|
|
return OPTION_NONE;
|
2011-03-30 21:06:27 +05:30
|
|
|
}
|
|
|
|
|
2011-07-25 13:15:05 +05:30
|
|
|
static const char bad_option_name[] = "BAD";
|
2011-03-30 21:06:27 +05:30
|
|
|
const char *option_name(uint8_t code)
|
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
for (int i = 0; options[i].code; ++i)
|
2011-03-30 21:06:27 +05:30
|
|
|
if (options[i].code == code)
|
2011-03-31 02:05:23 +05:30
|
|
|
return options[i].name;
|
|
|
|
return bad_option_name;
|
2011-03-30 21:06:27 +05:30
|
|
|
}
|
|
|
|
|
2011-03-31 01:27:01 +05:30
|
|
|
static uint8_t option_type_length(enum option_type type)
|
2011-03-30 19:43:34 +05:30
|
|
|
{
|
2011-03-31 02:05:23 +05:30
|
|
|
switch (type) {
|
|
|
|
case OPTION_IP: return 4;
|
|
|
|
case OPTION_U8: return 1;
|
|
|
|
case OPTION_U16: return 2;
|
|
|
|
case OPTION_S16: return 2;
|
|
|
|
case OPTION_U32: return 4;
|
|
|
|
case OPTION_S32: return 4;
|
|
|
|
default: return 0;
|
|
|
|
}
|
2011-03-30 19:43:34 +05:30
|
|
|
}
|
|
|
|
|
2011-03-31 01:27:01 +05:30
|
|
|
uint8_t option_length(uint8_t code)
|
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
for (int i = 0; options[i].code; i++)
|
2011-03-31 02:05:23 +05:30
|
|
|
if (options[i].code == code)
|
|
|
|
return option_type_length(options[i].type & 0xf);
|
2011-07-12 09:21:55 +05:30
|
|
|
log_warning("option_length: Unknown length for code 0x%02x.", code);
|
2011-03-31 02:05:23 +05:30
|
|
|
return 0;
|
2011-03-31 01:27:01 +05:30
|
|
|
}
|
|
|
|
|
2011-03-30 19:43:34 +05:30
|
|
|
int option_valid_list(uint8_t code)
|
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
for (int i = 0; options[i].code; ++i)
|
2011-03-31 02:05:23 +05:30
|
|
|
if ((options[i].code == code) && (options[i].type & OPTION_LIST))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
2011-03-30 19:43:34 +05:30
|
|
|
}
|
|
|
|
|
2011-06-27 22:23:35 +05:30
|
|
|
static size_t sizeof_option(uint8_t code, size_t datalen)
|
2011-03-30 16:35:00 +05:30
|
|
|
{
|
2011-07-25 12:41:47 +05:30
|
|
|
if (code == DCODE_PADDING || code == DCODE_END)
|
2011-03-31 02:05:23 +05:30
|
|
|
return 1;
|
|
|
|
return 2 + datalen;
|
2011-03-30 16:35:00 +05:30
|
|
|
}
|
|
|
|
|
2011-03-30 21:34:38 +05:30
|
|
|
// Worker function for get_option_data(). Optlen will be set to the length
|
2011-03-30 20:09:11 +05:30
|
|
|
// of the option data.
|
2011-03-30 21:34:38 +05:30
|
|
|
static uint8_t *do_get_option_data(uint8_t *buf, ssize_t buflen, int code,
|
2011-03-31 02:05:23 +05:30
|
|
|
char *overload, ssize_t *optlen)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
// option bytes: [code][len]([data1][data2]..[dataLEN])
|
2011-03-31 02:05:23 +05:30
|
|
|
*overload = 0;
|
|
|
|
while (buflen > 0) {
|
|
|
|
// Advance over padding.
|
2011-07-25 12:41:47 +05:30
|
|
|
if (buf[0] == DCODE_PADDING) {
|
2011-03-31 02:05:23 +05:30
|
|
|
buflen--;
|
|
|
|
buf++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We hit the end.
|
2011-07-25 12:41:47 +05:30
|
|
|
if (buf[0] == DCODE_END) {
|
2011-03-31 02:05:23 +05:30
|
|
|
*optlen = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
buflen -= buf[1] + 2;
|
|
|
|
if (buflen < 0) {
|
2011-07-12 09:21:55 +05:30
|
|
|
log_warning("Bad option data: length would exceed options field size.");
|
2011-03-31 02:05:23 +05:30
|
|
|
*optlen = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buf[0] == code) {
|
|
|
|
*optlen = buf[1];
|
|
|
|
return buf + 2;
|
|
|
|
}
|
|
|
|
|
2011-07-25 12:41:47 +05:30
|
|
|
if (buf[0] == DCODE_OVERLOAD) {
|
2011-03-31 02:05:23 +05:30
|
|
|
if (buf[1] == 1)
|
|
|
|
*overload |= buf[2];
|
2011-06-28 00:49:54 +05:30
|
|
|
// fall through
|
2011-03-31 02:05:23 +05:30
|
|
|
}
|
|
|
|
buf += buf[1] + 2;
|
|
|
|
}
|
2011-07-05 07:10:32 +05:30
|
|
|
// End of options field was unmarked: no option data
|
2011-03-31 02:05:23 +05:30
|
|
|
*optlen = 0;
|
|
|
|
return NULL;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2011-07-06 03:48:57 +05:30
|
|
|
// XXX: Never concatenates options. If this is added, refer to RFC3396.
|
2011-03-30 20:09:11 +05:30
|
|
|
// Get an option with bounds checking (warning, result is not aligned)
|
|
|
|
// optlen will be equal to the length of the option data.
|
2011-06-27 02:55:00 +05:30
|
|
|
uint8_t *get_option_data(struct dhcpmsg *packet, int code, ssize_t *optlen)
|
2011-03-30 19:05:17 +05:30
|
|
|
{
|
2011-03-31 02:05:23 +05:30
|
|
|
uint8_t *option, *buf;
|
|
|
|
ssize_t buflen;
|
|
|
|
char overload, parsed_ff = 0;
|
|
|
|
|
|
|
|
buf = packet->options;
|
|
|
|
buflen = sizeof packet->options;
|
|
|
|
|
|
|
|
option = do_get_option_data(buf, buflen, code, &overload, optlen);
|
|
|
|
if (option)
|
|
|
|
return option;
|
|
|
|
|
|
|
|
if (overload & 1) {
|
|
|
|
parsed_ff = 1;
|
|
|
|
option = do_get_option_data(packet->file, sizeof packet->file,
|
|
|
|
code, &overload, optlen);
|
|
|
|
if (option)
|
|
|
|
return option;
|
|
|
|
}
|
|
|
|
if (overload & 2) {
|
|
|
|
option = do_get_option_data(packet->sname, sizeof packet->sname,
|
|
|
|
code, &overload, optlen);
|
|
|
|
if (option)
|
|
|
|
return option;
|
|
|
|
if (!parsed_ff && overload & 1)
|
|
|
|
option = do_get_option_data(packet->file, sizeof packet->file,
|
|
|
|
code, &overload, optlen);
|
|
|
|
}
|
|
|
|
return option;
|
2011-03-30 19:05:17 +05:30
|
|
|
}
|
|
|
|
|
2011-06-28 00:49:54 +05:30
|
|
|
// return the position of the 'end' option
|
2011-06-27 22:23:35 +05:30
|
|
|
ssize_t get_end_option_idx(struct dhcpmsg *packet)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-06-28 00:49:54 +05:30
|
|
|
for (size_t i = 0; i < sizeof packet->options; ++i) {
|
2011-07-25 12:41:47 +05:30
|
|
|
if (packet->options[i] == DCODE_END)
|
2011-03-31 02:05:23 +05:30
|
|
|
return i;
|
2011-07-25 12:41:47 +05:30
|
|
|
if (packet->options[i] == DCODE_PADDING)
|
2011-03-31 02:05:23 +05:30
|
|
|
continue;
|
2011-07-25 12:41:47 +05:30
|
|
|
if (packet->options[i] != DCODE_PADDING)
|
2011-06-27 22:23:35 +05:30
|
|
|
i += packet->options[i+1] + 1;
|
2011-03-31 02:05:23 +05:30
|
|
|
}
|
2011-07-25 12:41:47 +05:30
|
|
|
log_warning("get_end_option_idx: Did not find DCODE_END marker.");
|
2011-03-31 02:05:23 +05:30
|
|
|
return -1;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2011-06-28 00:49:54 +05:30
|
|
|
// add an option string to the options (an option string contains an option
|
|
|
|
// code, length, then data)
|
2011-07-02 13:18:08 +05:30
|
|
|
size_t add_option_string(struct dhcpmsg *packet, uint8_t code, char *str,
|
|
|
|
size_t slen)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-07-02 13:18:08 +05:30
|
|
|
size_t len = sizeof_option(code, slen);
|
|
|
|
if (slen > 255 || len != slen + 2) {
|
|
|
|
log_warning("add_option_string: Length checks failed.");
|
|
|
|
return 0;
|
|
|
|
}
|
2011-03-31 02:05:23 +05:30
|
|
|
|
2011-07-02 14:15:11 +05:30
|
|
|
ssize_t end = get_end_option_idx(packet);
|
2011-03-31 02:05:23 +05:30
|
|
|
if (end == -1) {
|
2011-07-25 12:41:47 +05:30
|
|
|
log_warning("add_option_string: Buffer has no DCODE_END marker.");
|
2011-03-31 02:05:23 +05:30
|
|
|
return 0;
|
|
|
|
}
|
2011-07-02 13:18:08 +05:30
|
|
|
if (end + len >= sizeof packet->options) {
|
2011-07-12 09:21:55 +05:30
|
|
|
log_warning("add_option_string: No space for option 0x%02x.", code);
|
2011-03-31 02:05:23 +05:30
|
|
|
return 0;
|
|
|
|
}
|
2011-07-02 13:18:08 +05:30
|
|
|
packet->options[end] = code;
|
|
|
|
packet->options[end+1] = slen;
|
|
|
|
memcpy(packet->options + end + 2, str, slen);
|
2011-07-25 12:41:47 +05:30
|
|
|
packet->options[end+len] = DCODE_END;
|
2011-07-02 13:18:08 +05:30
|
|
|
return len;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2011-07-02 14:15:11 +05:30
|
|
|
static ssize_t add_option_check(struct dhcpmsg *packet, uint8_t code,
|
|
|
|
uint8_t rlen)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-07-02 14:15:11 +05:30
|
|
|
ssize_t end = get_end_option_idx(packet);
|
2011-07-02 13:18:08 +05:30
|
|
|
if (end == -1) {
|
2011-07-25 12:41:47 +05:30
|
|
|
log_warning("add_u%01u_option: Buffer has no DCODE_END marker.", rlen*8);
|
2011-07-02 14:15:11 +05:30
|
|
|
return -1;
|
2011-03-31 02:05:23 +05:30
|
|
|
}
|
2011-07-02 14:15:11 +05:30
|
|
|
if (end + 2 + rlen >= sizeof packet->options) {
|
2011-07-12 09:21:55 +05:30
|
|
|
log_warning("add_u%01u_option: No space for option 0x%02x.",
|
2011-07-02 14:15:11 +05:30
|
|
|
rlen*8, code);
|
|
|
|
return -1;
|
2011-07-02 13:18:08 +05:30
|
|
|
}
|
2011-07-02 14:15:11 +05:30
|
|
|
return end;
|
|
|
|
}
|
|
|
|
|
2011-07-26 09:04:32 +05:30
|
|
|
static size_t add_u8_option(struct dhcpmsg *packet, uint8_t code, uint8_t data)
|
2011-07-02 14:15:11 +05:30
|
|
|
{
|
|
|
|
ssize_t end = add_option_check(packet, code, 1);
|
|
|
|
if (end < 0)
|
|
|
|
return 0;
|
|
|
|
packet->options[end] = code;
|
|
|
|
packet->options[end+1] = 1;
|
|
|
|
packet->options[end+2] = data;
|
2011-07-25 12:41:47 +05:30
|
|
|
packet->options[end+3] = DCODE_END;
|
2011-07-02 14:15:11 +05:30
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data should be in network byte order.
|
2011-07-26 09:04:32 +05:30
|
|
|
static size_t add_u16_option(struct dhcpmsg *packet, uint8_t code,
|
|
|
|
uint16_t data)
|
2011-07-02 14:15:11 +05:30
|
|
|
{
|
|
|
|
ssize_t end = add_option_check(packet, code, 2);
|
|
|
|
if (end < 0)
|
|
|
|
return 0;
|
|
|
|
uint8_t *dp = (uint8_t *)&data;
|
|
|
|
packet->options[end] = code;
|
|
|
|
packet->options[end+1] = 2;
|
|
|
|
packet->options[end+2] = dp[0];
|
|
|
|
packet->options[end+3] = dp[1];
|
2011-07-25 12:41:47 +05:30
|
|
|
packet->options[end+4] = DCODE_END;
|
2011-07-02 14:15:11 +05:30
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data should be in network byte order.
|
2011-07-26 09:04:32 +05:30
|
|
|
static size_t add_u32_option(struct dhcpmsg *packet, uint8_t code,
|
|
|
|
uint32_t data)
|
2011-07-02 14:15:11 +05:30
|
|
|
{
|
|
|
|
ssize_t end = add_option_check(packet, code, 4);
|
|
|
|
if (end < 0)
|
|
|
|
return 0;
|
|
|
|
uint8_t *dp = (uint8_t *)&data;
|
2011-07-02 13:18:08 +05:30
|
|
|
packet->options[end] = code;
|
2011-07-02 14:15:11 +05:30
|
|
|
packet->options[end+1] = 4;
|
|
|
|
packet->options[end+2] = dp[0];
|
|
|
|
packet->options[end+3] = dp[1];
|
|
|
|
packet->options[end+4] = dp[2];
|
|
|
|
packet->options[end+5] = dp[3];
|
2011-07-25 12:41:47 +05:30
|
|
|
packet->options[end+6] = DCODE_END;
|
2011-07-02 14:15:11 +05:30
|
|
|
return 6;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2011-06-28 00:49:54 +05:30
|
|
|
// Add a paramater request list for stubborn DHCP servers
|
2011-07-02 13:18:08 +05:30
|
|
|
size_t add_option_request_list(struct dhcpmsg *packet)
|
2011-03-30 16:56:42 +05:30
|
|
|
{
|
2011-03-31 02:04:09 +05:30
|
|
|
uint8_t reqdata[256];
|
2011-07-02 13:18:08 +05:30
|
|
|
size_t j = 0;
|
2011-06-28 00:49:54 +05:30
|
|
|
for (int i = 0; options[i].code; i++) {
|
2011-03-31 02:04:09 +05:30
|
|
|
if (options[i].type & OPTION_REQ)
|
|
|
|
reqdata[j++] = options[i].code;
|
|
|
|
}
|
2011-07-25 12:41:47 +05:30
|
|
|
return add_option_string(packet, DCODE_PARAM_REQ, (char *)reqdata, j);
|
2011-07-01 21:07:13 +05:30
|
|
|
}
|
|
|
|
|
2011-07-26 09:04:32 +05:30
|
|
|
void add_option_msgtype(struct dhcpmsg *packet, uint8_t type)
|
|
|
|
{
|
|
|
|
add_u8_option(packet, DCODE_MSGTYPE, type);
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_option_reqip(struct dhcpmsg *packet, uint32_t ip)
|
|
|
|
{
|
|
|
|
add_u32_option(packet, DCODE_REQIP, ip);
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_option_maxsize(struct dhcpmsg *packet)
|
|
|
|
{
|
|
|
|
add_u16_option(packet, DCODE_MAX_SIZE,
|
|
|
|
htons(sizeof(struct ip_udp_dhcp_packet)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_option_serverid(struct dhcpmsg *packet, uint32_t sid)
|
|
|
|
{
|
|
|
|
add_u32_option(packet, DCODE_SERVER_ID, sid);
|
|
|
|
}
|
|
|
|
|