#include <ctype.h>
#include "dhcpd.h"
int parse_option_buffer(struct option_data *, unsigned char *, int);
int
parse_option_buffer(struct option_data *options, unsigned char *buffer,
int length)
{
unsigned char *s, *t, *end = buffer + length;
int len, code;
for (s = buffer; *s != DHO_END && s < end; ) {
code = s[0];
if (code == DHO_PAD) {
s++;
continue;
}
if (s + 1 < end) {
len = s[1];
if (s + 1 + len < end) {
;
} else {
warning("option %s (%d) larger than buffer.",
dhcp_options[code].name, len);
warning("rejecting bogus offer.");
return (0);
}
} else {
warning("option %s has no length field.",
dhcp_options[code].name);
warning("rejecting bogus offer.");
return (0);
}
if (dhcp_options[code].format[0] == 't') {
while (len > 0 && s[len + 1] == '\0')
len--;
}
if (!options[code].data) {
if (!(t = calloc(1, len + 1)))
error("Can't allocate storage for option %s.",
dhcp_options[code].name);
memcpy(t, &s[2], len);
t[len] = 0;
options[code].len = len;
options[code].data = t;
} else {
t = calloc(1, len + options[code].len + 1);
if (!t)
error("Can't expand storage for option %s.",
dhcp_options[code].name);
memcpy(t, options[code].data, options[code].len);
memcpy(t + options[code].len, &s[2], len);
options[code].len += len;
t[options[code].len] = 0;
free(options[code].data);
options[code].data = t;
}
s += len + 2;
}
return (1);
}
int
cons_options(struct option_data *options)
{
unsigned char *buf = client->packet.options;
int buflen = 576 - DHCP_FIXED_LEN;
int ix, incr, length, bufix, code, lastopt = -1;
bzero(buf, buflen);
memcpy(buf, DHCP_OPTIONS_COOKIE, 4);
if (options[DHO_DHCP_MESSAGE_TYPE].data) {
memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3);
buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0];
bufix = 7;
} else
bufix = 4;
for (code = DHO_SUBNET_MASK; code < DHO_END; code++) {
if (!options[code].data || code == DHO_DHCP_MESSAGE_TYPE)
continue;
length = options[code].len;
if (bufix + length + 2*((length+254)/255) >= buflen)
return (lastopt);
lastopt = bufix;
ix = 0;
while (length) {
incr = length > 255 ? 255 : length;
buf[bufix++] = code;
buf[bufix++] = incr;
memcpy(buf + bufix, options[code].data + ix, incr);
length -= incr;
ix += incr;
bufix += incr;
}
}
if (bufix < buflen) {
buf[bufix] = DHO_END;
lastopt = bufix;
}
return (lastopt);
}
char *
pretty_print_option(unsigned int code, struct option_data *option,
int emit_punct)
{
static char optbuf[32768];
int hunksize = 0, numhunk = -1, numelem = 0;
char fmtbuf[32], *op = optbuf;
int i, j, k, opleft = sizeof(optbuf);
unsigned char *data = option->data;
unsigned char *dp = data;
int len = option->len;
struct in_addr foo;
char comma;
if (code > 255)
error("pretty_print_option: bad code %d", code);
if (emit_punct)
comma = ',';
else
comma = ' ';
for (i = 0; dhcp_options[code].format[i]; i++) {
if (!numhunk) {
warning("%s: Excess information in format string: %s",
dhcp_options[code].name,
&(dhcp_options[code].format[i]));
break;
}
numelem++;
fmtbuf[i] = dhcp_options[code].format[i];
switch (dhcp_options[code].format[i]) {
case 'A':
--numelem;
fmtbuf[i] = 0;
numhunk = 0;
if (hunksize == 0) {
warning("%s: no size indicator before A"
" in format string: %s",
dhcp_options[code].name,
dhcp_options[code].format);
return ("<fmt error>");
}
break;
case 'X':
for (k = 0; k < len; k++)
if (!isascii(data[k]) ||
!isprint(data[k]))
break;
if (k == len) {
fmtbuf[i] = 't';
numhunk = -2;
} else {
fmtbuf[i] = 'x';
hunksize++;
comma = ':';
numhunk = 0;
}
fmtbuf[i + 1] = 0;
break;
case 't':
fmtbuf[i] = 't';
fmtbuf[i + 1] = 0;
numhunk = -2;
break;
case 'I':
case 'l':
case 'L':
hunksize += 4;
break;
case 's':
case 'S':
hunksize += 2;
break;
case 'b':
case 'B':
case 'f':
hunksize++;
break;
case 'e':
break;
default:
warning("%s: garbage in format string: %s",
dhcp_options[code].name,
&(dhcp_options[code].format[i]));
break;
}
}
if (hunksize > len) {
warning("%s: expecting at least %d bytes; got %d",
dhcp_options[code].name, hunksize, len);
return ("<error>");
}
if (numhunk == -1 && hunksize < len)
warning("%s: %d extra bytes",
dhcp_options[code].name, len - hunksize);
if (!numhunk)
numhunk = len / hunksize;
if (numhunk > 0 && numhunk * hunksize < len)
warning("%s: %d extra bytes at end of array",
dhcp_options[code].name, len - numhunk * hunksize);
if (numhunk < 0)
numhunk = 1;
for (i = 0; i < numhunk; i++) {
for (j = 0; j < numelem; j++) {
int opcount;
size_t oplen;
switch (fmtbuf[j]) {
case 't':
if (emit_punct) {
*op++ = '"';
opleft--;
}
for (; dp < data + len; dp++) {
if (!isascii(*dp) ||
!isprint(*dp)) {
if (dp + 1 != data + len ||
*dp != 0) {
size_t oplen;
snprintf(op, opleft,
"\\%03o", *dp);
oplen = strlen(op);
op += oplen;
opleft -= oplen;
}
} else if (*dp == '"' ||
*dp == '\'' ||
*dp == '$' ||
*dp == '`' ||
*dp == '\\') {
*op++ = '\\';
*op++ = *dp;
opleft -= 2;
} else {
*op++ = *dp;
opleft--;
}
}
if (emit_punct) {
*op++ = '"';
opleft--;
}
*op = 0;
break;
case 'I':
foo.s_addr = htonl(getULong(dp));
opcount = strlcpy(op, inet_ntoa(foo), opleft);
if (opcount >= opleft)
goto toobig;
opleft -= opcount;
dp += 4;
break;
case 'l':
opcount = snprintf(op, opleft, "%ld",
(long)getLong(dp));
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
dp += 4;
break;
case 'L':
opcount = snprintf(op, opleft, "%ld",
(unsigned long)getULong(dp));
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
dp += 4;
break;
case 's':
opcount = snprintf(op, opleft, "%d",
getShort(dp));
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
dp += 2;
break;
case 'S':
opcount = snprintf(op, opleft, "%d",
getUShort(dp));
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
dp += 2;
break;
case 'b':
opcount = snprintf(op, opleft, "%d",
*(char *)dp++);
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
break;
case 'B':
opcount = snprintf(op, opleft, "%d", *dp++);
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
break;
case 'x':
opcount = snprintf(op, opleft, "%x", *dp++);
if (opcount >= opleft || opcount == -1)
goto toobig;
opleft -= opcount;
break;
case 'f':
opcount = strlcpy(op,
*dp++ ? "true" : "false", opleft);
if (opcount >= opleft)
goto toobig;
opleft -= opcount;
break;
default:
warning("Unexpected format code %c", fmtbuf[j]);
}
oplen = strlen(op);
op += oplen;
opleft -= oplen;
if (opleft < 1)
goto toobig;
if (j + 1 < numelem && comma != ':') {
*op++ = ' ';
opleft--;
}
}
if (i + 1 < numhunk) {
*op++ = comma;
opleft--;
}
if (opleft < 1)
goto toobig;
}
return (optbuf);
toobig:
warning("dhcp option too large");
return ("<error>");
}
void
do_packet(int len, unsigned int from_port, struct iaddr from,
struct hardware *hfrom)
{
struct dhcp_packet *packet = &client->packet;
struct option_data options[256];
struct iaddrlist *ap;
void (*handler)(struct iaddr, struct option_data *);
char *type;
int i, options_valid = 1;
if (packet->hlen > sizeof(packet->chaddr)) {
note("Discarding packet with invalid hlen.");
return;
}
if ((ifi->hw_address.hlen != packet->hlen) ||
(memcmp(ifi->hw_address.haddr, packet->chaddr, packet->hlen)))
return;
memset(options, 0, sizeof(options));
if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) {
options_valid = parse_option_buffer(options,
&packet->options[4], sizeof(packet->options) - 4);
if (options_valid &&
options[DHO_DHCP_MESSAGE_TYPE].data &&
options[DHO_DHCP_OPTION_OVERLOAD].data) {
if (options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
options_valid = parse_option_buffer(options,
(unsigned char *)packet->file,
sizeof(packet->file));
if (options_valid &&
options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
options_valid = parse_option_buffer(options,
(unsigned char *)packet->sname,
sizeof(packet->sname));
}
}
type = "";
handler = NULL;
if (options[DHO_DHCP_MESSAGE_TYPE].data) {
switch (options[DHO_DHCP_MESSAGE_TYPE].data[0]) {
case DHCPOFFER:
handler = dhcpoffer;
type = "DHCPOFFER";
break;
case DHCPNAK:
handler = dhcpnak;
type = "DHCPNACK";
break;
case DHCPACK:
handler = dhcpack;
type = "DHCPACK";
break;
default:
break;
}
} else if (options_valid && packet->op == BOOTREPLY) {
handler = dhcpoffer;
type = "BOOTREPLY";
}
if (handler && client->xid == client->packet.xid) {
if (hfrom->hlen == 6)
note("%s from %s (%s)", type, piaddr(from),
ether_ntoa((struct ether_addr *)hfrom->haddr));
else
note("%s from %s", type, piaddr(from));
} else
handler = NULL;
for (ap = config->reject_list; ap && handler; ap = ap->next)
if (addr_eq(from, ap->addr)) {
note("%s from %s rejected.", type, piaddr(from));
handler = NULL;
}
if (handler)
(*handler)(from, options);
for (i = 0; i < 256; i++)
if (options[i].len && options[i].data)
free(options[i].data);
}