/* dnsmasq is Copyright (c) 2000-2003 Simon Kelley

   This program 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; version 2 dated June, 1991.

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

/* See RFC1035 for details of the protocol this code talks. */

/* Author's email: simon@thekelleys.org.uk */

#include "dnsmasq.h"

static int sigterm, sighup, sigusr1, sigalarm;

static void sig_handler(int sig)
{
  if (sig == SIGTERM)
    sigterm = 1;
  else if (sig == SIGHUP)
    sighup = 1;
  else if (sig == SIGUSR1)
    sigusr1 = 1;
  else if (sig == SIGALRM)
    sigalarm = 1;
}

int main (int argc, char **argv)
{
  int cachesize = CACHESIZ;
  int port = NAMESERVER_PORT;
  int maxleases = MAXLEASES;
  int query_port = 0;
  int first_loop = 1;
  unsigned long local_ttl = 0;
  unsigned int options, min_leasetime;
  char *runfile = RUNFILE;
  time_t resolv_changed = 0;
  time_t now, last = 0;
  struct irec *interfaces = NULL;
  struct listener *listener, *listeners;
  struct doctor *doctors = NULL;
  char *mxname = NULL;
  char *mxtarget = NULL;
  char *lease_file = NULL;
  char *addn_hosts = NULL;
  char *domain_suffix = NULL;
  char *username = CHUSER;
  char *groupname = CHGRP;
  struct iname *if_names = NULL;
  struct iname *if_addrs = NULL;
  struct iname *if_except = NULL;
  struct server *serv_addrs = NULL;
  char *dnamebuff, *packet;
  int uptime_fd = -1;
  struct server *servers, *last_server;
  struct resolvc default_resolv = { NULL, 1, 0, RESOLVFILE };
  struct resolvc *resolv = &default_resolv;
  struct bogus_addr *bogus_addr = NULL;
  struct serverfd *serverfdp, *sfds = NULL;
  struct dhcp_context *dhcp_tmp, *dhcp = NULL;
  struct dhcp_config *dhcp_configs = NULL;
  struct dhcp_opt *dhcp_options = NULL;
  struct dhcp_vendor *dhcp_vendors = NULL;
  char *dhcp_file = NULL, *dhcp_sname = NULL;
  struct in_addr dhcp_next_server;
  int leasefd = -1, dhcpfd = -1, dhcp_raw_fd = -1;
  struct sigaction sigact;
  sigset_t sigmask;

  sighup = 1; /* init cache the first time through */
  sigusr1 = 0; /* but don't dump */
  sigterm = 0; /* or die */
#ifdef HAVE_BROKEN_RTC
  sigalarm = 1; /* need regular lease dumps */
#else
  sigalarm = 0; /* or not */
#endif
 
  sigact.sa_handler = sig_handler;
  sigact.sa_flags = 0;
  sigemptyset(&sigact.sa_mask);
  sigaction(SIGUSR1, &sigact, NULL);
  sigaction(SIGHUP, &sigact, NULL);
  sigaction(SIGTERM, &sigact, NULL);
  sigaction(SIGALRM, &sigact, NULL);

  /* now block all the signals, they stay that way except
      during the call to pselect */
  sigaddset(&sigact.sa_mask, SIGUSR1);
  sigaddset(&sigact.sa_mask, SIGTERM);
  sigaddset(&sigact.sa_mask, SIGHUP);
  sigaddset(&sigact.sa_mask, SIGALRM);
  sigprocmask(SIG_BLOCK, &sigact.sa_mask, &sigmask); 

  /* These get allocated here to avoid overflowing the small stack
     on embedded systems. dnamebuff is big enough to hold one
     maximal sixed domain name and gets passed into all the processing
     code. We manage to get away with one buffer. */
  dnamebuff = safe_malloc(MAXDNAME);
  packet = safe_malloc(DNSMASQ_PACKETSZ);
  
  dhcp_next_server.s_addr = 0;
  options = read_opts(argc, argv, dnamebuff, &resolv, &mxname, &mxtarget, &lease_file,
		      &username, &groupname, &domain_suffix, &runfile, 
		      &if_names, &if_addrs, &if_except, &bogus_addr, 
		      &serv_addrs, &cachesize, &port, &query_port, &local_ttl, &addn_hosts,
		      &dhcp, &dhcp_configs, &dhcp_options, &dhcp_vendors,
		      &dhcp_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime,
		      &doctors);

  if (!lease_file)
    {
      if (dhcp)
	lease_file = LEASEFILE;
    }
#ifndef HAVE_ISC_READER
  else if (!dhcp)
    die("ISC dhcpd integration not available: set HAVE_ISC_READER in src/config.h", NULL);
#endif
  
#ifndef  HAVE_UDP_SRC_DST
  /* if we cannot support binding the wildcard address, set the "bind only
     interfaces in use" option */
  options |= OPT_NOWILD;
#endif

  interfaces = enumerate_interfaces(&if_names, &if_addrs, if_except, port);
  if (options & OPT_NOWILD)
    listeners = create_bound_listeners(interfaces);
  else
    listeners = create_wildcard_listeners(port);
    
  forward_init(1);
  cache_init(cachesize, options & OPT_LOG);

#ifdef HAVE_BROKEN_RTC
  if ((uptime_fd = open(UPTIME, O_RDONLY)) == -1)
    die("cannot open " UPTIME ":%s", NULL);
#endif
 
  now = dnsmasq_time(uptime_fd);
  
  if (dhcp)
    {
      dhcp_init(&dhcpfd, &dhcp_raw_fd);
      leasefd = lease_init(lease_file, domain_suffix, dnamebuff, packet, now, maxleases);
    }
  
  setbuf(stdout, NULL);

  if (!(options & OPT_DEBUG))
    {
      FILE *pidfile;
      struct passwd *ent_pw;
      int i;
        
      /* The following code "daemonizes" the process. 
	 See Stevens section 12.4 */

#ifndef NO_FORK
      if (fork() != 0 )
	exit(0);
      
      setsid();
      
      if (fork() != 0)
	exit(0);
#endif
      
      chdir("/");
      umask(022); /* make pidfile 0644 */
      
      /* write pidfile _after_ forking ! */
      if (runfile && (pidfile = fopen(runfile, "w")))
      	{
	  fprintf(pidfile, "%d\n", (int) getpid());
	  fclose(pidfile);
	}
      
      umask(0);

      for (i=0; i<64; i++)
	{
	  for (listener = listeners; listener; listener = listener->next)
	    if (listener->fd == i)
	      break;
	  if (listener)
	    continue;

	  if (i == leasefd || 
	      i == uptime_fd ||
	      i == dhcpfd || 
	      i == dhcp_raw_fd)
	    continue;

	  close(i);
	}

      /* Change uid and gid for security */
      if (username && (ent_pw = getpwnam(username)))
	{
	  gid_t dummy;
	  struct group *gp;
	  /* remove all supplimentary groups */
	  setgroups(0, &dummy);
	  /* change group for /etc/ppp/resolv.conf 
	     otherwise get the group for "nobody" */
	  if ((groupname && (gp = getgrnam(groupname))) || 
	      (gp = getgrgid(ent_pw->pw_gid)))
	    setgid(gp->gr_gid); 
	  /* finally drop root */
	  setuid(ent_pw->pw_uid);
	}
    }

  openlog("dnsmasq", 
	  DNSMASQ_LOG_OPT(options & OPT_DEBUG), 
	  DNSMASQ_LOG_FAC(options & OPT_DEBUG));
  
  if (cachesize)
    syslog(LOG_INFO, "started, version %s cachesize %d", VERSION, cachesize);
  else
    syslog(LOG_INFO, "started, version %s cache disabled", VERSION);
  
  if (options & OPT_LOCALMX)
    syslog(LOG_INFO, "serving MX record for local hosts target %s", mxtarget);
  else if (mxname)
    syslog(LOG_INFO, "serving MX record for mailhost %s target %s", 
	   mxname, mxtarget);

  for (dhcp_tmp = dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next)
    {
      strcpy(dnamebuff, inet_ntoa(dhcp_tmp->start));
      if (dhcp_tmp->lease_time == 0)
	sprintf(packet, "infinite");
      else
	sprintf(packet, "%ds", (int)dhcp_tmp->lease_time);
      syslog(LOG_INFO, 
	     dhcp_tmp->start.s_addr == dhcp_tmp->end.s_addr ? 
	     "DHCP, static leases only on %.0s%s, lease time %s" :
	     "DHCP, IP range %s -- %s, lease time %s",
	     dnamebuff, inet_ntoa(dhcp_tmp->end), packet);
    }

#ifdef HAVE_BROKEN_RTC
  if (dhcp)
    syslog(LOG_INFO, "DHCP, %s will be written every %ds", lease_file, min_leasetime/3);
#endif

  if (getuid() == 0 || geteuid() == 0)
    syslog(LOG_WARNING, "failed to drop root privs");
  
  servers = last_server = check_servers(serv_addrs, interfaces, &sfds);
  
  while (sigterm == 0)
    {
      fd_set rset;
      
      if (sighup)
	{
	  cache_reload(options, dnamebuff, domain_suffix, addn_hosts);
	  if (dhcp)
	    {
	      if (options & OPT_ETHERS)
		dhcp_configs = dhcp_read_ethers(dhcp_configs, dnamebuff);
	      dhcp_update_configs(dhcp_configs);
	      lease_update_from_configs(dhcp_configs, domain_suffix); 
	      lease_update_file(0, now); 
	      lease_update_dns();
	    }
	  if (resolv && (options & OPT_NO_POLL))
	    servers = last_server = 
	      check_servers(reload_servers(resolv->name, dnamebuff, servers, query_port), 
			    interfaces, &sfds);
	  sighup = 0;
	}
      
      if (sigusr1)
	{
	  dump_cache(options & (OPT_DEBUG | OPT_LOG), cachesize);
	  sigusr1 = 0;
	}
      
      if (sigalarm)
	{
	  if (dhcp)
	    {
	      lease_update_file(1, now);
#ifdef HAVE_BROKEN_RTC
	      alarm(min_leasetime/3);
#endif
	    } 
	  sigalarm  = 0;
	}
      
      FD_ZERO(&rset);
      
      if (!first_loop)
	{
	  int maxfd = 0;
	  
	  for (serverfdp = sfds; serverfdp; serverfdp = serverfdp->next)
	    {
	      FD_SET(serverfdp->fd, &rset);
	      if (serverfdp->fd > maxfd)
		maxfd = serverfdp->fd;
	    }
	  
	  for (listener = listeners; listener; listener = listener->next)
	    {
	      FD_SET(listener->fd, &rset);
	      if (listener->fd > maxfd)
		maxfd = listener->fd;
	    }
	  
	  if (dhcp)
	    {
	      FD_SET(dhcpfd, &rset);
	      if (dhcpfd > maxfd)
		maxfd = dhcpfd;
	    }

#ifdef HAVE_PSELECT
	  if (pselect(maxfd+1, &rset, NULL, NULL, NULL, &sigmask) < 0)
	    FD_ZERO(&rset); /* rset otherwise undefined after error */ 
#else
	  {
	    sigset_t save_mask;
	    sigprocmask(SIG_SETMASK, &sigmask, &save_mask);
	    if (select(maxfd+1, &rset, NULL, NULL, NULL) < 0)
	      FD_ZERO(&rset); /* rset otherwise undefined after error */ 
	    sigprocmask(SIG_SETMASK, &save_mask, NULL);
	  }
#endif
	  
	}

      first_loop = 0;
      now = dnsmasq_time(uptime_fd);

      /* Check for changes to resolv files once per second max. */
      if (last == 0 || difftime(now, last) > 1.0)
	{
	  last = now;

#ifdef HAVE_ISC_READER
	  if (lease_file && !dhcp)
	    load_dhcp(lease_file, domain_suffix, now, dnamebuff);
#endif

	  if (!(options & OPT_NO_POLL))
	    {
	      struct resolvc *res = resolv, *latest = NULL;
	      struct stat statbuf;
	      time_t last_change = 0;
	      /* There may be more than one possible file. 
		 Go through and find the one which changed _last_.
		 Warn of any which can't be read. */
	      while (res)
		{
		  if (stat(res->name, &statbuf) == -1)
		    {
		      if (!res->logged)
			syslog(LOG_WARNING, "failed to access %s: %m", res->name);
		      res->logged = 1;
		    }
		  else
		    {
		      res->logged = 0;
		      if (statbuf.st_mtime > last_change)
			{
			  last_change = statbuf.st_mtime;
			  latest = res;
			}
		    }
		  res = res->next;
		}
	  
	      if (latest && last_change > resolv_changed)
		{
		  resolv_changed = last_change;
		  servers = last_server = 
		    check_servers(reload_servers(latest->name, dnamebuff, servers, query_port),
				  interfaces, &sfds);
		}
	    }
	}
		
      for (serverfdp = sfds; serverfdp; serverfdp = serverfdp->next)
	if (FD_ISSET(serverfdp->fd, &rset))
	  last_server = reply_query(serverfdp->fd, options, packet, now, 
				    dnamebuff, last_server, bogus_addr, doctors);

      if (dhcp && FD_ISSET(dhcpfd, &rset))
	dhcp_packet(dhcp, packet, dhcp_options, dhcp_configs, dhcp_vendors,
		    now, dnamebuff, domain_suffix, dhcp_file,
		    dhcp_sname, dhcp_next_server, dhcpfd, dhcp_raw_fd,
		    if_names, if_addrs, if_except);
      
      for (listener = listeners; listener; listener = listener->next)
	if (FD_ISSET(listener->fd, &rset))
	  last_server = receive_query(listener, packet,
			mxname, mxtarget, options, now, local_ttl, dnamebuff,
			if_names, if_addrs, if_except, last_server, servers);
    }
  
  syslog(LOG_INFO, "exiting on receipt of SIGTERM");

#ifdef HAVE_BROKEN_RTC
  if (dhcp)
    lease_update_file(1, now);
#endif

  if (leasefd != -1)
    close(leasefd);
  return 0;
}






