From 793bf4abbbd7a3b95a5556f68baaed0492bd9b50 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Mon, 28 Apr 2014 00:51:13 +0200 Subject: [PATCH] Add support to read ISC DHCP lease file. --- Makefile | 2 +- src/cache.c | 13 ++- src/dnsmasq.c | 5 ++ src/dnsmasq.h | 4 + src/isc.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/option.c | 2 +- 6 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 src/isc.c diff --git a/Makefile b/Makefile index dd0513b..67abb2f 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ - poll.o rrfilter.o edns0.o arp.o + poll.o rrfilter.o edns0.o arp.o isc.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h ip6addr.h diff --git a/src/cache.c b/src/cache.c index a9eaa65..9835d05 100644 --- a/src/cache.c +++ b/src/cache.c @@ -17,7 +17,7 @@ #include "dnsmasq.h" static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL; -#ifdef HAVE_DHCP +#if (defined HAVE_DHCP) || (defined HAVE_ISC_READER) static struct crec *dhcp_spare = NULL; #endif static struct crec *new_chain = NULL; @@ -217,6 +217,9 @@ static void cache_free(struct crec *crecp) crecp->flags &= ~F_BIGNAME; } + if (crecp->flags & F_DHCP) + free(crecp->name.namep); + #ifdef HAVE_DNSSEC cache_blockdata_free(crecp); #endif @@ -1131,7 +1134,7 @@ void cache_reload(void) } -#ifdef HAVE_DHCP +#if (defined HAVE_DHCP) || (defined HAVE_ISC_READER) struct in_addr a_record_from_hosts(char *name, time_t now) { struct crec *crecp = NULL; @@ -1209,7 +1212,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, addrlen = sizeof(struct in6_addr); } #endif - + inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN); while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME))) @@ -1274,7 +1277,11 @@ void cache_add_dhcp_entry(char *host_name, int prot, else crec->ttd = ttd; crec->addr.addr = *host_address; +#ifdef HAVE_ISC_READER + crec->name.namep = strdup(host_name); +#else crec->name.namep = host_name; +#endif crec->uid = next_uid(); cache_hash(crec); diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 96aa780..0a72f24 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -1013,6 +1013,11 @@ int main (int argc, char **argv) poll_resolv(0, daemon->last_resolv != 0, now); daemon->last_resolv = now; + +#ifdef HAVE_ISC_READER + if (daemon->lease_file && !daemon->dhcp) + load_dhcp(now); +#endif } #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 549ef55..7bb2055 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1503,6 +1503,10 @@ int detect_loop(char *query, int type); void inotify_dnsmasq_init(); int inotify_check(time_t now); void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz); + +/* isc.c */ +#ifdef HAVE_ISC_READER +void load_dhcp(time_t now); #endif /* poll.c */ diff --git a/src/isc.c b/src/isc.c new file mode 100644 index 0000000..565e4e2 --- /dev/null +++ b/src/isc.c @@ -0,0 +1,234 @@ +/* dnsmasq is Copyright (c) 2014 John Volpe, Simon Kelley and + Michael Tremer + + 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, or + (at your option) version 3 dated 29 June, 2007. + + 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, see . + + Code in this file is based on contributions by John Volpe and + Simon Kelley. Updated for recent versions of dnsmasq by + Michael Tremer. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_ISC_READER +#define MAXTOK 50 + +struct isc_dhcp_lease { + char* name; + char* fqdn; + time_t expires; + struct in_addr addr; + struct isc_dhcp_lease* next; +}; + +static struct isc_dhcp_lease* dhcp_lease_new() { + struct isc_dhcp_lease* lease = whine_malloc(sizeof(*lease)); + + lease->name = NULL; + lease->fqdn = NULL; + lease->next = NULL; + + return lease; +} + +static void dhcp_lease_free(struct isc_dhcp_lease* lease) { + if (!lease) + return; + + if (lease->name) + free(lease->name); + if (lease->fqdn) + free(lease->fqdn); + free(lease); +} + +static int next_token(char* token, int buffsize, FILE* fp) { + int c, count = 0; + char* cp = token; + + while ((c = getc(fp)) != EOF) { + if (c == '#') { + do { + c = getc(fp); + } while (c != '\n' && c != EOF); + } + + if (c == ' ' || c == '\t' || c == '\n' || c == ';') { + if (count) + break; + } else if ((c != '"') && (count < buffsize - 1)) { + *cp++ = c; + count++; + } + } + + *cp = 0; + return count ? 1 : 0; +} + +static time_t parse_lease_time(const char* token_date, const char* token_time) { + time_t time = (time_t)(-1); + struct tm lease_time; + + static const int months[11] = {31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + if (sscanf(token_date, "%d/%d/%d", &lease_time.tm_year, &lease_time.tm_mon, &lease_time.tm_mday) == 3) { + if (sscanf(token_time, "%d:%d:%d", &lease_time.tm_hour, &lease_time.tm_min, &lease_time.tm_sec) == 3) { + /* There doesn't seem to be a universally available library function + which converts broken-down _GMT_ time to seconds-in-epoch. + The following was borrowed from ISC dhcpd sources, where + it is noted that it might not be entirely accurate for odd seconds. + Since we're trying to get the same answer as dhcpd, that's just + fine here. */ + time = ((((((365 * (lease_time.tm_year - 1970) + + (lease_time.tm_year - 1969) / 4 + + (lease_time.tm_mon > 1 ? months[lease_time.tm_mon - 2] : 0) + + (lease_time.tm_mon > 2 && !((lease_time.tm_year - 1972) & 3 )) + + (lease_time.tm_mday - 1)) * 24) + + lease_time.tm_hour) * 60) + + lease_time.tm_mon) * 60) + + lease_time.tm_sec; + } + } + + return time; +} + +static off_t lease_file_size = (off_t)0; +static ino_t lease_file_inode = (ino_t)0; + +void load_dhcp(time_t now) { + struct isc_dhcp_lease* leases = NULL; + + struct stat statbuf; + if (stat(daemon->lease_file, &statbuf) == -1) { + return; + } + + /* Do nothing if the lease file has not changed. */ + if ((statbuf.st_size <= lease_file_size) && (statbuf.st_ino == lease_file_inode)) + return; + + lease_file_size = statbuf.st_size; + lease_file_inode = statbuf.st_ino; + + FILE* fp = fopen(daemon->lease_file, "r"); + if (!fp) { + my_syslog(LOG_ERR, _("failed to load %s:%s"), daemon->lease_file, strerror(errno)); + return; + } + + my_syslog(LOG_INFO, _("reading %s"), daemon->lease_file); + + char* hostname = daemon->namebuff; + struct in_addr host_address; + time_t time_starts = -1; + time_t time_ends = -1; + int nomem; + + char token[MAXTOK]; + while ((next_token(token, MAXTOK, fp))) { + if (strcmp(token, "lease") == 0) { + hostname[0] = '\0'; + + if (next_token(token, MAXTOK, fp) && ((host_address.s_addr = inet_addr(token)) != (in_addr_t)-1)) { + if (next_token(token, MAXTOK, fp) && *token == '{') { + while (next_token(token, MAXTOK, fp) && *token != '}') { + if ((strcmp(token, "client-hostname") == 0) || (strcmp(token, "hostname") == 0)) { + if (next_token(hostname, MAXDNAME, fp)) { + if (!canonicalise(hostname, &nomem)) { + *hostname = 0; + my_syslog(LOG_ERR, _("bad name in %s"), daemon->lease_file); + } + } + } else if ((strcmp(token, "starts") == 0) || (strcmp(token, "ends") == 0)) { + char token_date[MAXTOK]; + char token_time[MAXTOK]; + + int is_starts = strcmp(token, "starts") == 0; + + // Throw away the weekday and parse the date. + if (next_token(token, MAXTOK, fp) && next_token(token_date, MAXTOK, fp) && next_token(token_time, MAXTOK, fp)) { + time_t time = parse_lease_time(token_date, token_time); + + if (is_starts) + time_starts = time; + else + time_ends = time; + } + } + } + + if (!*hostname) + continue; + + if ((time_starts == -1) || (time_ends == -1)) + continue; + + if (difftime(now, time_ends) > 0) + continue; + + char* dot = strchr(hostname, '.'); + if (dot) { + if (!daemon->domain_suffix || hostname_isequal(dot + 1, daemon->domain_suffix)) { + my_syslog(LOG_WARNING, + _("Ignoring DHCP lease for %s because it has an illegal domain part"), + hostname); + continue; + } + *dot = 0; + } + + struct isc_dhcp_lease* lease = dhcp_lease_new(); + lease->name = strdup(hostname); + lease->addr = host_address; + lease->expires = time_ends; + lease->next = NULL; + + if (daemon->domain_suffix) { + asprintf(&lease->fqdn, "%s.%s", hostname, daemon->domain_suffix); + } + + if (!leases) + leases = lease; + else + leases->next = lease; + } + } + } + } + + fclose(fp); + + // Drop all entries. + cache_unhash_dhcp(); + + while (leases) { + struct isc_dhcp_lease *lease = leases; + leases = lease->next; + + if (lease->fqdn) { + cache_add_dhcp_entry(lease->fqdn, AF_INET, (struct all_addr*)&lease->addr.s_addr, lease->expires); + } + + if (lease->name) { + cache_add_dhcp_entry(lease->name, AF_INET, (struct all_addr*)&lease->addr.s_addr, lease->expires); + } + + // Cleanup + dhcp_lease_free(lease); + } +} + +#endif diff --git a/src/option.c b/src/option.c index ac35e7c..d5a9f1a 100644 --- a/src/option.c +++ b/src/option.c @@ -1763,7 +1763,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(_("bad MX target")); break; -#ifdef HAVE_DHCP +#if (defined HAVE_DHCP) || (defined HAVE_ISC_READER) case 'l': /* --dhcp-leasefile */ daemon->lease_file = opt_string_alloc(arg); break; -- 2.47.3