blob: 588684e6cf44441e6588210a8aadb84af177fc3f [file] [log] [blame]
/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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 AUTHOR 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 AUTHOR 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.
*/
#include <sys/utsname.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "common.h"
#include "dhcp-common.h"
#include "dhcp.h"
#include "if.h"
#include "ipv6.h"
void
dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols)
{
while (cols < 40) {
putchar(' ');
cols++;
}
putchar('\t');
if (opt->type & EMBED)
printf(" embed");
if (opt->type & ENCAP)
printf(" encap");
if (opt->type & INDEX)
printf(" index");
if (opt->type & ARRAY)
printf(" array");
if (opt->type & UINT8)
printf(" byte");
else if (opt->type & UINT16)
printf(" uint16");
else if (opt->type & SINT16)
printf(" sint16");
else if (opt->type & UINT32)
printf(" uint32");
else if (opt->type & SINT32)
printf(" sint32");
else if (opt->type & ADDRIPV4)
printf(" ipaddress");
else if (opt->type & ADDRIPV6)
printf(" ip6address");
else if (opt->type & FLAG)
printf(" flag");
else if (opt->type & RFC3397)
printf(" domain");
else if (opt->type & DOMAIN)
printf(" dname");
else if (opt->type & ASCII)
printf(" ascii");
else if (opt->type & RAW)
printf(" raw");
else if (opt->type & BINHEX)
printf(" binhex");
else if (opt->type & STRING)
printf(" string");
if (opt->type & RFC3361)
printf(" rfc3361");
if (opt->type & RFC3442)
printf(" rfc3442");
if (opt->type & RFC5969)
printf(" rfc5969");
if (opt->type & REQUEST)
printf(" request");
if (opt->type & NOREQ)
printf(" norequest");
putchar('\n');
}
struct dhcp_opt *
vivso_find(uint32_t iana_en, const void *arg)
{
const struct interface *ifp;
size_t i;
struct dhcp_opt *opt;
ifp = arg;
for (i = 0, opt = ifp->options->vivso_override;
i < ifp->options->vivso_override_len;
i++, opt++)
if (opt->option == iana_en)
return opt;
for (i = 0, opt = ifp->ctx->vivso;
i < ifp->ctx->vivso_len;
i++, opt++)
if (opt->option == iana_en)
return opt;
return NULL;
}
ssize_t
dhcp_vendor(char *str, size_t len)
{
struct utsname utn;
char *p;
int l;
if (uname(&utn) != 0)
return (ssize_t)snprintf(str, len, "%s-%s",
PACKAGE, VERSION);
p = str;
l = snprintf(p, len,
"%s-%s:%s-%s:%s", PACKAGE, VERSION,
utn.sysname, utn.release, utn.machine);
if (l == -1 || (size_t)(l + 1) > len)
return -1;
p += l;
len -= (size_t)l;
l = if_machinearch(p, len);
if (l == -1 || (size_t)(l + 1) > len)
return -1;
p += l;
return p - str;
}
int
make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len,
const struct dhcp_opt *odopts, size_t odopts_len,
uint8_t *mask, const char *opts, int add)
{
char *token, *o, *p;
const struct dhcp_opt *opt;
int match, e;
unsigned int n;
size_t i;
if (opts == NULL)
return -1;
o = p = strdup(opts);
while ((token = strsep(&p, ", "))) {
if (*token == '\0')
continue;
match = 0;
for (i = 0, opt = odopts; i < odopts_len; i++, opt++) {
if (strcmp(opt->var, token) == 0)
match = 1;
else {
n = (unsigned int)strtou(token, NULL, 0,
0, UINT_MAX, &e);
if (e == 0 && opt->option == n)
match = 1;
}
if (match)
break;
}
if (match == 0) {
for (i = 0, opt = dopts; i < dopts_len; i++, opt++) {
if (strcmp(opt->var, token) == 0)
match = 1;
else {
n = (unsigned int)strtou(token, NULL, 0,
0, UINT_MAX, &e);
if (e == 0 && opt->option == n)
match = 1;
}
if (match)
break;
}
}
if (!match || !opt->option) {
free(o);
errno = ENOENT;
return -1;
}
if (add == 2 && !(opt->type & ADDRIPV4)) {
free(o);
errno = EINVAL;
return -1;
}
if (add == 1 || add == 2)
add_option_mask(mask, opt->option);
else
del_option_mask(mask, opt->option);
}
free(o);
return 0;
}
size_t
encode_rfc1035(const char *src, uint8_t *dst)
{
uint8_t *p;
uint8_t *lp;
size_t len;
uint8_t has_dot;
if (src == NULL || *src == '\0')
return 0;
if (dst) {
p = dst;
lp = p++;
}
/* Silence bogus GCC warnings */
else
p = lp = NULL;
len = 1;
has_dot = 0;
for (; *src; src++) {
if (*src == '\0')
break;
if (*src == '.') {
/* Skip the trailing . */
if (src[1] == '\0')
break;
has_dot = 1;
if (dst) {
*lp = (uint8_t)(p - lp - 1);
if (*lp == '\0')
return len;
lp = p++;
}
} else if (dst)
*p++ = (uint8_t)*src;
len++;
}
if (dst) {
*lp = (uint8_t)(p - lp - 1);
if (has_dot)
*p++ = '\0';
}
if (has_dot)
len++;
return len;
}
/* Decode an RFC3397 DNS search order option into a space
* separated string. Returns length of string (including
* terminating zero) or zero on error. out may be NULL
* to just determine output length. */
ssize_t
decode_rfc3397(char *out, size_t len, const uint8_t *p, size_t pl)
{
const char *start;
size_t start_len, l, count;
const uint8_t *r, *q = p, *e;
int hops;
uint8_t ltype;
count = 0;
start = out;
start_len = len;
q = p;
e = p + pl;
while (q < e) {
r = NULL;
hops = 0;
/* Check we are inside our length again in-case
* the name isn't fully qualified (ie, not terminated) */
while (q < e && (l = (size_t)*q++)) {
ltype = l & 0xc0;
if (ltype == 0x80 || ltype == 0x40)
return -1;
else if (ltype == 0xc0) { /* pointer */
if (q == e) {
errno = ERANGE;
return -1;
}
l = (l & 0x3f) << 8;
l |= *q++;
/* save source of first jump. */
if (!r)
r = q;
hops++;
if (hops > 255) {
errno = ERANGE;
return -1;
}
q = p + l;
if (q >= e) {
errno = ERANGE;
return -1;
}
} else {
/* straightforward name segment, add with '.' */
if (q + l > e) {
errno = ERANGE;
return -1;
}
count += l + 1;
if (out) {
if (l + 1 > len) {
errno = ENOBUFS;
return -1;
}
memcpy(out, q, l);
out += l;
*out++ = '.';
len -= l;
len--;
}
q += l;
}
}
/* change last dot to space */
if (out && out != start)
*(out - 1) = ' ';
if (r)
q = r;
}
/* change last space to zero terminator */
if (out) {
if (out != start)
*(out - 1) = '\0';
else if (start_len > 0)
*out = '\0';
}
if (count)
/* Don't count the trailing NUL */
count--;
return (ssize_t)count;
}
/* Check for a valid domain name as per RFC1123 with the exception of
* allowing - and _ (but not at start or end) as they seem to be widely used. */
static int
valid_domainname(char *lbl, int type)
{
char *slbl, *lst;
unsigned char c;
int start, len, errset;
if (lbl == NULL || *lbl == '\0') {
errno = EINVAL;
return 0;
}
slbl = lbl;
lst = NULL;
start = 1;
len = errset = 0;
for (;;) {
c = (unsigned char)*lbl++;
if (c == '\0')
return 1;
if (c == ' ') {
if (lbl - 1 == slbl) /* No space at start */
break;
if (!(type & ARRAY))
break;
/* Skip to the next label */
if (!start) {
start = 1;
lst = lbl - 1;
}
if (len)
len = 0;
continue;
}
if (c == '.') {
if (*lbl == '.')
break;
len = 0;
continue;
}
if (((c == '-' || c == '_') &&
!start && *lbl != ' ' && *lbl != '\0') ||
isalnum(c))
{
if (++len > 63) {
errno = ERANGE;
errset = 1;
break;
}
} else
break;
if (start)
start = 0;
}
if (!errset)
errno = EINVAL;
if (lst) {
/* At least one valid domain, return it */
*lst = '\0';
return 1;
}
return 0;
}
/*
* Prints a chunk of data to a string.
* PS_SHELL goes as it is these days, it's upto the target to validate it.
* PS_SAFE has all non ascii and non printables changes to escaped octal.
*/
static const char hexchrs[] = "0123456789abcdef";
ssize_t
print_string(char *dst, size_t len, int type, const uint8_t *data, size_t dl)
{
char *odst;
uint8_t c;
const uint8_t *e;
size_t bytes;
odst = dst;
bytes = 0;
e = data + dl;
while (data < e) {
c = *data++;
if (type & BINHEX) {
if (dst) {
if (len == 0 || len == 1) {
errno = ENOSPC;
return -1;
}
*dst++ = hexchrs[(c & 0xF0) >> 4];
*dst++ = hexchrs[(c & 0x0F)];
len -= 2;
}
bytes += 2;
continue;
}
if (type & ASCII && (!isascii(c))) {
errno = EINVAL;
break;
}
if (!(type & (ASCII | RAW | ESCSTRING | ESCFILE)) /* plain */ &&
(!isascii(c) && !isprint(c)))
{
errno = EINVAL;
break;
}
if ((type & (ESCSTRING | ESCFILE) &&
(c == '\\' || !isascii(c) || !isprint(c))) ||
(type & ESCFILE && (c == '/' || c == ' ')))
{
errno = EINVAL;
if (c == '\\') {
if (dst) {
if (len == 0 || len == 1) {
errno = ENOSPC;
return -1;
}
*dst++ = '\\'; *dst++ = '\\';
len -= 2;
}
bytes += 2;
continue;
}
if (dst) {
if (len < 5) {
errno = ENOSPC;
return -1;
}
*dst++ = '\\';
*dst++ = (char)(((c >> 6) & 03) + '0');
*dst++ = (char)(((c >> 3) & 07) + '0');
*dst++ = (char)(( c & 07) + '0');
len -= 4;
}
bytes += 4;
} else {
if (dst) {
if (len == 0) {
errno = ENOSPC;
return -1;
}
*dst++ = (char)c;
len--;
}
bytes++;
}
}
/* NULL */
if (dst) {
if (len == 0) {
errno = ENOSPC;
return -1;
}
*dst = '\0';
/* Now we've printed it, validate the domain */
if (type & DOMAIN && !valid_domainname(odst, type)) {
*odst = '\0';
return 1;
}
}
return (ssize_t)bytes;
}
#define ADDR6SZ 16
static size_t
dhcp_optlen(const struct dhcp_opt *opt, size_t dl)
{
size_t sz;
if (opt->type & ADDRIPV6)
sz = ADDR6SZ;
else if (opt->type & (UINT32 | ADDRIPV4))
sz = sizeof(uint32_t);
else if (opt->type & UINT16)
sz = sizeof(uint16_t);
else if (opt->type & (UINT8 | BITFLAG))
sz = sizeof(uint8_t);
else if (opt->type & FLAG)
return 0;
else {
/* All other types are variable length */
if (opt->len) {
if ((size_t)opt->len > dl) {
errno = ENODATA;
return -1;
}
return (ssize_t)opt->len;
}
return (ssize_t)dl;
}
if (dl < sz) {
errno = ENODATA;
return -1;
}
/* Trim any extra data.
* Maybe we need a settng to reject DHCP options with extra data? */
if (opt->type & ARRAY)
return (ssize_t)(dl - (dl % sz));
return (ssize_t)sz;
}
#ifdef INET6
#define PO_IFNAME
#else
#define PO_IFNAME __unused
#endif
ssize_t
print_option(char *s, size_t len, int type, const uint8_t *data, size_t dl,
PO_IFNAME const char *ifname)
{
const uint8_t *e, *t;
uint16_t u16;
int16_t s16;
uint32_t u32;
int32_t s32;
struct in_addr addr;
ssize_t bytes = 0, sl;
size_t l;
char *tmp;
if (type & RFC3397) {
sl = decode_rfc3397(NULL, 0, data, dl);
if (sl == 0 || sl == -1)
return sl;
l = (size_t)sl + 1;
tmp = malloc(l);
if (tmp == NULL)
return -1;
decode_rfc3397(tmp, l, data, dl);
sl = print_string(s, len, type, (uint8_t *)tmp, l - 1);
free(tmp);
return sl;
}
#ifdef INET
if (type & RFC3361) {
if ((tmp = decode_rfc3361(data, dl)) == NULL)
return -1;
l = strlen(tmp);
sl = print_string(s, len, type, (uint8_t *)tmp, l);
free(tmp);
return sl;
}
if (type & RFC3442)
return decode_rfc3442(s, len, data, dl);
if (type & RFC5969)
return decode_rfc5969(s, len, data, dl);
#endif
if (type & STRING)
return print_string(s, len, type, data, dl);
if (type & FLAG) {
if (s) {
*s++ = '1';
*s = '\0';
}
return 1;
}
if (!s) {
if (type & UINT8)
l = 3;
else if (type & UINT16) {
l = 5;
dl /= 2;
} else if (type & SINT16) {
l = 6;
dl /= 2;
} else if (type & UINT32) {
l = 10;
dl /= 4;
} else if (type & SINT32) {
l = 11;
dl /= 4;
} else if (type & ADDRIPV4) {
l = 16;
dl /= 4;
}
#ifdef INET6
else if (type & ADDRIPV6) {
e = data + dl;
l = 0;
while (data < e) {
if (l)
l++; /* space */
sl = ipv6_printaddr(NULL, 0, data, ifname);
if (sl != -1)
l += (size_t)sl;
data += 16;
}
return (ssize_t)l;
}
#endif
else {
errno = EINVAL;
return -1;
}
return (ssize_t)(l * dl);
}
t = data;
e = data + dl;
while (data < e) {
if (data != t) {
*s++ = ' ';
bytes++;
len--;
}
if (type & UINT8) {
sl = snprintf(s, len, "%u", *data);
data++;
} else if (type & UINT16) {
memcpy(&u16, data, sizeof(u16));
u16 = ntohs(u16);
sl = snprintf(s, len, "%u", u16);
data += sizeof(u16);
} else if (type & SINT16) {
memcpy(&u16, data, sizeof(u16));
s16 = (int16_t)ntohs(u16);
sl = snprintf(s, len, "%d", s16);
data += sizeof(u16);
} else if (type & UINT32) {
memcpy(&u32, data, sizeof(u32));
u32 = ntohl(u32);
sl = snprintf(s, len, "%u", u32);
data += sizeof(u32);
} else if (type & SINT32) {
memcpy(&u32, data, sizeof(u32));
s32 = (int32_t)ntohl(u32);
sl = snprintf(s, len, "%d", s32);
data += sizeof(u32);
} else if (type & ADDRIPV4) {
memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
sl = snprintf(s, len, "%s", inet_ntoa(addr));
data += sizeof(addr.s_addr);
}
#ifdef INET6
else if (type & ADDRIPV6) {
ssize_t r;
r = ipv6_printaddr(s, len, data, ifname);
if (r != -1)
sl = r;
else
sl = 0;
data += 16;
}
#endif
else
sl = 0;
if (len <= sl) {
bytes += len;
break;
}
len -= (size_t)sl;
bytes += sl;
s += sl;
}
return bytes;
}
/* Lease file name is formatted according to the expectation of the ChromiumOS's
* connection manager (shill). */
int
dhcp_set_leasefile(char *leasefile, size_t len, int family,
const struct interface *ifp, const char *extra)
{
char ssid[len];
if (ifp->name[0] == '\0') {
strlcpy(leasefile, ifp->ctx->pidfile, len);
return 0;
}
if (strlen(ifp->lease_identifier) > 0) {
return snprintf(leasefile, len,
family == AF_INET ? LEASEFILE : LEASEFILE6,
ifp->lease_identifier, "", extra);
}
return snprintf(leasefile, len,
family == AF_INET ? LEASEFILE : LEASEFILE6,
ifp->name, "", extra);
}
static size_t
dhcp_envoption1(struct dhcpcd_ctx *ctx, char **env, const char *prefix,
const struct dhcp_opt *opt, int vname, const uint8_t *od, size_t ol,
const char *ifname)
{
ssize_t len;
size_t e;
char *v, *val;
/* Ensure a valid length */
ol = (size_t)dhcp_optlen(opt, ol);
if ((ssize_t)ol == -1)
return 0;
len = print_option(NULL, 0, opt->type, od, ol, ifname);
if (len < 0)
return 0;
if (vname)
e = strlen(opt->var) + 1;
else
e = 0;
if (prefix)
e += strlen(prefix);
e += (size_t)len + 2;
if (env == NULL)
return e;
v = val = *env = malloc(e);
if (v == NULL) {
logger(ctx, LOG_ERR, "%s: %m", __func__);
return 0;
}
if (vname)
v += snprintf(val, e, "%s_%s=", prefix, opt->var);
else
v += snprintf(val, e, "%s=", prefix);
if (len != 0)
print_option(v, (size_t)len + 1, opt->type, od, ol, ifname);
return e;
}
size_t
dhcp_envoption(struct dhcpcd_ctx *ctx, char **env, const char *prefix,
const char *ifname, struct dhcp_opt *opt,
const uint8_t *(*dgetopt)(struct dhcpcd_ctx *,
size_t *, unsigned int *, size_t *,
const uint8_t *, size_t, struct dhcp_opt **),
const uint8_t *od, size_t ol)
{
size_t e, i, n, eos, eol;
unsigned int eoc;
const uint8_t *eod;
int ov;
struct dhcp_opt *eopt, *oopt;
char *pfx;
/* If no embedded or encapsulated options, it's easy */
if (opt->embopts_len == 0 && opt->encopts_len == 0) {
if (dhcp_envoption1(ctx, env == NULL ? NULL : &env[0],
prefix, opt, 1, od, ol, ifname))
return 1;
return 0;
}
/* Create a new prefix based on the option */
if (env) {
if (opt->type & INDEX) {
if (opt->index > 999) {
errno = ENOBUFS;
logger(ctx, LOG_ERR, "%s: %m", __func__);
return 0;
}
}
e = strlen(prefix) + strlen(opt->var) + 2 +
(opt->type & INDEX ? 3 : 0);
pfx = malloc(e);
if (pfx == NULL) {
logger(ctx, LOG_ERR, "%s: %m", __func__);
return 0;
}
if (opt->type & INDEX)
snprintf(pfx, e, "%s_%s%d", prefix,
opt->var, ++opt->index);
else
snprintf(pfx, e, "%s_%s", prefix, opt->var);
} else
pfx = NULL;
/* Embedded options are always processed first as that
* is a fixed layout */
n = 0;
for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) {
e = dhcp_optlen(eopt, ol);
if (e == 0) {
/* An option was expected, but there is not enough
* data for it.
* This may not be an error as some options like
* DHCP FQDN in RFC4702 have a string as the last
* option which is optional.
* FIXME: Add an flag to the options to indicate
* wether this is allowable or not. */
if (ol != 0 || i + 1 < opt->embopts_len)
logger(ctx, LOG_WARNING,
"%s: %s: malformed option %d",
ifname, __func__, opt->option);
goto out;
}
/* Use the option prefix if the embedded option
* name is different.
* This avoids new_fqdn_fqdn which would be silly. */
ov = strcmp(opt->var, eopt->var);
if (dhcp_envoption1(ctx, env == NULL ? NULL : &env[n],
pfx, eopt, ov, od, e, ifname))
n++;
od += e;
ol -= e;
}
/* Enumerate our encapsulated options */
if (opt->encopts_len && ol > 0) {
/* Zero any option indexes
* We assume that referenced encapsulated options are NEVER
* recursive as the index order could break. */
for (i = 0, eopt = opt->encopts;
i < opt->encopts_len;
i++, eopt++)
{
eoc = opt->option;
if (eopt->type & OPTION) {
dgetopt(ctx, NULL, &eoc, NULL, NULL, 0, &oopt);
if (oopt)
oopt->index = 0;
}
}
while ((eod = dgetopt(ctx, &eos, &eoc, &eol, od, ol, &oopt))) {
for (i = 0, eopt = opt->encopts;
i < opt->encopts_len;
i++, eopt++)
{
if (eopt->option == eoc) {
if (eopt->type & OPTION) {
if (oopt == NULL)
/* Report error? */
continue;
}
n += dhcp_envoption(ctx,
env == NULL ? NULL : &env[n], pfx,
ifname,
eopt->type & OPTION ? oopt : eopt,
dgetopt, eod, eol);
break;
}
}
od += eos + eol;
ol -= eos + eol;
}
}
out:
if (env)
free(pfx);
/* Return number of options found */
return n;
}
void
dhcp_zero_index(struct dhcp_opt *opt)
{
size_t i;
struct dhcp_opt *o;
opt->index = 0;
for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++)
dhcp_zero_index(o);
for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++)
dhcp_zero_index(o);
}