/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/dhcp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h> /* memcpy */
#include <fcntl.h>
#include <limits.h>
#include "dhcp_hostconf.h"
static void relativize_time(DHCP_OPT *, time_t, time_t);
static void relativize_v6(uint32_t *, time_t, time_t);
/*
* ifname_to_hostconf(): converts an interface name into a hostconf file for
* that interface
*
* input: const char *: the interface name
* boolean_t: B_TRUE if using DHCPv6
* output: char *: the hostconf filename
* note: uses an internal static buffer (not threadsafe)
*/
char *
ifname_to_hostconf(const char *ifname, boolean_t isv6)
{
static char filename[sizeof (DHCP_HOSTCONF_TMPL6) + LIFNAMSIZ];
(void) snprintf(filename, sizeof (filename), "%s%s%s",
DHCP_HOSTCONF_PREFIX, ifname,
isv6 ? DHCP_HOSTCONF_SUFFIX6 : DHCP_HOSTCONF_SUFFIX);
return (filename);
}
/* Same but using the old prefix. */
static char *
ifname_to_oldhostconf(const char *ifname, boolean_t isv6)
{
static char filename[sizeof (DHCP_HOSTCONF_TMPL6) + LIFNAMSIZ];
(void) snprintf(filename, sizeof (filename), "%s%s%s",
DHCP_HOSTCONF_OLD_PREFIX, ifname,
isv6 ? DHCP_HOSTCONF_SUFFIX6 : DHCP_HOSTCONF_SUFFIX);
return (filename);
}
/*
* remove_hostconf(): removes an interface.dhc file
*
* input: const char *: the interface name
* boolean_t: B_TRUE if using DHCPv6
* output: int: 0 if the file is removed, -1 if it can't be removed
* (errno is set)
*/
int
remove_hostconf(const char *ifname, boolean_t isv6)
{
(void) unlink(ifname_to_oldhostconf(ifname, isv6));
return (unlink(ifname_to_hostconf(ifname, isv6)));
}
/*
* read_hostconf(): reads the contents of an <if>.dhc file into a PKT_LIST
*
* input: const char *: the interface name
* PKT_LIST **: a pointer to a PKT_LIST * to store the info in
* uint_t: the length of the list of PKT_LISTs
* boolean_t: B_TRUE if using DHCPv6
* output: int: >0 if the file is read and loaded into the PKT_LIST *
* successfully, -1 otherwise (errno is set)
* note: the PKT and PKT_LISTs are dynamically allocated here
*/
int
read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen,
boolean_t isv6)
{
PKT_LIST *plp = NULL;
PKT *pkt = NULL;
int fd;
time_t orig_time, current_time = time(NULL);
uint32_t lease;
uint32_t magic;
int pcnt = 0;
int retval;
fd = open(ifname_to_hostconf(ifname, isv6), O_RDONLY);
if (fd == -1) {
fd = open(ifname_to_oldhostconf(ifname, isv6), O_RDONLY);
if (fd == -1)
return (-1);
}
if (read(fd, &magic, sizeof (magic)) != sizeof (magic))
goto failure;
if (magic != (isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC))
goto failure;
if (read(fd, &orig_time, sizeof (orig_time)) != sizeof (orig_time))
goto failure;
/*
* read the packet back in from disk, and for v4, run it through
* dhcp_options_scan(). note that we use calloc() because
* dhcp_options_scan() relies on the structure being zeroed.
*/
for (pcnt = 0; pcnt < plplen; pcnt++) {
plp = NULL;
pkt = NULL;
if ((plp = calloc(1, sizeof (PKT_LIST))) == NULL)
goto failure;
retval = read(fd, &plp->len, sizeof (plp->len));
if (retval == 0 && pcnt != 0) {
/*
* Reached end of file on a boundary, but after
* we've read at least one packet, so we consider
* this successful, allowing us to use files from
* older versions of the agent happily.
*/
free(plp);
break;
} else if (retval != sizeof (plp->len))
goto failure;
if ((pkt = malloc(plp->len)) == NULL)
goto failure;
if (read(fd, pkt, plp->len) != plp->len)
goto failure;
plp->pkt = pkt;
plpp[pcnt] = plp;
if (!isv6 && dhcp_options_scan(plp, B_TRUE) != 0)
goto failure;
/*
* First packet used to validate that we're interested,
* the rest are presumed to be historical reference and
* are not relativized
*/
if (pcnt == 0)
continue;
if (isv6) {
dhcpv6_option_t d6o;
dhcpv6_ia_na_t d6in;
dhcpv6_iaaddr_t d6ia;
uchar_t *opts, *optmax, *subomax;
/*
* Loop over contents of the packet to find the address
* options.
*/
opts = (uchar_t *)pkt + sizeof (dhcpv6_message_t);
optmax = (uchar_t *)pkt + plp->len;
while (opts + sizeof (d6o) <= optmax) {
/*
* Extract option header and make sure option
* is intact.
*/
(void) memcpy(&d6o, opts, sizeof (d6o));
d6o.d6o_code = ntohs(d6o.d6o_code);
d6o.d6o_len = ntohs(d6o.d6o_len);
subomax = opts + sizeof (d6o) + d6o.d6o_len;
if (subomax > optmax)
break;
/*
* If this isn't an option that contains
* address or prefix leases, then skip over it.
*/
if (d6o.d6o_code != DHCPV6_OPT_IA_NA &&
d6o.d6o_code != DHCPV6_OPT_IA_TA &&
d6o.d6o_code != DHCPV6_OPT_IA_PD) {
opts = subomax;
continue;
}
/*
* Handle the option first.
*/
if (d6o.d6o_code == DHCPV6_OPT_IA_TA) {
/* no timers in this structure */
opts += sizeof (dhcpv6_ia_ta_t);
} else {
/* both na and pd */
if (opts + sizeof (d6in) > subomax) {
opts = subomax;
continue;
}
(void) memcpy(&d6in, opts,
sizeof (d6in));
relativize_v6(&d6in.d6in_t1, orig_time,
current_time);
relativize_v6(&d6in.d6in_t2, orig_time,
current_time);
(void) memcpy(opts, &d6in,
sizeof (d6in));
opts += sizeof (d6in);
}
/*
* Now handle each suboption (address) inside.
*/
while (opts + sizeof (d6o) <= subomax) {
/*
* Verify the suboption header first.
*/
(void) memcpy(&d6o, opts,
sizeof (d6o));
d6o.d6o_code = ntohs(d6o.d6o_code);
d6o.d6o_len = ntohs(d6o.d6o_len);
if (opts + sizeof (d6o) + d6o.d6o_len >
subomax)
break;
if (d6o.d6o_code != DHCPV6_OPT_IAADDR) {
opts += sizeof (d6o) +
d6o.d6o_len;
continue;
}
/*
* Now process the contents.
*/
if (opts + sizeof (d6ia) > subomax)
break;
(void) memcpy(&d6ia, opts,
sizeof (d6ia));
relativize_v6(&d6ia.d6ia_preflife,
orig_time, current_time);
relativize_v6(&d6ia.d6ia_vallife,
orig_time, current_time);
(void) memcpy(opts, &d6ia,
sizeof (d6ia));
opts += sizeof (d6o) + d6o.d6o_len;
}
opts = subomax;
}
} else {
/*
* make sure the IPv4 DHCP lease is still valid.
*/
if (plp->opts[CD_LEASE_TIME] != NULL &&
plp->opts[CD_LEASE_TIME]->len ==
sizeof (lease_t)) {
(void) memcpy(&lease,
plp->opts[CD_LEASE_TIME]->value,
sizeof (lease_t));
lease = ntohl(lease);
if ((lease != DHCP_PERM) &&
(orig_time + lease) <= current_time)
goto failure;
}
relativize_time(plp->opts[CD_T1_TIME], orig_time,
current_time);
relativize_time(plp->opts[CD_T2_TIME], orig_time,
current_time);
relativize_time(plp->opts[CD_LEASE_TIME], orig_time,
current_time);
}
}
(void) close(fd);
return (pcnt);
failure:
free(pkt);
free(plp);
while (pcnt-- > 0) {
free(plpp[pcnt]->pkt);
free(plpp[pcnt]);
}
(void) close(fd);
return (-1);
}
/*
* write_hostconf(): writes the contents of a PKT_LIST into an <if>.dhc file
*
* input: const char *: the interface name
* PKT_LIST **: a list of pointers to PKT_LIST to write
* uint_t: length of the list of PKT_LIST pointers
* time_t: a starting time to treat the relative lease times
* in the first packet as relative to
* boolean_t: B_TRUE if using DHCPv6
* output: int: 0 if the file is written successfully, -1 otherwise
* (errno is set)
*/
int
write_hostconf(
const char *ifname,
PKT_LIST *pl[],
uint_t pllen,
time_t relative_to,
boolean_t isv6)
{
int fd;
struct iovec iov[IOV_MAX];
int retval;
uint32_t magic;
ssize_t explen = 0; /* Expected length of write */
int i, iovlen = 0;
fd = open(ifname_to_hostconf(ifname, isv6), O_WRONLY|O_CREAT|O_TRUNC,
0600);
if (fd == -1)
return (-1);
(void) unlink(ifname_to_oldhostconf(ifname, isv6));
/*
* first write our magic number, then the relative time of the
* leases, then for each packet we write the length of the packet
* followed by the packet. we will then use the relative time in
* read_hostconf() to recalculate the lease times for the first packet.
*/
magic = isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC;
iov[iovlen].iov_base = (caddr_t)&magic;
explen += iov[iovlen++].iov_len = sizeof (magic);
iov[iovlen].iov_base = (caddr_t)&relative_to;
explen += iov[iovlen++].iov_len = sizeof (relative_to);
for (i = 0; i < pllen && iovlen < (IOV_MAX - 1); i++) {
iov[iovlen].iov_base = (caddr_t)&pl[i]->len;
explen += iov[iovlen++].iov_len = sizeof (pl[i]->len);
iov[iovlen].iov_base = (caddr_t)pl[i]->pkt;
explen += iov[iovlen++].iov_len = pl[i]->len;
}
retval = writev(fd, iov, iovlen);
(void) close(fd);
if (retval != explen)
return (-1);
return (0);
}
/*
* relativize_time(): re-relativizes a time in a DHCP option
*
* input: DHCP_OPT *: the DHCP option parameter to convert
* time_t: the time the leases in the packet are currently relative to
* time_t: the current time which leases will become relative to
* output: void
*/
static void
relativize_time(DHCP_OPT *option, time_t orig_time, time_t current_time)
{
uint32_t pkt_time;
time_t time_diff = current_time - orig_time;
if (option == NULL || option->len != sizeof (lease_t))
return;
(void) memcpy(&pkt_time, option->value, option->len);
if (ntohl(pkt_time) != DHCP_PERM)
pkt_time = htonl(ntohl(pkt_time) - time_diff);
(void) memcpy(option->value, &pkt_time, option->len);
}
/*
* relativize_v6(): re-relativizes a time in a DHCPv6 option
*
* input: uint32_t *: the time value to convert
* time_t: the time the leases in the packet are currently relative to
* time_t: the current time which leases will become relative to
* output: void
*/
static void
relativize_v6(uint32_t *val, time_t orig_time, time_t current_time)
{
uint32_t hval;
time_t time_diff = current_time - orig_time;
hval = ntohl(*val);
if (hval != DHCPV6_INFTIME) {
if (hval < time_diff)
*val = 0;
else
*val = htonl(hval - time_diff);
}
}