diff options
Diffstat (limited to 'dhcp-common.c')
-rw-r--r-- | dhcp-common.c | 927 |
1 files changed, 927 insertions, 0 deletions
diff --git a/dhcp-common.c b/dhcp-common.c new file mode 100644 index 0000000..2936b55 --- /dev/null +++ b/dhcp-common.c @@ -0,0 +1,927 @@ +/* + * 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 ADDRSZ 4 +#define ADDR6SZ 16 +static size_t +dhcp_optlen(const struct dhcp_opt *opt, size_t dl) +{ + size_t sz; + + if (dl == 0) + return 0; + + if (opt->type == 0 || + opt->type & (STRING | BINHEX | RFC3442 | RFC5969)) + { + if (opt->len) { + if ((size_t)opt->len > dl) + return 0; + return (size_t)opt->len; + } + return dl; + } + + if ((opt->type & (ADDRIPV4 | ARRAY)) == (ADDRIPV4 | ARRAY)) { + if (dl < ADDRSZ) + return 0; + return dl - (dl % ADDRSZ); + } + + if ((opt->type & (ADDRIPV6 | ARRAY)) == (ADDRIPV6 | ARRAY)) { + if (dl < ADDR6SZ) + return 0; + return dl - (dl % ADDR6SZ); + } + + if (opt->type & (UINT32 | ADDRIPV4)) + sz = sizeof(uint32_t); + else if (opt->type & UINT16) + sz = sizeof(uint16_t); + else if (opt->type & UINT8) + sz = sizeof(uint8_t); + else if (opt->type & ADDRIPV6) + sz = ADDR6SZ; + else + /* If we don't know the size, assume it's valid */ + return dl; + return (dl < sz ? 0 : 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; + len -= (size_t)sl; + bytes += sl; + s += sl; + } + + return bytes; +} + +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; + } + + switch (family) { + case AF_INET: + case AF_INET6: + break; + default: + errno = EINVAL; + return -1; + } + + if (ifp->wireless) { + ssid[0] = '-'; + print_string(ssid + 1, sizeof(ssid) - 1, + ESCFILE, + (const uint8_t *)ifp->ssid, ifp->ssid_len); + } else + ssid[0] = '\0'; + return snprintf(leasefile, len, + family == AF_INET ? LEASEFILE : LEASEFILE6, + ifp->name, ssid, 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; + + if (opt->len && opt->len < ol) + ol = opt->len; + 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) + /* Report error? */ + return 0; + /* 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; + } + } + + 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); +} |