ndhc/ndhc/options.c

341 lines
10 KiB
C
Raw Normal View History

/* options.c - DHCP options handling
*
* Copyright (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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-12 14:32:18 +05:30
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "options.h"
#include "log.h"
#include "ifch_proto.h"
// Worker function for get_option_data(). Optlen will be set to the length
// of the option data.
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) {
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;
}
// 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
}
// XXX: Never concatenates options. If this is added, refer to RFC3396.
// Get an option with bounds checking (warning, result is not aligned)
// optlen will be equal to the length of the option data.
uint8_t *get_option_data(struct dhcpmsg *packet, int code, ssize_t *optlen)
{
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-06-28 00:49:54 +05:30
// return the position of the 'end' option
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)
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
}
static size_t sizeof_option(uint8_t code, size_t datalen)
{
if (code == DCODE_PADDING || code == DCODE_END)
return 1;
return 2 + datalen;
}
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)
static size_t add_option_string(struct dhcpmsg *packet, uint8_t code,
char *str, size_t slen)
2010-11-12 14:32:18 +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
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;
}
if (end + len >= sizeof packet->options) {
log_warning("add_option_string: No space for option 0x%02x.", code);
2011-03-31 02:05:23 +05:30
return 0;
}
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;
return len;
2010-11-12 14:32:18 +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
{
ssize_t end = get_end_option_idx(packet);
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);
return -1;
2011-03-31 02:05:23 +05:30
}
if (end + 2 + rlen >= sizeof packet->options) {
log_warning("add_u%01u_option: No space for option 0x%02x.",
rlen*8, code);
return -1;
}
return end;
}
static size_t add_u8_option(struct dhcpmsg *packet, uint8_t code, uint8_t data)
{
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;
return 3;
}
// Data should be in network byte order.
static size_t add_u16_option(struct dhcpmsg *packet, uint8_t code,
uint16_t data)
{
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;
return 4;
}
// Data should be in network byte order.
static size_t add_u32_option(struct dhcpmsg *packet, uint8_t code,
uint32_t data)
{
ssize_t end = add_option_check(packet, code, 4);
if (end < 0)
return 0;
uint8_t *dp = (uint8_t *)&data;
packet->options[end] = code;
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;
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
size_t add_option_request_list(struct dhcpmsg *packet)
{
static const uint8_t reqdata[] = {
DCODE_SUBNET, DCODE_ROUTER, DCODE_DNS, DCODE_HOSTNAME, DCODE_DOMAIN,
DCODE_BROADCAST,
};
return add_option_string(packet, DCODE_PARAM_REQ,
(char *)reqdata, sizeof reqdata);
}
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);
}
void add_option_vendor(struct dhcpmsg *packet)
{
size_t len = strlen(client_config.vendor);
if (len)
add_option_string(packet, DCODE_VENDOR, client_config.vendor, len);
else
add_option_string(packet, DCODE_VENDOR, "ndhc", sizeof "ndhc" - 1);
}
void add_option_clientid(struct dhcpmsg *packet)
{
char buf[sizeof client_config.clientid + 1];
size_t len = 6;
buf[0] = 1; // Ethernet MAC
if (!client_config.clientid_mac) {
size_t slen = strlen(client_config.clientid);
if (!slen) {
memcpy(buf+1, client_config.arp, len);
} else {
buf[0] = 0; // Not a hardware address
len = slen;
memcpy(buf+1, client_config.clientid, slen);
}
} else
memcpy(buf+1, client_config.clientid, len);
add_option_string(packet, DCODE_CLIENT_ID, buf, len+1);
}
void add_option_hostname(struct dhcpmsg *packet)
{
size_t len = strlen(client_config.hostname);
if (len)
add_option_string(packet, DCODE_HOSTNAME, client_config.hostname, len);
}
uint32_t get_option_router(struct dhcpmsg *packet)
{
ssize_t ol;
uint32_t ret = 0;
uint8_t *od = get_option_data(packet, DCODE_ROUTER, &ol);
if (ol == sizeof ret)
memcpy(&ret, od, sizeof ret);
return ret;
}
uint8_t get_option_msgtype(struct dhcpmsg *packet)
{
ssize_t ol;
uint8_t ret = 0;
uint8_t *t = get_option_data(packet, DCODE_MSGTYPE, &ol);
if (t)
ret = *t;
return ret;
}
uint32_t get_option_serverid(struct dhcpmsg *packet, int *found)
{
ssize_t ol;
uint8_t *t;
uint32_t ret = 0;
*found = 0;
t = get_option_data(packet, DCODE_SERVER_ID, &ol);
if (ol == sizeof ret) {
*found = 1;
memcpy(&ret, t, sizeof ret);
}
return ret;
}
uint32_t get_option_leasetime(struct dhcpmsg *packet)
{
ssize_t ol;
uint8_t *t;
uint32_t ret = 0;
t = get_option_data(packet, DCODE_LEASET, &ol);
if (ol == sizeof ret) {
memcpy(&ret, t, sizeof ret);
ret = ntohl(ret);
}
return ret;
}