mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
429 lines
11 KiB
C
429 lines
11 KiB
C
/* 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;
|
|
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_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime,
|
|
&doctors);
|
|
|
|
/* if we cannot support binding the wildcard address, set the "bind only
|
|
interfaces in use" option */
|
|
#ifndef HAVE_UDP_SRC_DST
|
|
options |= OPT_NOWILD;
|
|
#endif
|
|
|
|
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
|
|
|
|
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,
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|