mirror of
https://github.com/rsmarples/dhcpcd.git
synced 2024-11-27 20:14:15 +08:00
552 lines
15 KiB
C
552 lines
15 KiB
C
/*
|
|
* dhcpcd - DHCP client daemon -
|
|
* Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
|
|
*
|
|
* dhcpcd is an RFC2131 compliant DHCP client daemon.
|
|
*
|
|
* This is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/in.h>
|
|
#ifdef __linux__
|
|
#include <netinet/ether.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <resolv.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
#include "configure.h"
|
|
#include "dhcp.h"
|
|
#include "interface.h"
|
|
#include "dhcpcd.h"
|
|
#include "pathnames.h"
|
|
#include "logger.h"
|
|
#include "socket.h"
|
|
|
|
static char *cleanmetas (const char *cstr)
|
|
{
|
|
/* The largest single element we can have is 256 bytes according to the RFC,
|
|
so this buffer size should be safe even if it's all ' */
|
|
char buffer[1024] = {0};
|
|
char *b = buffer;
|
|
|
|
if (! cstr || strlen (cstr) == 0)
|
|
return b;
|
|
|
|
do
|
|
if (*cstr == 39)
|
|
{
|
|
*b++ = '\'';
|
|
*b++ = '\\';
|
|
*b++ = '\'';
|
|
*b++ = '\'';
|
|
}
|
|
else
|
|
*b++ = *cstr;
|
|
while (*cstr++);
|
|
|
|
*b++ = 0;
|
|
b = buffer;
|
|
|
|
return b;
|
|
}
|
|
|
|
static void exec_script (const char *script, const char *infofile,
|
|
const char *arg)
|
|
{
|
|
struct stat buf;
|
|
pid_t pid;
|
|
|
|
if (! script || ! infofile || ! arg)
|
|
return;
|
|
|
|
if (stat (script, &buf) < 0)
|
|
{
|
|
if (strcmp (script, DEFAULT_SCRIPT) != 0)
|
|
logger (LOG_ERR, "`%s': %s", script, strerror (ENOENT));
|
|
return;
|
|
}
|
|
|
|
logger (LOG_DEBUG, "exec \"%s %s %s\"", script, infofile, arg);
|
|
|
|
/* We don't wait for the user script to finish - do we trust it? */
|
|
/* Don't use vfork as we lose our memory when dhcpcd exits
|
|
causing the script to fail */
|
|
if ((pid = fork ()) == 0)
|
|
{
|
|
if (execle (script, script, infofile, arg, NULL, NULL))
|
|
logger (LOG_ERR, "error executing \"%s %s %s\": %s",
|
|
script, infofile, arg, strerror (errno));
|
|
exit (0);
|
|
}
|
|
else if (pid == -1)
|
|
logger (LOG_ERR, "fork: %s", strerror (errno));
|
|
}
|
|
|
|
static int make_resolv (const char *ifname, const dhcp_t *dhcp)
|
|
{
|
|
FILE *f;
|
|
struct stat buf;
|
|
char resolvconf[PATH_MAX] = {0};
|
|
address_t *address;
|
|
|
|
if (stat (RESOLVCONF, &buf) == 0)
|
|
{
|
|
logger (LOG_DEBUG, "sending DNS information to resolvconf");
|
|
snprintf (resolvconf, PATH_MAX, RESOLVCONF" -a %s", ifname);
|
|
f = popen (resolvconf, "w");
|
|
|
|
if (! f)
|
|
logger (LOG_ERR, "popen: %s", strerror (errno));
|
|
}
|
|
else
|
|
{
|
|
logger (LOG_DEBUG, "writing "RESOLVFILE);
|
|
if (! (f = fopen(RESOLVFILE, "w")))
|
|
logger (LOG_ERR, "fopen `%s': %s", RESOLVFILE, strerror (errno));
|
|
}
|
|
|
|
if (f)
|
|
{
|
|
fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
|
|
if (dhcp->dnssearch)
|
|
fprintf (f, "search %s\n", dhcp->dnssearch);
|
|
else if (dhcp->dnsdomain) {
|
|
fprintf (f, "search %s\n", dhcp->dnsdomain);
|
|
}
|
|
|
|
for (address = dhcp->dnsservers; address; address = address->next)
|
|
fprintf (f, "nameserver %s\n", inet_ntoa (address->address));
|
|
|
|
if (*resolvconf)
|
|
pclose (f);
|
|
else
|
|
fclose (f);
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
/* Refresh the local resolver */
|
|
res_init ();
|
|
return 0;
|
|
}
|
|
|
|
static void restore_resolv(const char *ifname)
|
|
{
|
|
struct stat buf;
|
|
pid_t pid;
|
|
|
|
if (stat (RESOLVCONF, &buf) < 0)
|
|
return;
|
|
|
|
logger (LOG_DEBUG, "removing information from resolvconf");
|
|
|
|
/* Don't wait around here as we should only be called when
|
|
dhcpcd is closing down and something may do a kill -9
|
|
if we take too long */
|
|
/* Don't use vfork as we lose our memory when dhcpcd exits
|
|
causing the script to fail */
|
|
if ((pid = fork ()) == 0)
|
|
{
|
|
if (execle (RESOLVCONF, RESOLVCONF, "-d", ifname, NULL, NULL))
|
|
logger (LOG_ERR, "error executing \"%s -d %s\": %s",
|
|
RESOLVCONF, ifname, strerror (errno));
|
|
exit (0);
|
|
}
|
|
else if (pid == -1)
|
|
logger (LOG_ERR, "fork: %s", strerror (errno));
|
|
}
|
|
|
|
static int make_ntp (const char *ifname, const dhcp_t *dhcp)
|
|
{
|
|
FILE *f;
|
|
address_t *address;
|
|
char *a;
|
|
|
|
logger (LOG_DEBUG, "writing "NTPFILE);
|
|
if (! (f = fopen(NTPFILE, "w")))
|
|
{
|
|
logger (LOG_ERR, "fopen `%s': %s", NTPFILE, strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
|
|
fprintf (f, "restrict default noquery notrust nomodify\n");
|
|
fprintf (f, "restrict 127.0.0.1\n");
|
|
|
|
for (address = dhcp->ntpservers; address; address = address->next)
|
|
{
|
|
a = inet_ntoa (address->address);
|
|
fprintf (f, "restrict %s nomodify notrap noquery\nserver %s\n", a, a);
|
|
}
|
|
|
|
fprintf (f, "driftfile " NTPDRIFTFILE "\n");
|
|
fprintf (f, "logfile " NTPLOGFILE "\n");
|
|
fclose (f);
|
|
return 0;
|
|
}
|
|
|
|
static int make_nis (const char *ifname, const dhcp_t *dhcp)
|
|
{
|
|
FILE *f;
|
|
address_t *address;
|
|
char prefix[256] = {0};
|
|
|
|
logger (LOG_DEBUG, "writing "NISFILE);
|
|
if (! (f = fopen(NISFILE, "w")))
|
|
{
|
|
logger (LOG_ERR, "fopen `%s': %s", NISFILE, strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
|
|
if (dhcp->nisdomain)
|
|
{
|
|
setdomainname (dhcp->nisdomain, strlen (dhcp->nisdomain));
|
|
|
|
if (dhcp->nisservers)
|
|
snprintf (prefix, sizeof (prefix), "domain %s server", dhcp->nisdomain);
|
|
else
|
|
fprintf (f, "domain %s broadcast\n", dhcp->nisdomain);
|
|
}
|
|
else
|
|
snprintf (prefix, sizeof (prefix), "%s", "ypserver");
|
|
|
|
for (address = dhcp->nisservers; address; address = address->next)
|
|
fprintf (f, "%s %s\n", prefix, inet_ntoa (address->address));
|
|
|
|
fclose (f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_info(const interface_t *iface, const dhcp_t *dhcp,
|
|
const options_t *options)
|
|
{
|
|
FILE *f;
|
|
route_t *route;
|
|
address_t *address;
|
|
|
|
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 -1;
|
|
}
|
|
|
|
fprintf (f, "IPADDR='%s'\n", inet_ntoa (dhcp->address));
|
|
fprintf (f, "NETMASK='%s'\n", inet_ntoa (dhcp->netmask));
|
|
fprintf (f, "BROADCAST='%s'\n", inet_ntoa (dhcp->broadcast));
|
|
if (dhcp->mtu > 0)
|
|
fprintf (f, "MTU='%d'\n", dhcp->mtu);
|
|
|
|
if (dhcp->routes)
|
|
{
|
|
fprintf (f, "ROUTES='");
|
|
for (route = dhcp->routes; route; route = route->next)
|
|
{
|
|
fprintf (f, "%s", inet_ntoa (route->destination));
|
|
fprintf (f, ",%s", inet_ntoa (route->netmask));
|
|
fprintf (f, ",%s", inet_ntoa (route->gateway));
|
|
if (route->next)
|
|
fprintf (f, " ");
|
|
}
|
|
fprintf (f, "'\n");
|
|
}
|
|
|
|
if (dhcp->hostname)
|
|
fprintf (f, "HOSTNAME='%s'\n",cleanmetas (dhcp->hostname));
|
|
|
|
if (dhcp->dnsdomain)
|
|
fprintf (f, "DNSDOMAIN='%s'\n", cleanmetas (dhcp->dnsdomain));
|
|
|
|
if (dhcp->dnssearch)
|
|
fprintf (f, "DNSSEARCH='%s'\n", cleanmetas (dhcp->dnssearch));
|
|
|
|
if (dhcp->dnsservers)
|
|
{
|
|
fprintf (f, "DNSSERVERS='");
|
|
for (address = dhcp->dnsservers; address; address = address->next)
|
|
{
|
|
fprintf (f, "%s", inet_ntoa (address->address));
|
|
if (address->next)
|
|
fprintf (f, " ");
|
|
}
|
|
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);
|
|
fprintf (f, "FQDNHOSTNAME='%s'\n", dhcp->fqdn->name);
|
|
}
|
|
|
|
if (dhcp->ntpservers)
|
|
{
|
|
fprintf (f, "NTPSERVERS='");
|
|
for (address = dhcp->ntpservers; address; address = address->next)
|
|
{
|
|
fprintf (f, "%s", inet_ntoa (address->address));
|
|
if (address->next)
|
|
fprintf (f, " ");
|
|
}
|
|
fprintf (f, "'\n");
|
|
}
|
|
|
|
if (dhcp->nisdomain)
|
|
fprintf (f, "NISDOMAIN='%s'\n", cleanmetas (dhcp->nisdomain));
|
|
|
|
if (dhcp->nisservers)
|
|
{
|
|
fprintf (f, "NISSERVERS='");
|
|
for (address = dhcp->nisservers; address; address = address->next)
|
|
{
|
|
fprintf (f, "%s", inet_ntoa (address->address));
|
|
if (address->next)
|
|
fprintf (f, " ");
|
|
}
|
|
fprintf (f, "'\n");
|
|
}
|
|
|
|
if (dhcp->rootpath)
|
|
fprintf (f, "ROOTPATH='%s'\n", cleanmetas (dhcp->rootpath));
|
|
|
|
fprintf (f, "DHCPSID='%s'\n", inet_ntoa (dhcp->serveraddress));
|
|
fprintf (f, "DHCPSNAME='%s'\n", cleanmetas (dhcp->servername));
|
|
fprintf (f, "LEASETIME='%u'\n", dhcp->leasetime);
|
|
fprintf (f, "RENEWALTIME='%u'\n", dhcp->renewaltime);
|
|
fprintf (f, "REBINDTIME='%u'\n", dhcp->rebindtime);
|
|
fprintf (f, "INTERFACE='%s'\n", iface->name);
|
|
fprintf (f, "CLASSID='%s'\n", cleanmetas (options->classid));
|
|
if (options->clientid[0])
|
|
fprintf (f, "CLIENTID='%s'\n", cleanmetas (options->clientid));
|
|
else
|
|
fprintf (f, "CLIENTID='%s'\n", ether_ntoa (&iface->ethernet_address));
|
|
fprintf (f, "DHCPCHADDR='%s'\n", ether_ntoa (&iface->ethernet_address));
|
|
fclose (f);
|
|
return 0;
|
|
}
|
|
|
|
int configure (const options_t *options, interface_t *iface,
|
|
const dhcp_t *dhcp)
|
|
{
|
|
route_t *route = NULL;
|
|
route_t *new_route = NULL;
|
|
route_t *old_route = NULL;
|
|
struct hostent *he = NULL;
|
|
char newhostname[HOSTNAME_MAX_LEN] = {0};
|
|
char curhostname[HOSTNAME_MAX_LEN] = {0};
|
|
char *dname = NULL;
|
|
int dnamel = 0;
|
|
|
|
if (! options || ! iface || ! dhcp)
|
|
return -1;
|
|
|
|
/* Remove old routes
|
|
Always do this as the interface may have >1 address not added by us
|
|
so the routes we added may still exist */
|
|
if (iface->previous_routes)
|
|
{
|
|
for (route = iface->previous_routes; route; route = route->next)
|
|
if (route->destination.s_addr || options->dogateway)
|
|
{
|
|
int have = 0;
|
|
if (dhcp->address.s_addr != 0)
|
|
for (new_route = dhcp->routes; new_route; new_route = new_route->next)
|
|
if (new_route->destination.s_addr == route->destination.s_addr
|
|
&& new_route->netmask.s_addr == route->netmask.s_addr
|
|
&& new_route->gateway.s_addr == route->gateway.s_addr)
|
|
{
|
|
have = 1;
|
|
break;
|
|
}
|
|
if (! have)
|
|
del_route (iface->name, route->destination, route->netmask,
|
|
route->gateway, options->metric);
|
|
}
|
|
}
|
|
|
|
/* If we don't have an address, then return */
|
|
if (dhcp->address.s_addr == 0)
|
|
{
|
|
if (iface->previous_routes)
|
|
{
|
|
free_route (iface->previous_routes);
|
|
iface->previous_routes = NULL;
|
|
}
|
|
|
|
/* Only reset things if we had set them before */
|
|
if (iface->previous_address.s_addr != 0)
|
|
{
|
|
del_address (iface->name, iface->previous_address);
|
|
memset (&iface->previous_address, 0, sizeof (struct in_addr));
|
|
|
|
restore_resolv (iface->name);
|
|
|
|
/* we currently don't have a resolvconf style programs for ntp/nis */
|
|
exec_script (options->script, iface->infofile, "down");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (add_address (iface->name, dhcp->address, dhcp->netmask,
|
|
dhcp->broadcast) < 0 && errno != EEXIST)
|
|
return -1;
|
|
|
|
/* Now delete the old address if different */
|
|
if (iface->previous_address.s_addr != dhcp->address.s_addr
|
|
&& iface->previous_address.s_addr != 0)
|
|
del_address (iface->name, iface->previous_address);
|
|
|
|
#ifdef __linux__
|
|
/* On linux, we need to change the subnet route to have our metric. */
|
|
if (iface->previous_address.s_addr != dhcp->address.s_addr
|
|
&& options->metric > 0)
|
|
{
|
|
struct in_addr td;
|
|
struct in_addr tg;
|
|
memset (&td, 0, sizeof (td));
|
|
memset (&tg, 0, sizeof (tg));
|
|
td.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr;
|
|
add_route (iface->name, td, dhcp->netmask, tg, options->metric);
|
|
del_route (iface->name, td, dhcp->netmask, tg, 0);
|
|
}
|
|
#endif
|
|
|
|
/* Remember added routes */
|
|
if (dhcp->routes)
|
|
{
|
|
route_t *new_routes = NULL;
|
|
int remember;
|
|
|
|
for (route = dhcp->routes; route; route = route->next)
|
|
{
|
|
/* Don't set default routes if not asked to */
|
|
if (route->destination.s_addr == 0 && route->netmask.s_addr == 0
|
|
&& ! options->dogateway)
|
|
continue;
|
|
|
|
remember = add_route (iface->name, route->destination,
|
|
route->netmask, route->gateway,
|
|
options->metric);
|
|
/* If we failed to add the route, we may have already added it
|
|
ourselves. If so, remember it again. */
|
|
if (remember < 0)
|
|
for (old_route = iface->previous_routes; old_route;
|
|
old_route = old_route->next)
|
|
if (old_route->destination.s_addr == route->destination.s_addr
|
|
&& old_route->netmask.s_addr == route->netmask.s_addr
|
|
&& old_route->gateway.s_addr == route->gateway.s_addr)
|
|
{
|
|
remember = 1;
|
|
break;
|
|
}
|
|
|
|
if (remember >= 0)
|
|
{
|
|
if (! new_routes)
|
|
{
|
|
new_routes = xmalloc (sizeof (route_t));
|
|
memset (new_routes, 0, sizeof (route_t));
|
|
new_route = new_routes;
|
|
}
|
|
else
|
|
{
|
|
new_route->next = xmalloc (sizeof (route_t));
|
|
new_route = new_route->next;
|
|
}
|
|
memcpy (new_route, route, sizeof (route_t));
|
|
new_route -> next = NULL;
|
|
}
|
|
}
|
|
|
|
if (iface->previous_routes)
|
|
free_route (iface->previous_routes);
|
|
|
|
iface->previous_routes = new_routes;
|
|
}
|
|
|
|
if (options->dodns && dhcp->dnsservers)
|
|
make_resolv(iface->name, dhcp);
|
|
else
|
|
logger (LOG_DEBUG, "no dns information to write");
|
|
|
|
if (options->dontp && dhcp->ntpservers)
|
|
make_ntp(iface->name, dhcp);
|
|
|
|
if (options->donis && (dhcp->nisservers || dhcp->nisdomain))
|
|
make_nis(iface->name, dhcp);
|
|
|
|
/* Now we have made a resolv.conf we can obtain a hostname if we need one */
|
|
if (options->dohostname && ! dhcp->hostname)
|
|
{
|
|
he = gethostbyaddr (inet_ntoa (dhcp->address),
|
|
sizeof (struct in_addr), AF_INET);
|
|
if (he)
|
|
{
|
|
dname = he->h_name;
|
|
while (*dname > 32)
|
|
dname++;
|
|
dnamel = dname - he->h_name;
|
|
memcpy (newhostname, he->h_name, dnamel);
|
|
newhostname[dnamel] = 0;
|
|
}
|
|
}
|
|
|
|
gethostname (curhostname, sizeof (curhostname));
|
|
|
|
if (options->dohostname
|
|
|| strlen (curhostname) == 0
|
|
|| strcmp (curhostname, "(none)") == 0
|
|
|| strcmp (curhostname, "localhost") == 0)
|
|
{
|
|
if (dhcp->hostname)
|
|
strcpy (newhostname, dhcp->hostname);
|
|
|
|
if (*newhostname)
|
|
{
|
|
logger (LOG_INFO, "setting hostname to `%s'", newhostname);
|
|
sethostname (newhostname, strlen (newhostname));
|
|
}
|
|
}
|
|
|
|
write_info (iface, dhcp, options);
|
|
|
|
if (iface->previous_address.s_addr != dhcp->address.s_addr)
|
|
{
|
|
memcpy (&iface->previous_address,
|
|
&dhcp->address, sizeof (struct in_addr));
|
|
exec_script (options->script, iface->infofile, "new");
|
|
}
|
|
else
|
|
exec_script (options->script, iface->infofile, "up");
|
|
|
|
return 0;
|
|
}
|
|
|