/* 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;
}