summaryrefslogtreecommitdiffstats
path: root/dhcpcd/dhcp-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpcd/dhcp-common.c')
-rw-r--r--dhcpcd/dhcp-common.c684
1 files changed, 684 insertions, 0 deletions
diff --git a/dhcpcd/dhcp-common.c b/dhcpcd/dhcp-common.c
new file mode 100644
index 00000000..c8db0738
--- /dev/null
+++ b/dhcpcd/dhcp-common.c
@@ -0,0 +1,684 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcp-common.h"
+#include "dhcp.h"
+
+/* DHCP Enterprise options, RFC3925 */
+struct dhcp_opt *vivso = NULL;
+size_t vivso_len = 0;
+
+struct dhcp_opt *
+vivso_find(uint16_t iana_en, const void *arg)
+{
+ const struct interface *ifp = arg;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (arg) {
+ 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 = vivso; i < vivso_len; i++, opt++)
+ if (opt->option == iana_en)
+ return opt;
+ return NULL;
+}
+
+int
+make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len,
+ uint8_t *mask, const char *opts, int add)
+{
+ char *token, *o, *p, *t;
+ const struct dhcp_opt *opt;
+ int match;
+ unsigned int n;
+ size_t i;
+
+ o = p = strdup(opts);
+ if (opts == NULL)
+ return -1;
+ while ((token = strsep(&p, ", "))) {
+ if (*token == '\0')
+ continue;
+ for (i = 0, opt = dopts; i < dopts_len; i++, opt++) {
+ match = 0;
+ if (strcmp(opt->var, token) == 0)
+ match = 1;
+ else {
+ errno = 0;
+ n = strtol(token, &t, 0);
+ if (errno == 0 && !*t)
+ if (opt->option == n)
+ match = 1;
+ }
+ if (match) {
+ 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);
+ break;
+ }
+ }
+ if (!opt->option) {
+ free(o);
+ errno = ENOENT;
+ return -1;
+ }
+ }
+ 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 = p - lp - 1;
+ if (*lp == '\0')
+ return len;
+ lp = p++;
+ }
+ } else if (dst)
+ *p++ = (uint8_t)*src;
+ len++;
+ }
+
+ if (dst) {
+ *lp = 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, ssize_t len, int pl, const uint8_t *p)
+{
+ const char *start;
+ ssize_t start_len;
+ const uint8_t *r, *q = p;
+ int count = 0, l, hops;
+ uint8_t ltype;
+
+ start = out;
+ start_len = len;
+ while (q - p < pl) {
+ r = NULL;
+ hops = 0;
+ /* Check we are inside our length again in-case
+ * the name isn't fully qualified (ie, not terminated) */
+ while (q - p < pl && (l = *q++)) {
+ ltype = l & 0xc0;
+ if (ltype == 0x80 || ltype == 0x40)
+ return 0;
+ else if (ltype == 0xc0) { /* pointer */
+ l = (l & 0x3f) << 8;
+ l |= *q++;
+ /* save source of first jump. */
+ if (!r)
+ r = q;
+ hops++;
+ if (hops > 255)
+ return 0;
+ q = p + l;
+ if (q - p >= pl)
+ return 0;
+ } else {
+ /* straightforward name segment, add with '.' */
+ count += l + 1;
+ if (out) {
+ if ((ssize_t)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';
+ }
+
+ return count;
+}
+
+ssize_t
+print_string(char *s, ssize_t len, int dl, const uint8_t *data)
+{
+ uint8_t c;
+ const uint8_t *e, *p;
+ ssize_t bytes = 0;
+ ssize_t r;
+
+ e = data + dl;
+ while (data < e) {
+ c = *data++;
+ if (c == '\0') {
+ /* If rest is all NULL, skip it. */
+ for (p = data; p < e; p++)
+ if (*p != '\0')
+ break;
+ if (p == e)
+ break;
+ }
+ if (!isascii(c) || !isprint(c)) {
+ if (s) {
+ if (len < 5) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ r = snprintf(s, len, "\\%03o", c);
+ len -= r;
+ bytes += r;
+ s += r;
+ } else
+ bytes += 4;
+ continue;
+ }
+ switch (c) {
+ case '"': /* FALLTHROUGH */
+ case '\'': /* FALLTHROUGH */
+ case '$': /* FALLTHROUGH */
+ case '`': /* FALLTHROUGH */
+ case '\\': /* FALLTHROUGH */
+ case '|': /* FALLTHROUGH */
+ case '&':
+ if (s) {
+ if (len < 3) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *s++ = '\\';
+ len--;
+ }
+ bytes++;
+ break;
+ }
+ if (s) {
+ *s++ = c;
+ len--;
+ }
+ bytes++;
+ }
+
+ /* NULL */
+ if (s)
+ *s = '\0';
+ bytes++;
+ return 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 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);
+ }
+
+ sz = 0;
+ 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, ssize_t len, int type, int dl, const uint8_t *data,
+ 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;
+ ssize_t l;
+ char *tmp;
+
+ if (type & RFC3397) {
+ l = decode_rfc3397(NULL, 0, dl, data);
+ if (l < 1)
+ return l;
+ tmp = malloc(l);
+ if (tmp == NULL)
+ return -1;
+ decode_rfc3397(tmp, l, dl, data);
+ l = print_string(s, len, l - 1, (uint8_t *)tmp);
+ free(tmp);
+ return l;
+ }
+
+#ifdef INET
+ if (type & RFC3361) {
+ if ((tmp = decode_rfc3361(dl, data)) == NULL)
+ return -1;
+ l = strlen(tmp);
+ l = print_string(s, len, l, (uint8_t *)tmp);
+ free(tmp);
+ return l;
+ }
+
+ if (type & RFC3442)
+ return decode_rfc3442(s, len, dl, data);
+
+ if (type & RFC5969)
+ return decode_rfc5969(s, len, dl, data);
+#endif
+
+ if (type & STRING) {
+ /* Some DHCP servers return NULL strings */
+ if (*data == '\0')
+ return 0;
+ return print_string(s, len, dl, data);
+ }
+
+ if (type & FLAG) {
+ if (s) {
+ *s++ = '1';
+ *s = '\0';
+ }
+ return 2;
+ }
+
+ 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 */
+ dl = ipv6_printaddr(NULL, 0, data, ifname);
+ if (dl != -1)
+ l += dl;
+ data += 16;
+ }
+ return l + 1;
+ }
+#endif
+ else if (type & BINHEX) {
+ l = 2;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+ return (l + 1) * dl;
+ }
+
+ t = data;
+ e = data + dl;
+ while (data < e) {
+ if (data != t && type != BINHEX) {
+ *s++ = ' ';
+ bytes++;
+ len--;
+ }
+ if (type & UINT8) {
+ l = snprintf(s, len, "%u", *data);
+ data++;
+ } else if (type & UINT16) {
+ memcpy(&u16, data, sizeof(u16));
+ u16 = ntohs(u16);
+ l = snprintf(s, len, "%u", u16);
+ data += sizeof(u16);
+ } else if (type & SINT16) {
+ memcpy(&s16, data, sizeof(s16));
+ s16 = ntohs(s16);
+ l = snprintf(s, len, "%d", s16);
+ data += sizeof(s16);
+ } else if (type & UINT32) {
+ memcpy(&u32, data, sizeof(u32));
+ u32 = ntohl(u32);
+ l = snprintf(s, len, "%u", u32);
+ data += sizeof(u32);
+ } else if (type & SINT32) {
+ memcpy(&s32, data, sizeof(s32));
+ s32 = ntohl(s32);
+ l = snprintf(s, len, "%d", s32);
+ data += sizeof(s32);
+ } else if (type & ADDRIPV4) {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ l = snprintf(s, len, "%s", inet_ntoa(addr));
+ data += sizeof(addr.s_addr);
+ }
+#ifdef INET6
+ else if (type & ADDRIPV6) {
+ dl = ipv6_printaddr(s, len, data, ifname);
+ if (dl != -1)
+ l = dl;
+ else
+ l = 0;
+ data += 16;
+ }
+#endif
+ else if (type & BINHEX) {
+ l = snprintf(s, len, "%.2x", data[0]);
+ data++;
+ } else
+ l = 0;
+ len -= l;
+ bytes += l;
+ s += l;
+ }
+
+ return bytes;
+}
+
+static size_t
+dhcp_envoption1(char **env, const char *prefix,
+ const struct dhcp_opt *opt, int vname, const uint8_t *od, int 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, ol, od, ifname);
+ if (len < 0)
+ return 0;
+ if (vname)
+ e = strlen(opt->var) + 1;
+ else
+ e = 0;
+ if (prefix)
+ e += strlen(prefix);
+ e += len + 4;
+ if (env == NULL)
+ return e;
+ v = val = *env = malloc(e);
+ if (v == NULL) {
+ syslog(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, len, opt->type, ol, od, ifname);
+ return e;
+}
+
+ssize_t
+dhcp_envoption(char **env, const char *prefix,
+ const char *ifname, struct dhcp_opt *opt,
+ const uint8_t *(*dgetopt)(unsigned int *, unsigned int *, unsigned int *,
+ const uint8_t *, unsigned int, struct dhcp_opt **),
+ const uint8_t *od, int ol)
+{
+ ssize_t e, n;
+ size_t i;
+ unsigned int eoc, eos, eol;
+ 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(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;
+ syslog(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) {
+ syslog(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(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(NULL, &eoc, NULL, NULL, 0, &oopt);
+ if (oopt)
+ oopt->index = 0;
+ }
+ }
+
+ while ((eod = dgetopt(&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(
+ 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);
+}