view configure.c @ 0:49aca2b065f8 draft

Add dhcpcd-3 re-write
author Roy Marples <roy@marples.name>
date Mon, 27 Nov 2006 20:23:22 +0000
parents
children 1405eec5277b
line wrap: on
line source

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

#ifdef __linux__
#include <netinet/ether.h>
#endif
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <resolv.h>
#include <stdlib.h>
#include <unistd.h>

#include "common.h"
#include "dhcp.h"
#include "interface.h"
#include "dhcpcd.h"
#include "pathnames.h"
#include "logger.h"
#include "socket.h"

static char *cleanmetas (char *cstr)
{
  if (!cstr)
    return "";
  
  register char *c = cstr;

  do
    if (*c == 39)
      *c = ' ';
  while (*c++);
  
  return cstr;
}

void exec_script (char *script, char *infofile, char *arg)
{
  if (!script || !infofile || !arg)
    return;

  struct stat buf;
  if (stat (script, &buf))
    {
      if (strcmp (script, DEFAULT_SCRIPT))
	logger (LOG_ERR, "`%s': %s", script, strerror (ENOENT));
      return;
    }
  
  char *argc[4];

  argc[0] = script;
  argc[1] = infofile;
  argc[2] = arg;
  argc[3] = NULL;
  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 */
  pid_t pid;
  if ((pid = fork ()) == 0)
    {
      if (execv (script, argc))
	logger (LOG_ERR, "error executing \"%s %s %s\": %m",
		argc[0], argc[1], argc[2]);
      exit (0);
    }
  else if (pid == -1)
    logger (LOG_ERR, "fork: %s", strerror (errno));
}

static int make_resolv (char *ifname, dhcp_t *dhcp, int wait)
{
  FILE *f;
  struct stat buf;
  char resolvconf[PATH_MAX];
  address_t *address;

  if (!stat ("/sbin/resolvconf", &buf))
    {
      logger (LOG_DEBUG, "sending DNS information to resolvconf");
      snprintf (resolvconf, PATH_MAX, "/sbin/resolvconf -a %s", ifname);
      f = popen (resolvconf, "w");

      if (!f)
	logger (LOG_ERR, "popen: %m");
    }
  else
    {
      if (! (f = fopen(RESOLVFILE, "w")))
	logger (LOG_ERR, "fopen `%s': %m", RESOLVFILE);
    }

  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);
	  logger (LOG_DEBUG, "resolvconf completed");
	}
      else
	fclose (f);
    }
  else
    return -1;

  /* Refresh the local resolver */
  res_init ();
  return 0;
}

static void restore_resolv(char *ifname)
{
  struct stat buf;

  if (stat ("/sbin/resolvconf", &buf))
    return;

  logger (LOG_DEBUG, "removing information from resolvconf");

  char *argc[4];

  argc[0] = "/sbin/resolvconf";
  argc[1] = "-d";
  argc[2] = ifname;
  argc[3] = NULL;

  /* 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 */
  pid_t pid;
  if ((pid = fork ()) == 0)
    {
      if (execve (argc[0], argc, NULL))
	logger (LOG_ERR, "error executing \"%s %s %s\": %m",
		argc[0], argc[1], argc[2]);
      exit (0);
    }
  else if (pid == -1)
    logger (LOG_ERR, "fork: %s", strerror (errno));
}

static int make_ntp (char *ifname, dhcp_t *dhcp)
{
  FILE *f;
  address_t *address;
  char *a;

  if (! (f = fopen(NTPFILE, "w")))
    {
      logger (LOG_ERR, "fopen `%s': %m", NTPFILE);
      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 (char *ifname, dhcp_t *dhcp)
{
  FILE *f;
  address_t *address;
  char prefix[256] = {0};

  if (! (f = fopen(NISFILE, "w")))
    {
      logger (LOG_ERR, "fopen `%s': %m", NISFILE);
      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
    sprintf(prefix, "ypserver %c", '\0');

  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(interface_t *iface, dhcp_t *dhcp)
{
  FILE *f;
  route_t *route;
  address_t *address;

  if ((f = fopen (iface->infofile, "w")) == NULL)
    {
      logger (LOG_ERR, "fopen `%s': %m", iface->infofile);
      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, "DHCPCHADDR=%s\n", ether_ntoa (&iface->ethernet_address));
  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 (dhcp->classid));
  fprintf (f, "CLIENTID='%s'\n", cleanmetas (dhcp->clientid));

  fclose (f);
  return 0;
}

int configure (options_t *options, interface_t *iface, 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;
	}

      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;
      
      for (route = dhcp->routes; route; route = route->next)
	{
	  int 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, (options->dohostname && !dhcp->hostname));

  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) 
      || !strcmp (curhostname, "(none)") || !strcmp (curhostname, "localhost"))
    {
      if (dhcp->hostname)
	strcpy ((char *) newhostname, dhcp->hostname); 

      sethostname ((char *) newhostname, strlen ((char *) newhostname));
      logger (LOG_INFO, "setting hostname to %s", newhostname);
    }

  write_info (iface, dhcp);

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