Changeset View
Changeset View
Standalone View
Standalone View
contrib/dhcpcd/src/dhcp-common.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/* SPDX-License-Identifier: BSD-2-Clause */ | |||||
/* | |||||
* dhcpcd - DHCP client daemon | |||||
* Copyright (c) 2006-2019 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/stat.h> | |||||
#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, 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; | |||||
if (strncmp(token, "dhcp6_", 6) == 0) | |||||
token += 6; | |||||
if (strncmp(token, "nd6_", 4) == 0) | |||||
token += 4; | |||||
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 settng 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); | |||||
} | |||||
size_t | |||||
dhcp_read_lease_fd(int fd, void **lease) | |||||
{ | |||||
struct stat st; | |||||
size_t sz; | |||||
void *buf; | |||||
ssize_t len; | |||||
if (fstat(fd, &st) != 0) | |||||
goto out; | |||||
if (!S_ISREG(st.st_mode)) { | |||||
errno = EINVAL; | |||||
goto out; | |||||
} | |||||
if (st.st_size > UINT32_MAX) { | |||||
errno = E2BIG; | |||||
goto out; | |||||
} | |||||
sz = (size_t)st.st_size; | |||||
if (sz == 0) | |||||
goto out; | |||||
if ((buf = malloc(sz)) == NULL) | |||||
goto out; | |||||
if ((len = read(fd, buf, sz)) == -1) { | |||||
free(buf); | |||||
goto out; | |||||
} | |||||
*lease = buf; | |||||
return (size_t)len; | |||||
out: | |||||
*lease = NULL; | |||||
return 0; | |||||
} |