aboutsummaryrefslogtreecommitdiff
path: root/src/dhcp-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp-common.c')
-rw-r--r--src/dhcp-common.c1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
new file mode 100644
index 000000000000..dbcfcc564df8
--- /dev/null
+++ b/src/dhcp-common.c
@@ -0,0 +1,1029 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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"
+#include "logerr.h"
+#include "script.h"
+
+const char *
+dhcp_get_hostname(char *buf, size_t buf_len, const struct if_options *ifo)
+{
+
+ if (ifo->hostname[0] == '\0') {
+ if (gethostname(buf, buf_len) != 0)
+ return NULL;
+ buf[buf_len - 1] = '\0';
+ } else
+ strlcpy(buf, ifo->hostname, buf_len);
+
+ /* Deny sending of these local hostnames */
+ if (buf[0] == '\0' || buf[0] == '.' ||
+ strcmp(buf, "(none)") == 0 ||
+ strcmp(buf, "localhost") == 0 ||
+ strncmp(buf, "localhost.", strlen("localhost.")) == 0)
+ return NULL;
+
+ /* Shorten the hostname if required */
+ if (ifo->options & DHCPCD_HOSTNAME_SHORT) {
+ char *hp;
+
+ hp = strchr(buf, '.');
+ if (hp != NULL)
+ *hp = '\0';
+ }
+
+ return buf;
+}
+
+void
+dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols)
+{
+
+ while (cols < 40) {
+ putchar(' ');
+ cols++;
+ }
+ putchar('\t');
+ if (opt->type & OT_EMBED)
+ printf(" embed");
+ if (opt->type & OT_ENCAP)
+ printf(" encap");
+ if (opt->type & OT_INDEX)
+ printf(" index");
+ if (opt->type & OT_ARRAY)
+ printf(" array");
+ if (opt->type & OT_UINT8)
+ printf(" uint8");
+ else if (opt->type & OT_INT8)
+ printf(" int8");
+ else if (opt->type & OT_UINT16)
+ printf(" uint16");
+ else if (opt->type & OT_INT16)
+ printf(" int16");
+ else if (opt->type & OT_UINT32)
+ printf(" uint32");
+ else if (opt->type & OT_INT32)
+ printf(" int32");
+ else if (opt->type & OT_ADDRIPV4)
+ printf(" ipaddress");
+ else if (opt->type & OT_ADDRIPV6)
+ printf(" ip6address");
+ else if (opt->type & OT_FLAG)
+ printf(" flag");
+ else if (opt->type & OT_BITFLAG)
+ printf(" bitflags");
+ else if (opt->type & OT_RFC1035)
+ printf(" domain");
+ else if (opt->type & OT_DOMAIN)
+ printf(" dname");
+ else if (opt->type & OT_ASCII)
+ printf(" ascii");
+ else if (opt->type & OT_RAW)
+ printf(" raw");
+ else if (opt->type & OT_BINHEX)
+ printf(" binhex");
+ else if (opt->type & OT_STRING)
+ printf(" string");
+ if (opt->type & OT_RFC3361)
+ printf(" rfc3361");
+ if (opt->type & OT_RFC3442)
+ printf(" rfc3442");
+ if (opt->type & OT_REQUEST)
+ printf(" request");
+ if (opt->type & OT_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) == -1)
+ 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 + 1, len - 1);
+ if (l == -1 || (size_t)(l + 1) > len)
+ return -1;
+ *p = ':';
+ 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;
+ if (strncmp(token, "dhcp6_", 6) == 0)
+ token += 6;
+ if (strncmp(token, "nd_", 3) == 0)
+ token += 3;
+ match = 0;
+ for (i = 0, opt = odopts; i < odopts_len; i++, opt++) {
+ if (opt->var == NULL || opt->option == 0)
+ continue; /* buggy dhcpcd-definitions.conf */
+ 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 & OT_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 RFC1035 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_rfc1035(char *out, size_t len, const uint8_t *p, size_t pl)
+{
+ const char *start;
+ size_t start_len, l, d_len, o_len;
+ const uint8_t *r, *q = p, *e;
+ int hops;
+ uint8_t ltype;
+
+ o_len = 0;
+ start = out;
+ start_len = len;
+ q = p;
+ e = p + pl;
+ while (q < e) {
+ r = NULL;
+ d_len = 0;
+ 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) {
+ /* Currently reserved for future use as noted
+ * in RFC1035 4.1.4 as the 10 and 01
+ * combinations. */
+ errno = ENOTSUP;
+ 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;
+ }
+ if (l > NS_MAXLABEL) {
+ errno = EINVAL;
+ return -1;
+ }
+ d_len += l + 1;
+ if (out) {
+ if (l + 1 > len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(out, q, l);
+ out += l;
+ *out++ = '.';
+ len -= l;
+ len--;
+ }
+ q += l;
+ }
+ }
+
+ /* Don't count the trailing NUL */
+ if (d_len > NS_MAXDNAME + 1) {
+ errno = E2BIG;
+ return -1;
+ }
+ o_len += d_len;
+
+ /* 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';
+ }
+
+ /* Remove the trailing NUL */
+ if (o_len != 0)
+ o_len--;
+
+ return (ssize_t)o_len;
+}
+
+/* Check for a valid name as per RFC952 and RFC1123 section 2.1 */
+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 & OT_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 > NS_MAXLABEL) {
+ 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 & OT_BINHEX) {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst++ = hexchrs[(c & 0xF0) >> 4];
+ *dst++ = hexchrs[(c & 0x0F)];
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (type & OT_ASCII && (!isascii(c))) {
+ errno = EINVAL;
+ break;
+ }
+ if (!(type & (OT_ASCII | OT_RAW | OT_ESCSTRING | OT_ESCFILE)) &&
+ (!isascii(c) && !isprint(c)))
+ {
+ errno = EINVAL;
+ break;
+ }
+ if ((type & (OT_ESCSTRING | OT_ESCFILE) &&
+ (c == '\\' || !isascii(c) || !isprint(c))) ||
+ (type & OT_ESCFILE && (c == '/' || c == ' ')))
+ {
+ errno = EINVAL;
+ if (c == '\\') {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst++ = '\\'; *dst++ = '\\';
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (dst) {
+ if (len < 5) {
+ errno = ENOBUFS;
+ 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 = ENOBUFS;
+ return -1;
+ }
+ *dst++ = (char)c;
+ len--;
+ }
+ bytes++;
+ }
+ }
+
+ /* NULL */
+ if (dst) {
+ if (len == 0) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst = '\0';
+
+ /* Now we've printed it, validate the domain */
+ if (type & OT_DOMAIN && !valid_domainname(odst, type)) {
+ *odst = '\0';
+ return 1;
+ }
+
+ }
+
+ return (ssize_t)bytes;
+}
+
+#define ADDR6SZ 16
+static ssize_t
+dhcp_optlen(const struct dhcp_opt *opt, size_t dl)
+{
+ size_t sz;
+
+ if (opt->type & OT_ADDRIPV6)
+ sz = ADDR6SZ;
+ else if (opt->type & (OT_INT32 | OT_UINT32 | OT_ADDRIPV4))
+ sz = sizeof(uint32_t);
+ else if (opt->type & (OT_INT16 | OT_UINT16))
+ sz = sizeof(uint16_t);
+ else if (opt->type & (OT_INT8 | OT_UINT8 | OT_BITFLAG))
+ sz = sizeof(uint8_t);
+ else if (opt->type & OT_FLAG)
+ return 0;
+ else {
+ /* All other types are variable length */
+ if (opt->len) {
+ if ((size_t)opt->len > dl) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ return (ssize_t)opt->len;
+ }
+ return (ssize_t)dl;
+ }
+ if (dl < sz) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ /* Trim any extra data.
+ * Maybe we need a setting to reject DHCP options with extra data? */
+ if (opt->type & OT_ARRAY)
+ return (ssize_t)(dl - (dl % sz));
+ return (ssize_t)sz;
+}
+
+static ssize_t
+print_option(FILE *fp, const char *prefix, const struct dhcp_opt *opt,
+ int vname,
+ const uint8_t *data, size_t dl, const char *ifname)
+{
+ fpos_t fp_pos;
+ const uint8_t *e, *t;
+ uint16_t u16;
+ int16_t s16;
+ uint32_t u32;
+ int32_t s32;
+ struct in_addr addr;
+ ssize_t sl;
+ size_t l;
+
+ /* Ensure a valid length */
+ dl = (size_t)dhcp_optlen(opt, dl);
+ if ((ssize_t)dl == -1)
+ return 0;
+
+ if (fgetpos(fp, &fp_pos) == -1)
+ return -1;
+ if (fprintf(fp, "%s", prefix) == -1)
+ goto err;
+
+ /* We printed something, so always goto err from now-on
+ * to terminate the string. */
+ if (vname) {
+ if (fprintf(fp, "_%s", opt->var) == -1)
+ goto err;
+ }
+ if (fputc('=', fp) == EOF)
+ goto err;
+ if (dl == 0)
+ goto done;
+
+ if (opt->type & OT_RFC1035) {
+ char domain[NS_MAXDNAME];
+
+ sl = decode_rfc1035(domain, sizeof(domain), data, dl);
+ if (sl == -1)
+ goto err;
+ if (sl == 0)
+ goto done;
+ if (valid_domainname(domain, opt->type) == -1)
+ goto err;
+ return efprintf(fp, "%s", domain);
+ }
+
+#ifdef INET
+ if (opt->type & OT_RFC3361)
+ return print_rfc3361(fp, data, dl);
+
+ if (opt->type & OT_RFC3442)
+ return print_rfc3442(fp, data, dl);
+#endif
+
+ if (opt->type & OT_STRING) {
+ char buf[1024];
+
+ if (print_string(buf, sizeof(buf), opt->type, data, dl) == -1)
+ goto err;
+ return efprintf(fp, "%s", buf);
+ }
+
+ if (opt->type & OT_FLAG)
+ return efprintf(fp, "1");
+
+ if (opt->type & OT_BITFLAG) {
+ /* bitflags are a string, MSB first, such as ABCDEFGH
+ * where A is 10000000, B is 01000000, etc. */
+ for (l = 0, sl = sizeof(opt->bitflags) - 1;
+ l < sizeof(opt->bitflags);
+ l++, sl--)
+ {
+ /* Don't print NULL or 0 flags */
+ if (opt->bitflags[l] != '\0' &&
+ opt->bitflags[l] != '0' &&
+ *data & (1 << sl))
+ {
+ if (fputc(opt->bitflags[l], fp) == EOF)
+ goto err;
+ }
+ }
+ goto done;
+ }
+
+ t = data;
+ e = data + dl;
+ while (data < e) {
+ if (data != t) {
+ if (fputc(' ', fp) == EOF)
+ goto err;
+ }
+ if (opt->type & OT_UINT8) {
+ if (fprintf(fp, "%u", *data) == -1)
+ goto err;
+ data++;
+ } else if (opt->type & OT_INT8) {
+ if (fprintf(fp, "%d", *data) == -1)
+ goto err;
+ data++;
+ } else if (opt->type & OT_UINT16) {
+ memcpy(&u16, data, sizeof(u16));
+ u16 = ntohs(u16);
+ if (fprintf(fp, "%u", u16) == -1)
+ goto err;
+ data += sizeof(u16);
+ } else if (opt->type & OT_INT16) {
+ memcpy(&u16, data, sizeof(u16));
+ s16 = (int16_t)ntohs(u16);
+ if (fprintf(fp, "%d", s16) == -1)
+ goto err;
+ data += sizeof(u16);
+ } else if (opt->type & OT_UINT32) {
+ memcpy(&u32, data, sizeof(u32));
+ u32 = ntohl(u32);
+ if (fprintf(fp, "%u", u32) == -1)
+ goto err;
+ data += sizeof(u32);
+ } else if (opt->type & OT_INT32) {
+ memcpy(&u32, data, sizeof(u32));
+ s32 = (int32_t)ntohl(u32);
+ if (fprintf(fp, "%d", s32) == -1)
+ goto err;
+ data += sizeof(u32);
+ } else if (opt->type & OT_ADDRIPV4) {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ if (fprintf(fp, "%s", inet_ntoa(addr)) == -1)
+ goto err;
+ data += sizeof(addr.s_addr);
+ } else if (opt->type & OT_ADDRIPV6) {
+ char buf[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET6, data, buf, sizeof(buf)) == NULL)
+ goto err;
+ if (fprintf(fp, "%s", buf) == -1)
+ goto err;
+ if (data[0] == 0xfe && (data[1] & 0xc0) == 0x80) {
+ if (fprintf(fp,"%%%s", ifname) == -1)
+ goto err;
+ }
+ data += 16;
+ } else {
+ errno = EINVAL;
+ goto err;
+ }
+ }
+
+done:
+ if (fputc('\0', fp) == EOF)
+ return -1;
+ return 1;
+
+err:
+ (void)fsetpos(fp, &fp_pos);
+ return -1;
+}
+
+int
+dhcp_set_leasefile(char *leasefile, size_t len, int family,
+ const struct interface *ifp)
+{
+ char ssid[1 + (IF_SSIDLEN * 4) + 1]; /* - prefix and NUL terminated. */
+
+ 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,
+ OT_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);
+}
+
+void
+dhcp_envoption(struct dhcpcd_ctx *ctx, FILE *fp, 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 i, eos, eol;
+ ssize_t eo;
+ 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 (opt->type & OT_RESERVED)
+ return;
+ if (print_option(fp, prefix, opt, 1, od, ol, ifname) == -1)
+ logerr("%s: %s %d", ifname, __func__, opt->option);
+ return;
+ }
+
+ /* Create a new prefix based on the option */
+ if (opt->type & OT_INDEX) {
+ if (asprintf(&pfx, "%s_%s%d",
+ prefix, opt->var, ++opt->index) == -1)
+ pfx = NULL;
+ } else {
+ if (asprintf(&pfx, "%s_%s", prefix, opt->var) == -1)
+ pfx = NULL;
+ }
+ if (pfx == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ /* Embedded options are always processed first as that
+ * is a fixed layout */
+ for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) {
+ eo = dhcp_optlen(eopt, ol);
+ if (eo == -1) {
+ logerrx("%s: %s %d.%d/%zu: "
+ "malformed embedded option",
+ ifname, __func__, opt->option,
+ eopt->option, i);
+ goto out;
+ }
+ if (eo == 0) {
+ /* An option was expected, but there is no data
+ * 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. */
+ if (ol != 0 || !(eopt->type & OT_OPTIONAL))
+ logerrx("%s: %s %d.%d/%zu: "
+ "missing embedded option",
+ ifname, __func__, opt->option,
+ eopt->option, i);
+ goto out;
+ }
+ /* Use the option prefix if the embedded option
+ * name is different.
+ * This avoids new_fqdn_fqdn which would be silly. */
+ if (!(eopt->type & OT_RESERVED)) {
+ ov = strcmp(opt->var, eopt->var);
+ if (print_option(fp, pfx, eopt, ov, od, (size_t)eo,
+ ifname) == -1)
+ logerr("%s: %s %d.%d/%zu",
+ ifname, __func__,
+ opt->option, eopt->option, i);
+ }
+ od += (size_t)eo;
+ ol -= (size_t)eo;
+ }
+
+ /* 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 & OT_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)
+ continue;
+ if (eopt->type & OT_OPTION) {
+ if (oopt == NULL)
+ /* Report error? */
+ continue;
+ }
+ dhcp_envoption(ctx, fp, pfx, ifname,
+ eopt->type & OT_OPTION ? oopt:eopt,
+ dgetopt, eod, eol);
+ }
+ od += eos + eol;
+ ol -= eos + eol;
+ }
+ }
+
+out:
+ free(pfx);
+}
+
+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);
+}
+
+ssize_t
+dhcp_readfile(struct dhcpcd_ctx *ctx, const char *file, void *data, size_t len)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_readfile(ctx, file, data, len);
+#else
+ UNUSED(ctx);
+#endif
+
+ return readfile(file, data, len);
+}
+
+ssize_t
+dhcp_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode,
+ const void *data, size_t len)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_writefile(ctx, file, mode, data, len);
+#else
+ UNUSED(ctx);
+#endif
+
+ return writefile(file, mode, data, len);
+}
+
+int
+dhcp_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return (int)ps_root_filemtime(ctx, file, time);
+#else
+ UNUSED(ctx);
+#endif
+
+ return filemtime(file, time);
+}
+
+int
+dhcp_unlink(struct dhcpcd_ctx *ctx, const char *file)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return (int)ps_root_unlink(ctx, file);
+#else
+ UNUSED(ctx);
+#endif
+
+ return unlink(file);
+}
+
+size_t
+dhcp_read_hwaddr_aton(struct dhcpcd_ctx *ctx, uint8_t **data, const char *file)
+{
+ char buf[BUFSIZ];
+ ssize_t bytes;
+ size_t len;
+
+ bytes = dhcp_readfile(ctx, file, buf, sizeof(buf));
+ if (bytes == -1 || bytes == sizeof(buf))
+ return 0;
+
+ bytes[buf] = '\0';
+ len = hwaddr_aton(NULL, buf);
+ if (len == 0)
+ return 0;
+ *data = malloc(len);
+ if (*data == NULL)
+ return 0;
+ hwaddr_aton(*data, buf);
+ return len;
+}