/*
* dhcpcd - DHCP client daemon
* Copyright 2006-2008 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 <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include "common.h"
#include "dhcp.h"
#include "interface.h"
#include "logger.h"
#include "info.h"
#ifdef ENABLE_INFO
/* Create a malloced string of cstr, changing ' to '\''
* so the contents work in a shell */
static char *cleanmetas (const char *cstr)
{
const char *p = cstr;
char *new;
char *n;
size_t len;
if (cstr == NULL || (len = strlen (cstr)) == 0)
return (xstrdup (""));
n = new = xmalloc (sizeof (char) * len + 2);
do
if (*p == '\'')
{
size_t pos = n - new;
len += 4;
new = xrealloc (new, sizeof (char) * len + 1);
n = new + pos;
*n++ = '\'';
*n++ = '\\';
*n++ = '\'';
*n++ = '\'';
}
else
*n++ = *p;
while (*p++);
/* Terminate the sucker */
*n = '\0';
return (new);
}
static void print_addresses (FILE *f, const struct address_head *addresses)
{
const address_t *addr;
STAILQ_FOREACH (addr, addresses, entries)
{
fprintf (f, "%s", inet_ntoa (addr->address));
if (STAILQ_NEXT (addr, entries))
fprintf (f, " ");
}
}
static void print_clean (FILE *f, const char *name, const char *value)
{
char *clean;
if (! value)
return;
clean = cleanmetas (value);
fprintf (f, "%s='%s'\n", name, clean);
free (clean);
}
bool write_info(const interface_t *iface, const dhcp_t *dhcp,
const options_t *options, bool overwrite)
{
FILE *f;
route_t *route;
struct stat sb;
if (options->test)
f = stdout;
else
{
if (! overwrite && stat (iface->infofile, &sb) == 0)
return (true);
logger (LOG_DEBUG, "writing %s", iface->infofile);
if ((f = fopen (iface->infofile, "w")) == NULL)
{
logger (LOG_ERR, "fopen `%s': %s",
iface->infofile, strerror (errno));
return (false);
}
}
if (dhcp->address.s_addr)
{
struct in_addr n;
n.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr;
fprintf (f, "IPADDR='%s'\n", inet_ntoa (dhcp->address));
fprintf (f, "NETMASK='%s'\n", inet_ntoa (dhcp->netmask));
fprintf (f, "NETWORK='%s'\n", inet_ntoa (n));
fprintf (f, "BROADCAST='%s'\n", inet_ntoa (dhcp->broadcast));
}
if (dhcp->mtu > 0)
fprintf (f, "MTU='%d'\n", dhcp->mtu);
if (dhcp->routes)
{
bool doneone = false;
fprintf (f, "ROUTES='");
STAILQ_FOREACH (route, dhcp->routes, entries)
{
if (route->destination.s_addr != 0)
{
if (doneone)
fprintf (f, " ");
fprintf (f, "%s", inet_ntoa (route->destination));
fprintf (f, ",%s", inet_ntoa (route->netmask));
fprintf (f, ",%s", inet_ntoa (route->gateway));
doneone = true;
}
}
fprintf (f, "'\n");
doneone = false;
fprintf (f, "GATEWAYS='");
STAILQ_FOREACH (route, dhcp->routes, entries)
{
if (route->destination.s_addr == 0)
{
if (doneone)
fprintf (f, " ");
fprintf (f, "%s", inet_ntoa (route->gateway));
doneone = true;
}
}
fprintf (f, "'\n");
}
print_clean (f, "HOSTNAME", dhcp->hostname);
print_clean (f, "DNSDOMAIN", dhcp->dnsdomain);
print_clean (f, "DNSSEARCH", dhcp->dnssearch);
if (dhcp->dnsservers)
{
fprintf (f, "DNSSERVERS='");
print_addresses (f, dhcp->dnsservers);
fprintf (f, "'\n");
}
if (dhcp->fqdn)
{
fprintf (f, "FQDNFLAGS='%u'\n", dhcp->fqdn->flags);
fprintf (f, "FQDNRCODE1='%u'\n", dhcp->fqdn->r1);
fprintf (f, "FQDNRCODE2='%u'\n", dhcp->fqdn->r2);
print_clean (f, "FQDNHOSTNAME", dhcp->fqdn->name);
}
if (dhcp->ntpservers)
{
fprintf (f, "NTPSERVERS='");
print_addresses (f, dhcp->ntpservers);
fprintf (f, "'\n");
}
print_clean (f, "NISDOMAIN", dhcp->nisdomain);
if (dhcp->nisservers)
{
fprintf (f, "NISSERVERS='");
print_addresses (f, dhcp->nisservers);
fprintf (f, "'\n");
}
print_clean (f, "ROOTPATH", dhcp->rootpath);
print_clean (f, "SIPSERVERS", dhcp->sipservers);
if (dhcp->serveraddress.s_addr)
fprintf (f, "DHCPSID='%s'\n", inet_ntoa (dhcp->serveraddress));
if (dhcp->servername[0])
print_clean (f, "DHCPSNAME", dhcp->servername);
if (! options->doinform && dhcp->address.s_addr)
{
if (! options->test)
fprintf (f, "LEASEDFROM='%u'\n", dhcp->leasedfrom);
fprintf (f, "LEASETIME='%u'\n", dhcp->leasetime);
fprintf (f, "RENEWALTIME='%u'\n", dhcp->renewaltime);
fprintf (f, "REBINDTIME='%u'\n", dhcp->rebindtime);
}
print_clean (f, "INTERFACE", iface->name);
print_clean (f, "CLASSID", options->classid);
if (iface->clientid_len > 0)
{
fprintf (f, "CLIENTID='%s'\n",
hwaddr_ntoa (iface->clientid, iface->clientid_len));
}
fprintf (f, "DHCPCHADDR='%s'\n", hwaddr_ntoa (iface->hwaddr,
iface->hwlen));
#ifdef ENABLE_INFO_COMPAT
/* Support the old .info settings if we need to */
fprintf (f, "\n# dhcpcd-1.x and 2.x compatible variables\n");
if (dhcp->dnsservers)
{
address_t *addr;
fprintf (f, "DNS='");
STAILQ_FOREACH (addr, dhcp->dnsservers, entries)
{
fprintf (f, "%s", inet_ntoa (addr->address));
if (STAILQ_NEXT (addr, entries))
fprintf (f, ",");
}
fprintf (f, "'\n");
}
if (dhcp->routes)
{
bool doneone = false;
fprintf (f, "GATEWAY='");
STAILQ_FOREACH (route, dhcp->routes, entries)
{
if (route->destination.s_addr == 0)
{
if (doneone)
fprintf (f, ",");
fprintf (f, "%s", inet_ntoa (route->gateway));
doneone = true;
}
}
fprintf (f, "'\n");
}
#endif
if (! options->test)
fclose (f);
return (true);
}
static bool parse_address (struct in_addr *addr,
const char *value, const char *var)
{
if (inet_aton (value, addr) == 0)
{
logger (LOG_ERR, "%s `%s': %s", var, value,
strerror (errno));
return (false);
}
return (true);
}
static bool parse_uint (unsigned int *i,
const char *value, const char *var)
{
if (sscanf (value, "%u", i) != 1)
{
logger (LOG_ERR, "%s `%s': not a valid number",
var, value);
return (false);
}
return (true);
}
static bool parse_ushort (unsigned short *s,
const char *value, const char *var)
{
if (sscanf (value, "%hu", s) != 1)
{
logger (LOG_ERR, "%s `%s': not a valid number",
var, value);
return (false);
}
return (true);
}
static struct address_head *parse_addresses (char *value, const char *var)
{
char *token;
char *p = value;
struct address_head *head = NULL;
while ((token = strsep (&p, " ")))
{
address_t *a = xzalloc (sizeof (*a));
if (inet_aton (token, &a->address) == 0)
{
logger (LOG_ERR, "%s: invalid address `%s'", var, token);
free_address (head);
free (a);
return (NULL);
}
if (! head)
{
head = xmalloc (sizeof (*head));
STAILQ_INIT (head);
}
STAILQ_INSERT_TAIL (head, a, entries);
}
return (head);
}
bool read_info (const interface_t *iface, dhcp_t *dhcp)
{
FILE *fp;
char *line;
char *var;
char *value;
char *p;
struct stat sb;
if (stat (iface->infofile, &sb) != 0)
{
logger (LOG_ERR, "lease information file `%s' does not exist",
iface->infofile);
return (false);
}
if (! (fp = fopen (iface->infofile, "r")))
{
logger (LOG_ERR, "fopen `%s': %s",
iface->infofile, strerror (errno));
return (false);
}
dhcp->frominfo = true;
while ((line = get_line (fp)))
{
var = line;
/* Strip leading spaces/tabs */
while ((*var == ' ') || (*var == '\t'))
var++;
/* Trim trailing \n */
p = var + strlen (var) - 1;
if (*p == '\n')
*p = 0;
/* Skip comments */
if (*var == '#')
goto next;
/* If we don't have an equals sign then skip it */
if (! (p = strchr (var, '=')))
goto next;
/* Terminate the = so we have two strings */
*p = 0;
value = p + 1;
/* Strip leading and trailing quotes if present */
if (*value == '\'' || *value == '"')
value++;
p = value + strlen (value) - 1;
if (*p == '\'' || *p == '"')
*p = 0;
/* Don't process null vars or values */
if (! *var || ! *value)
goto next;
if (strcmp (var, "IPADDR") == 0)
parse_address (&dhcp->address, value, "IPADDR");
else if (strcmp (var, "NETMASK") == 0)
parse_address (&dhcp->netmask, value, "NETMASK");
else if (strcmp (var, "BROADCAST") == 0)
parse_address (&dhcp->broadcast, value, "BROADCAST");
else if (strcmp (var, "MTU") == 0)
parse_ushort (&dhcp->mtu, value, "MTU");
else if (strcmp (var, "ROUTES") == 0)
{
p = value;
while ((value = strsep (&p, " ")))
{
char *pp = value;
char *dest = strsep (&pp, ",");
char *net = strsep (&pp, ",");
char *gate = strsep (&pp, ",");
route_t *route;
if (! dest || ! net || ! gate)
{
logger (LOG_ERR, "read_info ROUTES `%s,%s,%s': invalid route",
dest, net, gate);
goto next;
}
/* See if we can create a route */
route = xzalloc (sizeof (*route));
if (inet_aton (dest, &route->destination) == 0)
{
logger (LOG_ERR, "read_info ROUTES `%s': not a valid destination address",
dest);
free (route);
goto next;
}
if (inet_aton (dest, &route->netmask) == 0)
{
logger (LOG_ERR, "read_info ROUTES `%s': not a valid netmask address",
net);
free (route);
goto next;
}
if (inet_aton (dest, &route->gateway) == 0)
{
logger (LOG_ERR, "read_info ROUTES `%s': not a valid gateway address",
gate);
free (route);
goto next;
}
/* OK, now add our route */
if (! dhcp->routes)
{
dhcp->routes = xmalloc (sizeof (*dhcp->routes));
STAILQ_INIT (dhcp->routes);
}
STAILQ_INSERT_TAIL (dhcp->routes, route, entries);
}
}
else if (strcmp (var, "GATEWAYS") == 0)
{
p = value;
while ((value = strsep (&p, " ")))
{
route_t *route = xzalloc (sizeof (*route));
if (parse_address (&route->gateway, value, "GATEWAYS"))
{
if (! dhcp->routes)
{
dhcp->routes = xmalloc (sizeof (*dhcp->routes));
STAILQ_INIT (dhcp->routes);
}
STAILQ_INSERT_TAIL (dhcp->routes, route, entries);
}
else
free (route);
}
}
else if (strcmp (var, "HOSTNAME") == 0)
dhcp->hostname = xstrdup (value);
else if (strcmp (var, "DNSDOMAIN") == 0)
dhcp->dnsdomain = xstrdup (value);
else if (strcmp (var, "DNSSEARCH") == 0)
dhcp->dnssearch = xstrdup (value);
else if (strcmp (var, "DNSSERVERS") == 0)
dhcp->dnsservers = parse_addresses (value, "DNSSERVERS");
else if (strcmp (var, "NTPSERVERS") == 0)
dhcp->ntpservers = parse_addresses (value, "NTPSERVERS");
else if (strcmp (var, "NISDOMAIN") == 0)
dhcp->nisdomain = xstrdup (value);
else if (strcmp (var, "NISSERVERS") == 0)
dhcp->nisservers = parse_addresses (value, "NISSERVERS");
else if (strcmp (var, "ROOTPATH") == 0)
dhcp->rootpath = xstrdup (value);
else if (strcmp (var, "DHCPSID") == 0)
parse_address (&dhcp->serveraddress, value, "DHCPSID");
else if (strcmp (var, "DHCPSNAME") == 0)
strlcpy (dhcp->servername, value, sizeof (dhcp->servername));
else if (strcmp (var, "LEASEDFROM") == 0)
parse_uint (&dhcp->leasedfrom, value, "LEASEDFROM");
else if (strcmp (var, "LEASETIME") == 0)
parse_uint (&dhcp->leasetime, value, "LEASETIME");
else if (strcmp (var, "RENEWALTIME") == 0)
parse_uint (&dhcp->renewaltime, value, "RENEWALTIME");
else if (strcmp (var, "REBINDTIME") == 0)
parse_uint (&dhcp->rebindtime, value, "REBINDTIME");
next:
free (line);
}
fclose (fp);
return (true);
}
#endif